fix #4556
This implements a number of new safety checks and error messages when object constructors are used: In case objects: * the compiler will prevent you from initializing fields in conflicting branches * When a field from a particular branch is initialized, the compiler will demand that the discriminator field is also supplied with a maching compile-time value In all objects: * When the "requiresInit" pragma is applied to a type, all fields of the type must be initialized when object construction is used. The code will be simplified in a follow up commit.
This commit is contained in:
parent
88c4d6aabe
commit
6edb07091d
2 changed files with 165 additions and 56 deletions
|
|
@ -45,7 +45,7 @@ type
|
||||||
inst*: PInstantiation
|
inst*: PInstantiation
|
||||||
|
|
||||||
TExprFlag* = enum
|
TExprFlag* = enum
|
||||||
efLValue, efWantIterator, efInTypeof,
|
efLValue, efWantIterator, efWantStatic, efInTypeof,
|
||||||
efWantStmt, efAllowStmt, efDetermineType, efExplain,
|
efWantStmt, efAllowStmt, efDetermineType, efExplain,
|
||||||
efAllowDestructor, efWantValue, efOperand, efNoSemCheck,
|
efAllowDestructor, efWantValue, efOperand, efNoSemCheck,
|
||||||
efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo,
|
efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo,
|
||||||
|
|
|
||||||
|
|
@ -2097,25 +2097,137 @@ proc isTupleType(n: PNode): bool =
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc checkInitialized(n: PNode, ids: IntSet, info: TLineInfo) =
|
type
|
||||||
case n.kind
|
InitializationResult = enum
|
||||||
|
initUnknown
|
||||||
|
initFull # All of the fields have been initialized
|
||||||
|
initPartial # Some of the fields have been initialized
|
||||||
|
initNone # None of the fields have been initialized
|
||||||
|
initConflict # Fields from different branches have been initialized
|
||||||
|
|
||||||
|
InitStatus = object
|
||||||
|
status: InitializationResult
|
||||||
|
missingFields: seq[int]
|
||||||
|
conflictingFields: seq[int]
|
||||||
|
assumptions: seq[(int, PNode)] # field and expected value
|
||||||
|
|
||||||
|
proc mergeInitStatus(existing: var InitializationResult,
|
||||||
|
newStatus: InitializationResult) =
|
||||||
|
case newStatus
|
||||||
|
of initConflict:
|
||||||
|
existing = initConflict
|
||||||
|
of initPartial:
|
||||||
|
if existing in {initUnknown, initFull, initNone}:
|
||||||
|
existing = initPartial
|
||||||
|
of initNone:
|
||||||
|
if existing == initUnknown:
|
||||||
|
existing = initNone
|
||||||
|
elif existing == initFull:
|
||||||
|
existing = initPartial
|
||||||
|
of initFull:
|
||||||
|
if existing == initUnknown:
|
||||||
|
existing = initFull
|
||||||
|
elif existing == initNone:
|
||||||
|
existing = initPartial
|
||||||
|
of initUnknown:
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc semConstrField(c: PContext, flags: TExprFlags,
|
||||||
|
field: PSym, initExpr: PNode): PNode =
|
||||||
|
let fieldId = field.name.id
|
||||||
|
|
||||||
|
for i in 1 .. <initExpr.len:
|
||||||
|
let asgnExpr = initExpr[i]
|
||||||
|
internalAssert asgnExpr.kind == nkExprColonExpr
|
||||||
|
|
||||||
|
if fieldId == considerQuotedIdent(asgnExpr[0]).id:
|
||||||
|
if not fieldVisible(c, field):
|
||||||
|
localError(initExpr.info,
|
||||||
|
"the field '$1' is not accessible.", [field.name.s])
|
||||||
|
return
|
||||||
|
|
||||||
|
var initValue: PNode
|
||||||
|
if efWantStatic in flags:
|
||||||
|
initValue = tryConstExpr(c, asgnExpr[1])
|
||||||
|
if initValue == nil:
|
||||||
|
localError(asgnExpr[1].info,
|
||||||
|
"the discriminator '$1' appearing in the construction " &
|
||||||
|
"of a case object must be a compile-time value.",
|
||||||
|
[field.name.s])
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
initValue = semExprWithType(c, asgnExpr[1], flags*{efAllowDestructor})
|
||||||
|
|
||||||
|
asgnExpr.sons[0] = newSymNode(field)
|
||||||
|
asgnExpr.sons[1] = fitNode(c, field.typ, initValue, asgnExpr.info)
|
||||||
|
asgnExpr.flags.incl nfSem
|
||||||
|
return initValue
|
||||||
|
|
||||||
|
if {tfNotNil, tfNeedsInit} * field.typ.flags != {}:
|
||||||
|
localError(initExpr.info, "field not initialized: " & field.name.s)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
proc matchesAnyCaseInNkOf(matched, branches: PNode): bool =
|
||||||
|
for i in 0 .. (branches.len - 2):
|
||||||
|
if exprStructuralEquivalent(branches[i], matched):
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc semConstructFields(c: PContext, info: TLineInfo, flags: TExprFlags,
|
||||||
|
t: PType, recNode, initExpr: PNode): InitStatus =
|
||||||
|
result.status = initUnknown
|
||||||
|
|
||||||
|
case recNode.kind
|
||||||
of nkRecList:
|
of nkRecList:
|
||||||
for i in countup(0, sonsLen(n) - 1):
|
for field in recNode:
|
||||||
checkInitialized(n.sons[i], ids, info)
|
let status = semConstructFields(c, info, flags, nil, field, initExpr)
|
||||||
|
mergeInitStatus(result.status, status.status)
|
||||||
|
|
||||||
|
if t != nil and t.sons[0] != nil:
|
||||||
|
var t = skipTypes(t.sons[0], skipPtrs)
|
||||||
|
let status = semConstructFields(c, info, flags, t, t.n, initExpr)
|
||||||
|
mergeInitStatus(result.status, status.status)
|
||||||
|
|
||||||
of nkRecCase:
|
of nkRecCase:
|
||||||
if (n.sons[0].kind != nkSym): internalError(info, "checkInitialized")
|
let discriminator = recNode.sons[0];
|
||||||
checkInitialized(n.sons[0], ids, info)
|
internalAssert discriminator.kind == nkSym
|
||||||
when false:
|
var selectedBranch = -1
|
||||||
# XXX we cannot check here, as we don't know the branch!
|
|
||||||
for i in countup(1, sonsLen(n) - 1):
|
for i in 1 .. <recNode.len:
|
||||||
case n.sons[i].kind
|
let innerNode = if recNode[i].kind == nkOfBranch:
|
||||||
of nkOfBranch, nkElse: checkInitialized(lastSon(n.sons[i]), ids, info)
|
recNode[i]{-1}
|
||||||
else: internalError(info, "checkInitialized")
|
else:
|
||||||
|
recNode[i][0]
|
||||||
|
|
||||||
|
let s = semConstructFields(c, info, flags, nil, innerNode, initExpr)
|
||||||
|
if s.status notin {initNone, initUnknown}:
|
||||||
|
mergeInitStatus(result.status, s.status)
|
||||||
|
if selectedBranch != -1:
|
||||||
|
localError(initExpr.info, "conflicting branches")
|
||||||
|
result.status = initConflict
|
||||||
|
else:
|
||||||
|
selectedBranch = i
|
||||||
|
|
||||||
|
if selectedBranch >= 0 and selectedBranch < recNode.len - 1:
|
||||||
|
let discriminatorVal = semConstrField(c, flags + {efWantStatic},
|
||||||
|
discriminator.sym, initExpr)
|
||||||
|
if discriminatorVal == nil:
|
||||||
|
localError(initExpr.info, "discrimantor not const")
|
||||||
|
elif not discriminatorVal.matchesAnyCaseInNkOf(recNode[selectedBranch]):
|
||||||
|
localError(initExpr.info, "wrong branch taken")
|
||||||
|
else:
|
||||||
|
result.status = initNone
|
||||||
|
let f = semConstrField(c, flags, discriminator.sym, initExpr)
|
||||||
|
mergeInitStatus(result.status, if f != nil: initFull else: initNone)
|
||||||
|
|
||||||
of nkSym:
|
of nkSym:
|
||||||
if {tfNotNil, tfNeedsInit} * n.sym.typ.flags != {} and
|
let e = semConstrField(c, flags, recNode.sym, initExpr)
|
||||||
n.sym.name.id notin ids:
|
result.status = if e != nil: initFull else: initNone
|
||||||
message(info, errGenerated, "field not initialized: " & n.sym.name.s)
|
|
||||||
else: internalError(info, "checkInitialized")
|
else:
|
||||||
|
internalAssert false
|
||||||
|
|
||||||
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
var t = semTypeNode(c, n.sons[0], nil)
|
var t = semTypeNode(c, n.sons[0], nil)
|
||||||
|
|
@ -2126,46 +2238,43 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
if t.kind != tyObject:
|
if t.kind != tyObject:
|
||||||
localError(n.info, errGenerated, "object constructor needs an object type")
|
localError(n.info, errGenerated, "object constructor needs an object type")
|
||||||
return
|
return
|
||||||
var objType = t
|
|
||||||
var ids = initIntSet()
|
|
||||||
for i in 1.. <n.len:
|
|
||||||
let it = n.sons[i]
|
|
||||||
if it.kind != nkExprColonExpr:
|
|
||||||
localError(n.info, errNamedExprExpected)
|
|
||||||
break
|
|
||||||
let id = considerQuotedIdent(it.sons[0], it)
|
|
||||||
|
|
||||||
if containsOrIncl(ids, id.id):
|
# Check if the object is fully initialized by recursively testing each
|
||||||
localError(it.info, errFieldInitTwice, id.s)
|
# field (if this is a case object, initialized fields in two different
|
||||||
var e = semExprWithType(c, it.sons[1], flags*{efAllowDestructor})
|
# branches will be reported as an error):
|
||||||
var
|
let initResult = semConstructFields(c, n.info, flags, t, t.n, result)
|
||||||
check: PNode = nil
|
if initResult.status == initConflict:
|
||||||
f: PSym
|
localError(n.info,
|
||||||
t = objType
|
"invalid object construction. " &
|
||||||
while true:
|
"fields from conflicting case branches have been initialized.")
|
||||||
check = nil
|
return
|
||||||
f = lookupInRecordAndBuildCheck(c, it, t.n, id, check)
|
|
||||||
if f != nil: break
|
# It's possible that the object was not fully initialized while
|
||||||
if t.sons[0] == nil: break
|
# specifying a .requiresInit. pragma.
|
||||||
t = skipTypes(t.sons[0], skipPtrs)
|
# XXX: Turn this into an error in the next release
|
||||||
if f != nil and fieldVisible(c, f):
|
if tfNeedsInit in t.flags and initResult.status != initFull:
|
||||||
it.sons[0] = newSymNode(f)
|
message(n.info, warnUser,
|
||||||
e = fitNode(c, f.typ, e, it.info)
|
"object type uses the 'requiresInit' pragma, but not all fields " &
|
||||||
# small hack here in a nkObjConstr the ``nkExprColonExpr`` node can have
|
"have been initialized. future versions of Nim will treat this as " &
|
||||||
# 3 children the last being the field check
|
"an error")
|
||||||
if check != nil:
|
|
||||||
check.sons[0] = it.sons[0]
|
# Since we were traversing the object fields, it's possible that
|
||||||
it.add(check)
|
# not all of the fields specified in the constructor was visited.
|
||||||
else:
|
# We'll check for such fields here:
|
||||||
localError(it.info, errUndeclaredFieldX, id.s)
|
for i in 1.. <n.len:
|
||||||
it.sons[1] = e
|
let field = n[i]
|
||||||
result.add it
|
if nfSem notin field.flags:
|
||||||
# XXX object field name check for 'case objects' if the kind is static?
|
let id = considerQuotedIdent(field[0])
|
||||||
if tfNeedsInit in objType.flags:
|
# This node was not processed. There are two possible reasons:
|
||||||
while true:
|
# 1) It was shadowed by a field with the same name on the left
|
||||||
checkInitialized(objType.n, ids, n.info)
|
for j in 1 .. <i:
|
||||||
if objType.sons[0] == nil: break
|
let prevId = considerQuotedIdent(n[j][0])
|
||||||
objType = skipTypes(objType.sons[0], skipPtrs)
|
if prevId.id == id.id:
|
||||||
|
localError(field.info, errFieldInitTwice, id.s)
|
||||||
|
return
|
||||||
|
# 2) No such field exists in the constructed type
|
||||||
|
localError(field.info, errUndeclaredFieldX, id.s)
|
||||||
|
return
|
||||||
|
|
||||||
proc semBlock(c: PContext, n: PNode): PNode =
|
proc semBlock(c: PContext, n: PNode): PNode =
|
||||||
result = n
|
result = n
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue