summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/semexprs.nim18
-rw-r--r--doc/manual/types.txt4
-rw-r--r--tests/constructors/tinvalid_construction.nim122
3 files changed, 133 insertions, 11 deletions
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index f80f67160..5a71896ef 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -2146,7 +2146,8 @@ proc semConstrField(c: PContext, flags: TExprFlags,
       return
 
     var initValue = semExprFlagDispatched(c, assignment[1], flags)
-    initValue = fitNode(c, field.typ, initValue, assignment.info)
+    if initValue != nil:
+      initValue = fitNode(c, field.typ, initValue, assignment.info)
     assignment.sons[0] = newSymNode(field)
     assignment.sons[1] = initValue
     assignment.flags.incl nfSem
@@ -2252,9 +2253,8 @@ proc semConstructFields(c: PContext, recNode: PNode,
       if discriminatorVal == nil:
         let fields = fieldsPresentInBranch(selectedBranch)
         localError(initExpr.info,
-          "the discriminator '$1' appearing in the construction of a case " &
-          "object must be a compile-time value in order to prove that it's " &
-          "initialize field(s) $2.",
+          "you must provide a compile-time value for the discriminator '$1' " &
+          "in order to prove that it's safe to initialize $2.",
           [discriminator.sym.name.s, fields])
         mergeInitStatus(result, initNone)
       else:
@@ -2344,17 +2344,15 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
   # field (if this is a case object, initialized fields in two different
   # branches will be reported as an error):
   let initResult = semContructType(c, t, n, flags)
-  if initResult == initConflict:
-    localError(n.info,
-      "invalid object construction. " &
-      "fields from conflicting case branches have been initialized.")
-    return
 
   # It's possible that the object was not fully initialized while
   # specifying a .requiresInit. pragma.
   # XXX: Turn this into an error in the next release
   if tfNeedsInit in t.flags and initResult != initFull:
-    message(n.info, warnUser,
+    # XXX: Disable this warning for now, because tfNeedsInit is propagated
+    # too aggressively from fields to object types (and this is not correct
+    # in case objects)
+    when false: message(n.info, warnUser,
       "object type uses the 'requiresInit' pragma, but not all fields " &
       "have been initialized. future versions of Nim will treat this as " &
       "an error")
diff --git a/doc/manual/types.txt b/doc/manual/types.txt
index 57e086558..e6875f2df 100644
--- a/doc/manual/types.txt
+++ b/doc/manual/types.txt
@@ -694,7 +694,9 @@ the ``case`` statement: The branches in a ``case`` section may be indented too.
 In the example the ``kind`` field is called the `discriminator`:idx:\: For
 safety its address cannot be taken and assignments to it are restricted: The
 new value must not lead to a change of the active object branch. For an object
-branch switch ``system.reset`` has to be used.
+branch switch ``system.reset`` has to be used. Also, when the fields of a
+particular branch are specified during object construction, the correct value
+for the discriminator must be supplied at compile-time.
 
 
 Set type
diff --git a/tests/constructors/tinvalid_construction.nim b/tests/constructors/tinvalid_construction.nim
new file mode 100644
index 000000000..bb3b1bebb
--- /dev/null
+++ b/tests/constructors/tinvalid_construction.nim
@@ -0,0 +1,122 @@
+template accept(x) =
+  static: assert compiles(x)
+
+template reject(x) =
+  static: assert(not compiles(x))
+
+type
+  TRefObj = ref object
+    x: int
+
+  THasNotNils = object of TObject
+    a: TRefObj not nil
+    b: TRefObj not nil
+    c: TRefObj
+
+  THasNotNilsRef = ref THasNotNils
+
+  TChoice = enum A, B, C, D, E, F
+
+  TBaseHasNotNils = object of THasNotNils
+    case choice: TChoice
+    of A:
+      moreNotNils: THasNotNils
+    of B:
+      indirectNotNils: ref THasNotNils
+    else:
+      discard
+
+  TObj = object
+    case choice: TChoice
+    of A:
+      a: int
+    of B, C:
+      bc: int
+    of D:
+      d: TRefObj
+    of E:
+      e1: TRefObj
+      e2: int
+    else:
+      f: string
+
+  TNestedChoices = object
+    case outerChoice: bool
+    of true:
+      truthy: int
+    else:
+      case innerChoice: TChoice
+      of A:
+        a: int
+      of B:
+        b: int
+      else:
+        notnil: TRefObj not nil
+
+var x = D
+var nilRef: TRefObj
+var notNilRef = TRefObj(x: 20)
+
+proc makeHasNotNils: ref THasNotNils =
+  result.a = TRefObj(x: 10)
+  result.b = TRefObj(x: 20)
+
+accept TObj()
+accept TObj(choice: A)
+reject TObj(choice: A, bc: 10)  # bc is in the wrong branch
+accept TObj(choice: B, bc: 20)
+reject TObj(a: 10)              # branch selected without providing discriminator
+reject TObj(choice: x, a: 10)   # the discrimantor must be a compile-time value when a branch is selected
+accept TObj(choice: x)          # it's OK to use run-time value when a branch is not selected
+accept TObj(choice: F, f: "")   # match an else clause
+reject TObj(f: "")              # the discriminator must still be provided for an else clause
+reject TObj(a: 10, f: "")       # conflicting fields
+accept TObj(choice: E, e1: TRefObj(x: 10), e2: 10)
+
+accept THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
+# XXX: the "not nil" logic in the compiler is not strong enough to catch this one yet:
+# reject THasNotNils(a: notNilRef, b: nilRef, c: nilRef)
+reject THasNotNils(b: notNilRef, c: notNilRef)              # there is a missing not nil field
+reject THasNotNils()                                        # again, missing fields
+accept THasNotNils(a: notNilRef, b: notNilRef)              # it's OK to omit a non-mandatory field
+
+# missing not nils in base
+reject TBaseHasNotNils()
+
+# once you take care of them, it's ok
+accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: D)
+
+# this one is tricky!
+# it has to be rejected, because choice gets value A by default (0) and this means
+# that the THasNotNils field will be active (and it will demand more initialized fields).
+reject TBaseHasNotNils(a: notNilRef, b: notNilRef)
+
+# you can select a branch without mandatory fields
+accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B)
+accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: nil)
+
+# but once you select a branch with mandatory fields, you must specify them
+reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A)
+reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A, indirectNotNils: nil)
+reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A, moreNotNils: THasNotNils())
+accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A, moreNotNils: THasNotNils(a: notNilRef, b: notNilRef))
+
+# all rules apply to sub-objects as well
+accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: makeHasNotNils())
+reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef())
+accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef(a: notNilRef, b: notNilRef))
+
+# this will be accepted, because the false outer branch will be taken and the inner A branch
+accept TNestedChoices()
+
+# but if we supply a run-time value for the inner branch, the compiler won't be able to prove
+# that the notnil field was initialized
+reject TNestedChoices(outerChoice: false, innerChoice: x) # XXX: The error message is not very good here
+reject TNestedChoices(outerChoice: true,  innerChoice: A) # XXX: The error message is not very good here
+
+accept TNestedChoices(outerChoice: false, innerChoice: B)
+
+reject TNestedChoices(outerChoice: false, innerChoice: C)
+accept TNestedChoices(outerChoice: false, innerChoice: C, notnil: notNilRef)
+reject TNestedChoices(outerChoice: false, innerChoice: C, notnil: nil)
+