diff options
author | Zahary Karadjov <zahary@gmail.com> | 2020-03-30 18:56:03 +0300 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2020-04-01 19:38:44 +0200 |
commit | ce9a4ed124d798d0287a62e4700a32f1d15878c9 (patch) | |
tree | 73a31457060d51b283be407c5cf37af20b6b0b1e /compiler | |
parent | d374c6373bed4d6807ff70b6179328e79fbe1ac8 (diff) | |
download | Nim-ce9a4ed124d798d0287a62e4700a32f1d15878c9.tar.gz |
Replace tfHasRequiresInit with a more accurate mechanism
The new mechanism can deal with more complex scenarios such as not nil field appearing in a non-default case object branch or a field within a generic object that may depend on a when branch. The commit also plugs another hole: the user is no longer able to create illegal default values through seq.setLen(N).
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/ast.nim | 14 | ||||
-rw-r--r-- | compiler/pragmas.nim | 2 | ||||
-rw-r--r-- | compiler/sem.nim | 4 | ||||
-rw-r--r-- | compiler/semdata.nim | 1 | ||||
-rw-r--r-- | compiler/semexprs.nim | 14 | ||||
-rw-r--r-- | compiler/semobjconstr.nim | 61 | ||||
-rw-r--r-- | compiler/sempass2.nim | 6 | ||||
-rw-r--r-- | compiler/semstmts.nim | 6 | ||||
-rw-r--r-- | compiler/semtypes.nim | 4 | ||||
-rw-r--r-- | compiler/semtypinst.nim | 2 |
10 files changed, 69 insertions, 45 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 01f9ed29b..f4f680811 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -517,11 +517,11 @@ type tfIterator, # type is really an iterator, not a tyProc tfPartial, # type is declared as 'partial' tfNotNil, # type cannot be 'nil' - - tfHasRequiresInit,# type constains a "not nil" constraint somewhere or + tfRequiresInit, # type constains a "not nil" constraint somewhere or # a `requiresInit` field, so the default zero init # is not appropriate - tfRequiresInit, # all fields of the type must be initialized + tfNeedsFullInit, # object type marked with {.requiresInit.} + # all fields must be initialized tfVarIsPtr, # 'var' type is translated like 'ptr' even in C++ mode tfHasMeta, # type contains "wildcard" sub-types such as generic params # or other type classes @@ -1397,7 +1397,7 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType = proc exactReplica*(t: PType): PType = copyType(t, t.owner, true) template requiresInit*(t: PType): bool = - t.flags * {tfRequiresInit, tfHasRequiresInit, tfNotNil} != {} + t.flags * {tfRequiresInit, tfNotNil} != {} proc copySym*(s: PSym): PSym = result = newSym(s.kind, s.name, s.owner, s.info, s.options) @@ -1489,12 +1489,6 @@ proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) = if tfNotNil in elem.flags: if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvocation}: owner.flags.incl tfNotNil - elif owner.kind notin HaveTheirOwnEmpty: - owner.flags.incl tfHasRequiresInit - - if {tfRequiresInit, tfHasRequiresInit} * elem.flags != {}: - if owner.kind in HaveTheirOwnEmpty: discard - else: owner.flags.incl tfHasRequiresInit if elem.isMetaType: owner.flags.incl tfHasMeta diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 604ba063f..100ce1560 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -1090,7 +1090,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, if sym.kind == skField: sym.flags.incl sfRequiresInit elif sym.typ != nil: - incl(sym.typ.flags, tfRequiresInit) + incl(sym.typ.flags, tfNeedsFullInit) else: invalidPragma(c, it) of wByRef: diff --git a/compiler/sem.nim b/compiler/sem.nim index 07b765f88..471a30099 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -46,7 +46,6 @@ proc addParams(c: PContext, n: PNode, kind: TSymKind) proc maybeAddResult(c: PContext, s: PSym, n: PNode) proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc activate(c: PContext, n: PNode) -proc checkDefaultConstruction(c: PContext, typ: PType, info: TLineInfo) proc semQuoteAst(c: PContext, n: PNode): PNode proc finishMethod(c: PContext, s: PSym) proc evalAtCompileTime(c: PContext, n: PNode): PNode @@ -54,6 +53,8 @@ proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode proc semStaticExpr(c: PContext, n: PNode): PNode proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType proc semTypeOf(c: PContext; n: PNode): PNode +proc computeRequiresInit(c: PContext, t: PType): bool +proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) proc hasUnresolvedArgs(c: PContext, n: PNode): bool proc isArrayConstr(n: PNode): bool {.inline.} = result = n.kind == nkBracket and @@ -512,6 +513,7 @@ proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = c.semExpr = semExpr c.semTryExpr = tryExpr c.semTryConstExpr = tryConstExpr + c.computeRequiresInit = computeRequiresInit c.semOperand = semOperand c.semConstBoolExpr = semConstBoolExpr c.semOverloadedCall = semOverloadedCall diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 7fa671722..fbc9762f3 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -103,6 +103,7 @@ type semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semTryExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semTryConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} + computeRequiresInit*: proc (c: PContext, t: PType): bool {.nimcall.} semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet semOverloadedCall*: proc (c: PContext, n, nOrig: PNode, diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d920b0f25..56de00d56 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2267,13 +2267,19 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = of mSizeOf: markUsed(c, n.info, s) result = semSizeof(c, setMs(n, s)) + of mSetLengthSeq: + result = semDirectOp(c, n, flags) + let seqType = result[1].typ.skipTypes({tyPtr, tyRef, # in case we had auto-dereferencing + tyVar, tyGenericInst, tyOwned, tySink, + tyAlias, tyUserTypeClassInst}) + if seqType.kind == tySequence and seqType.base.requiresInit: + localError(c.config, n.info, "setLen can potentially expand the sequence, " & + "but the element type $1 doesn't have a default value.", + [typeToString(seqType.base)]) of mDefault: result = semDirectOp(c, n, flags) c.config.internalAssert result[1].typ.kind == tyTypeDesc - let typ = result[1].typ.base - if typ.kind == tyObject: - checkDefaultConstruction(c, typ, n.info) - elif typ.kind in {tyRef, tyPtr} and tfNotNil in typ.flags: + if result[1].typ.base.requiresInit: localError(c.config, n.info, "not nil types don't have a default value") else: result = semDirectOp(c, n, flags) diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim index 9d39dd5f8..eb0589601 100644 --- a/compiler/semobjconstr.nim +++ b/compiler/semobjconstr.nim @@ -15,7 +15,7 @@ type ObjConstrContext = object typ: PType # The constructed type initExpr: PNode # The init expression (nkObjConstr) - requiresFullInit: bool # A `requiresInit` derived type will + needsFullInit: bool # A `requiresInit` derived type will # set this to true while visiting # parent types. missingFields: seq[PSym] # Fields that the user failed to specify @@ -147,7 +147,7 @@ proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): strin proc collectMissingFields(c: PContext, fieldsRecList: PNode, constrCtx: var ObjConstrContext) = for r in directFieldsInRecList(fieldsRecList): - if constrCtx.requiresFullInit or + if constrCtx.needsFullInit or sfRequiresInit in r.sym.flags or r.sym.typ.requiresInit: let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr) @@ -323,14 +323,11 @@ proc semConstructFields(c: PContext, recNode: PNode, else: internalAssert c.config, false -proc semConstructType(c: PContext, initExpr: PNode, - t: PType, flags: TExprFlags): InitStatus = +proc semConstructTypeAux(c: PContext, + constrCtx: var ObjConstrContext, + flags: TExprFlags): InitStatus = result = initUnknown - - var - t = t - constrCtx = ObjConstrContext(typ: t, initExpr: initExpr, - requiresFullInit: tfRequiresInit in t.flags) + var t = constrCtx.typ while true: let status = semConstructFields(c, t.n, constrCtx, flags) mergeInitStatus(result, status) @@ -339,18 +336,30 @@ proc semConstructType(c: PContext, initExpr: PNode, let base = t[0] if base == nil: break t = skipTypes(base, skipPtrs) - constrCtx.requiresFullInit = constrCtx.requiresFullInit or - tfRequiresInit in t.flags - - # It's possible that the object was not fully initialized while - # specifying a .requiresInit. pragma: - if constrCtx.missingFields.len > 0: - localError(c.config, constrCtx.initExpr.info, - "The $1 type requires the following fields to be initialized: $2.", - [constrCtx.typ.sym.name.s, listSymbolNames(constrCtx.missingFields)]) - -proc checkDefaultConstruction(c: PContext, typ: PType, info: TLineInfo) = - discard semConstructType(c, newNodeI(nkObjConstr, info), typ, {}) + constrCtx.needsFullInit = constrCtx.needsFullInit or + tfNeedsFullInit in t.flags + +proc initConstrContext(t: PType, initExpr: PNode): ObjConstrContext = + ObjConstrContext(typ: t, initExpr: initExpr, + needsFullInit: tfNeedsFullInit in t.flags) + +proc computeRequiresInit(c: PContext, t: PType): bool = + assert t.kind == tyObject + var constrCtx = initConstrContext(t, newNode(nkObjConstr)) + let initResult = semConstructTypeAux(c, constrCtx, {}) + constrCtx.missingFields.len > 0 + +proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) = + var objType = t + while objType.kind != tyObject: + objType = objType.lastSon + assert objType != nil + var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info)) + let initResult = semConstructTypeAux(c, constrCtx, {}) + assert constrCtx.missingFields.len > 0 + localError(c.config, info, + "The $1 type doesn't have a default value. The following fields must be initialized: $2.", + [typeToString(t), listSymbolNames(constrCtx.missingFields)]) proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = var t = semTypeNode(c, n[0], nil) @@ -376,7 +385,15 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = # Check if the object is fully initialized by recursively testing each # field (if this is a case object, initialized fields in two different # branches will be reported as an error): - let initResult = semConstructType(c, result, t, flags) + var constrCtx = initConstrContext(t, result) + let initResult = semConstructTypeAux(c, constrCtx, flags) + + # It's possible that the object was not fully initialized while + # specifying a .requiresInit. pragma: + if constrCtx.missingFields.len > 0: + localError(c.config, result.info, + "The $1 type requires the following fields to be initialized: $2.", + [t.sym.name.s, listSymbolNames(constrCtx.missingFields)]) # Since we were traversing the object fields, it's possible that # not all of the fields specified in the constructor was visited. diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 40b3b15ce..79d969fbb 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -184,7 +184,7 @@ proc initVar(a: PEffects, n: PNode; volatileCheck: bool) = proc initVarViaNew(a: PEffects, n: PNode) = if n.kind != nkSym: return let s = n.sym - if {tfRequiresInit, tfHasRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}: + if {tfRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}: # 'x' is not nil, but that doesn't mean its "not nil" children # are initialized: initVar(a, n, volatileCheck=true) @@ -590,7 +590,7 @@ proc trackCase(tracked: PEffects, n: PNode) = track(tracked, n[0]) let oldState = tracked.init.len let oldFacts = tracked.guards.s.len - let stringCase = skipTypes(n[0].typ, + let stringCase = n[0].typ != nil and skipTypes(n[0].typ, abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} let interesting = not stringCase and interestingCaseExpr(n[0]) and tracked.config.hasWarn(warnProveField) @@ -838,7 +838,7 @@ proc track(tracked: PEffects, n: PNode) = # may not look like an assignment, but it is: let arg = n[1] initVarViaNew(tracked, arg) - if arg.typ.len != 0 and {tfRequiresInit, tfHasRequiresInit} * arg.typ.lastSon.flags != {}: + if arg.typ.len != 0 and {tfRequiresInit} * arg.typ.lastSon.flags != {}: if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and n[2].intVal == 0: # var s: seq[notnil]; newSeq(s, 0) is a special case! diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 23e581a53..215d554fb 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -614,8 +614,10 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = else: v.typ = tup b[j] = newSymNode(v) if def.kind == nkEmpty: - if v.typ.kind == tyObject: - checkDefaultConstruction(c, v.typ, v.info) + let actualType = v.typ.skipTypes({tyGenericInst, tyAlias, + tyUserTypeClassInst}) + if actualType.kind == tyObject and actualType.requiresInit: + defaultConstructionError(c, v.typ, v.info) else: checkNilable(c, v) if sfCompileTime in v.flags: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 39df93b62..21b95d3f6 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -770,8 +770,6 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, f.typ = typ f.position = pos f.options = c.config.options - if sfRequiresInit in f.flags: - rectype.flags.incl tfHasRequiresInit if fieldOwner != nil and {sfImportc, sfExportc} * fieldOwner.flags != {} and not hasCaseFields and f.loc.r == nil: @@ -879,6 +877,8 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; isInheritable: bool): PTy pragma(c, s, n[0], typePragmas) if base == nil and tfInheritable notin result.flags: incl(result.flags, tfFinal) + if c.inGenericContext == 0 and computeRequiresInit(c, result): + result.flags.incl tfRequiresInit proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = if n.len < 1: diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 5aec3eed9..08ea85708 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -613,6 +613,8 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = of tyObject, tyTuple: propagateFieldFlags(result, result.n) + if result.kind == tyObject and cl.c.computeRequiresInit(cl.c, result): + result.flags.incl tfRequiresInit of tyProc: eraseVoidParams(result) |