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:
Zahary Karadjov 2017-03-18 01:44:51 +02:00
commit 6edb07091d
2 changed files with 165 additions and 56 deletions

View file

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

View file

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