summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorZahary Karadjov <zahary@gmail.com>2020-03-27 18:43:23 +0200
committerAndreas Rumpf <rumpf_a@web.de>2020-04-01 19:38:44 +0200
commit7652aede41d81b4e7db2c70ffe462c6fb675d078 (patch)
treed44427bafa4bbdf19adbfa629aafcd0dc932a2ba
parenta8b6222c862d207a32c104699fc4f0f0a8bced17 (diff)
downloadNim-7652aede41d81b4e7db2c70ffe462c6fb675d078.tar.gz
More sophistication; Allow requiresInit to be specified per-field
-rw-r--r--compiler/ast.nim3
-rw-r--r--compiler/pragmas.nim10
-rw-r--r--compiler/semobjconstr.nim32
-rw-r--r--tests/constructors/tinvalid_construction.nim31
4 files changed, 61 insertions, 15 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index a4630732b..a7eb2cac5 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -259,6 +259,7 @@ type
                       # needed for the code generator
     sfProcvar,        # proc can be passed to a proc var
     sfDiscriminant,   # field is a discriminant in a record/object
+    sfRequiresInit,   # field must be initialized during construction
     sfDeprecated,     # symbol is deprecated
     sfExplain,        # provide more diagnostics when this symbol is used
     sfError,          # usage of symbol should trigger a compile-time error
@@ -1488,7 +1489,7 @@ proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) =
     elif owner.kind notin HaveTheirOwnEmpty:
       owner.flags.incl tfHasRequiresInit
 
-  if tfRequiresInit in elem.flags:
+  if {tfRequiresInit, tfHasRequiresInit} * elem.flags != {}:
     if owner.kind in HaveTheirOwnEmpty: discard
     else: owner.flags.incl tfHasRequiresInit
 
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index e4b72bed7..604ba063f 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -65,7 +65,7 @@ const
     wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
     wBorrow, wGcSafe, wPartial, wExplain, wPackage}
   fieldPragmas* = declPragmas + {
-    wGuard, wBitsize, wCursor} - {wExportNims, wNodecl} # why exclude these?
+    wGuard, wBitsize, wCursor, wRequiresInit} - {wExportNims, wNodecl} # why exclude these?
   varPragmas* = declPragmas + {wVolatile, wRegister, wThreadVar,
     wMagic, wHeader, wCompilerProc, wCore, wDynlib,
     wNoInit, wCompileTime, wGlobal,
@@ -1087,8 +1087,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
         else: incl(sym.typ.flags, tfUnion)
       of wRequiresInit:
         noVal(c, it)
-        if sym.typ == nil: invalidPragma(c, it)
-        else: incl(sym.typ.flags, tfRequiresInit)
+        if sym.kind == skField:
+          sym.flags.incl sfRequiresInit
+        elif sym.typ != nil:
+          incl(sym.typ.flags, tfRequiresInit)
+        else:
+          invalidPragma(c, it)
       of wByRef:
         noVal(c, it)
         if sym == nil or sym.typ == nil:
diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim
index bc1e85425..ee79f1a67 100644
--- a/compiler/semobjconstr.nim
+++ b/compiler/semobjconstr.nim
@@ -136,9 +136,11 @@ proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): strin
       if result.len != 0: result.add ", "
       result.add field.sym.name.s.quoteStr
 
-proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode): string =
+proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode,
+                            requiresFullInit = false): string =
   for r in directFieldsInRecList(fieldsRecList):
-    if {tfNotNil, tfRequiresInit} * r.sym.typ.flags != {}:
+    if requiresFullInit or sfRequiresInit in r.sym.flags or
+       {tfNotNil, tfRequiresInit, tfHasRequiresInit} * r.sym.typ.flags != {}:
       let assignment = locateFieldInInitExpr(c, r.sym, initExpr)
       if assignment == nil:
         if result.len == 0:
@@ -147,19 +149,22 @@ proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode): string
           result.add ", "
           result.add r.sym.name.s
 
-proc checkForMissingFields(c: PContext, recList, initExpr: PNode) =
-  let missing = missingMandatoryFields(c, recList, initExpr)
+proc checkForMissingFields(c: PContext, recList, initExpr: PNode,
+                           requiresFullInit = false) =
+  let missing = missingMandatoryFields(c, recList, initExpr, requiresFullInit)
   if missing.len > 0:
     localError(c.config, initExpr.info, "fields not initialized: $1.", [missing])
 
 proc semConstructFields(c: PContext, recNode: PNode,
-                        initExpr: PNode, flags: TExprFlags): InitStatus =
+                        initExpr: PNode, flags: TExprFlags,
+                        requiresFullInit = false): InitStatus =
   result = initUnknown
 
   case recNode.kind
   of nkRecList:
     for field in recNode:
