object construction: test cases and manual additions
This commit is contained in:
parent
564c0acae2
commit
f162214d5d
3 changed files with 133 additions and 11 deletions
|
|
@ -2146,6 +2146,7 @@ proc semConstrField(c: PContext, flags: TExprFlags,
|
||||||
return
|
return
|
||||||
|
|
||||||
var initValue = semExprFlagDispatched(c, assignment[1], flags)
|
var initValue = semExprFlagDispatched(c, assignment[1], flags)
|
||||||
|
if initValue != nil:
|
||||||
initValue = fitNode(c, field.typ, initValue, assignment.info)
|
initValue = fitNode(c, field.typ, initValue, assignment.info)
|
||||||
assignment.sons[0] = newSymNode(field)
|
assignment.sons[0] = newSymNode(field)
|
||||||
assignment.sons[1] = initValue
|
assignment.sons[1] = initValue
|
||||||
|
|
@ -2252,9 +2253,8 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
if discriminatorVal == nil:
|
if discriminatorVal == nil:
|
||||||
let fields = fieldsPresentInBranch(selectedBranch)
|
let fields = fieldsPresentInBranch(selectedBranch)
|
||||||
localError(initExpr.info,
|
localError(initExpr.info,
|
||||||
"the discriminator '$1' appearing in the construction of a case " &
|
"you must provide a compile-time value for the discriminator '$1' " &
|
||||||
"object must be a compile-time value in order to prove that it's " &
|
"in order to prove that it's safe to initialize $2.",
|
||||||
"initialize field(s) $2.",
|
|
||||||
[discriminator.sym.name.s, fields])
|
[discriminator.sym.name.s, fields])
|
||||||
mergeInitStatus(result, initNone)
|
mergeInitStatus(result, initNone)
|
||||||
else:
|
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
|
# field (if this is a case object, initialized fields in two different
|
||||||
# branches will be reported as an error):
|
# branches will be reported as an error):
|
||||||
let initResult = semContructType(c, t, n, flags)
|
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
|
# It's possible that the object was not fully initialized while
|
||||||
# specifying a .requiresInit. pragma.
|
# specifying a .requiresInit. pragma.
|
||||||
# XXX: Turn this into an error in the next release
|
# XXX: Turn this into an error in the next release
|
||||||
if tfNeedsInit in t.flags and initResult != initFull:
|
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 " &
|
"object type uses the 'requiresInit' pragma, but not all fields " &
|
||||||
"have been initialized. future versions of Nim will treat this as " &
|
"have been initialized. future versions of Nim will treat this as " &
|
||||||
"an error")
|
"an error")
|
||||||
|
|
|
||||||
|
|
@ -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
|
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
|
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
|
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
|
Set type
|
||||||
|
|
|
||||||
122
tests/constructors/tinvalid_construction.nim
Normal file
122
tests/constructors/tinvalid_construction.nim
Normal 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)
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue