summary refs log tree commit diff stats
path: root/compiler
diff options
context:
space:
mode:
authorZahary Karadjov <zahary@gmail.com>2020-03-30 18:56:03 +0300
committerAndreas Rumpf <rumpf_a@web.de>2020-04-01 19:38:44 +0200
commitce9a4ed124d798d0287a62e4700a32f1d15878c9 (patch)
tree73a31457060d51b283be407c5cf37af20b6b0b1e /compiler
parentd374c6373bed4d6807ff70b6179328e79fbe1ac8 (diff)
downloadNim-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.nim14
-rw-r--r--compiler/pragmas.nim2
-rw-r--r--compiler/sem.nim4
-rw-r--r--compiler/semdata.nim1
-rw-r--r--compiler/semexprs.nim14
-rw-r--r--compiler/semobjconstr.nim61
-rw-r--r--compiler/sempass2.nim6
-rw-r--r--compiler/semstmts.nim6
-rw-r--r--compiler/semtypes.nim4
-rw-r--r--compiler/semtypinst.nim2
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)