object construction: test cases and manual additions

This commit is contained in:
Zahary Karadjov
2017-03-18 20:21:36 +02:00
parent 564c0acae2
commit f162214d5d
3 changed files with 133 additions and 11 deletions

View File

@@ -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")

View File

@@ -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

View File

@@ -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)