-      let status = semConstructFields(c, field, initExpr, flags)
+      let status = semConstructFields(c, field, initExpr,
+                                      flags, requiresFullInit)
       mergeInitStatus(result, status)
 
   of nkRecCase:
@@ -171,7 +176,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
     template checkMissingFields(branchNode: PNode) =
       if branchNode != nil:
         let fields = branchNode[^1]
-        checkForMissingFields(c, fields, initExpr)
+        checkForMissingFields(c, fields, initExpr, requiresFullInit)
 
     let discriminator = recNode[0]
     internalAssert c.config, discriminator.kind == nkSym
@@ -179,7 +184,8 @@ proc semConstructFields(c: PContext, recNode: PNode,
 
     for i in 1..<recNode.len:
       let innerRecords = recNode[i][^1]
-      let status = semConstructFields(c, innerRecords, initExpr, flags)
+      let status = semConstructFields(c, innerRecords, initExpr,
+                                      flags, requiresFullInit)
       if status notin {initNone, initUnknown}:
         mergeInitStatus(result, status)
         if selectedBranch != -1:
@@ -319,16 +325,20 @@ proc semConstructFields(c: PContext, recNode: PNode,
 
 proc semConstructType(c: PContext, initExpr: PNode,
                       t: PType, flags: TExprFlags): InitStatus =
-  var t = t
   result = initUnknown
+  var
+    t = t
+    requiresFullInit = tfRequiresInit in t.flags
   while true:
-    let status = semConstructFields(c, t.n, initExpr, flags)
+    let status = semConstructFields(c, t.n, initExpr,
+                                    flags, requiresFullInit)
     mergeInitStatus(result, status)
     if status in {initPartial, initNone, initUnknown}:
-      checkForMissingFields c, t.n, initExpr
+      checkForMissingFields c, t.n, initExpr, requiresFullInit
     let base = t[0]
     if base == nil: break
     t = skipTypes(base, skipPtrs)
+    requiresFullInit = requiresFullInit or tfRequiresInit in t.flags
 
 proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
   var t = semTypeNode(c, n[0], nil)
diff --git a/tests/constructors/tinvalid_construction.nim b/tests/constructors/tinvalid_construction.nim
index b3e56eec6..aaf06d4f2 100644
--- a/tests/constructors/tinvalid_construction.nim
+++ b/tests/constructors/tinvalid_construction.nim
@@ -3,7 +3,9 @@ template accept(x) =
 
 template reject(x) =
   static: assert(not compiles(x))
+
 {.experimental: "notnil".}
+
 type
   TRefObj = ref object
     x: int
@@ -26,6 +28,18 @@ type
     else:
       discard
 
+  PartialRequiresInit = object
+    a {.requiresInit.}: int
+    b: string
+
+  FullRequiresInit {.requiresInit.} = object
+    a: int
+    b: int
+
+  FullRequiresInitWithParent {.requiresInit.} = object of THasNotNils
+    e: int
+    d: int
+
   TObj = object
     case choice: TChoice
     of A:
@@ -106,6 +120,23 @@ accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: m
 reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef())
 accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef(a: notNilRef, b: notNilRef))
 
+# Accept only instances where the `a` field is present
+accept PartialRequiresInit(a: 10, b: "x")
+accept PartialRequiresInit(a: 20)
+reject PartialRequiresInit(b: "x")
+reject PartialRequiresInit()
+
+accept FullRequiresInit(a: 10, b: 20)
+reject FullRequiresInit(a: 10)
+reject FullRequiresInit(b: 20)
+
+accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: notNilRef, e: 10, d: 20)
+accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10, d: 20)
+reject FullRequiresInitWithParent(a: notNilRef, b: nil, c: nil, e: 10, d: 20) # b should not be nil
+reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, e: 10, d: 20)   # c should not be missing
+reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10)  # d should not be missing
+reject FullRequiresInitWithParent()
+
 # this will be accepted, because the false outer branch will be taken and the inner A branch
 accept TNestedChoices()