diff options
-rw-r--r-- | compiler/semexprs.nim | 18 | ||||
-rw-r--r-- | doc/manual/types.txt | 4 | ||||
-rw-r--r-- | tests/constructors/tinvalid_construction.nim | 122 |
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) + |