More precise error messages for uninitialized fields in the presence of inheritance

This commit is contained in:
Zahary Karadjov 2020-03-29 19:05:01 +03:00 • committed by Andreas Rumpf
commit 7b7e42be54
3 changed files with 39 additions and 38 deletions

View file

@ -1041,3 +1041,10 @@ proc isAddrNode*(n: PNode): bool =
if n[0].kind == nkSym and n[0].sym.magic == mAddr: true if n[0].kind == nkSym and n[0].sym.magic == mAddr: true
else: false else: false
else: false else: false
proc listSymbolNames*(symbols: openArray[PSym]): string =
for sym in symbols:
if result.len > 0:
result.add ", "
result.add sym.name.s

View file

@ -13,11 +13,12 @@
type type
ObjConstrContext = object ObjConstrContext = object
typ: PType # The constructed type typ: PType # The constructed type
initExpr: PNode # The init expression (nkObjConstr) initExpr: PNode # The init expression (nkObjConstr)
requiresFullInit: bool # A `requiresInit` derived type will requiresFullInit: bool # A `requiresInit` derived type will
# set this to true while visiting # set this to true while visiting
# parent types. # parent types.
missingFields: seq[PSym] # Fields that the user failed to specify
InitStatus = enum # This indicates the result of object construction InitStatus = enum # This indicates the result of object construction
initUnknown initUnknown
@ -143,30 +144,18 @@ proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): strin
if result.len != 0: result.add ", " if result.len != 0: result.add ", "
result.add field.sym.name.s.quoteStr result.add field.sym.name.s.quoteStr
proc missingMandatoryFields(c: PContext, fieldsRecList: PNode, proc collectMissingFields(c: PContext, fieldsRecList: PNode,
constrCtx: ObjConstrContext): string = constrCtx: var ObjConstrContext) =
for r in directFieldsInRecList(fieldsRecList): for r in directFieldsInRecList(fieldsRecList):
if constrCtx.requiresFullInit or if constrCtx.requiresFullInit or
sfRequiresInit in r.sym.flags or sfRequiresInit in r.sym.flags or
r.sym.typ.requiresInit: r.sym.typ.requiresInit:
let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr) let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr)
if assignment == nil: if assignment == nil:
if result.len == 0: constrCtx.missingFields.add r.sym
result = r.sym.name.s
else:
result.add ", "
result.add r.sym.name.s
proc checkForMissingFields(c: PContext, recList: PNode,
constrCtx: ObjConstrContext) =
let missing = missingMandatoryFields(c, recList, constrCtx)
if missing.len > 0:
localError(c.config, constrCtx.initExpr.info,
"The $1 type requires the following fields to be initialized: $2.",
[constrCtx.typ.sym.name.s, missing])
proc semConstructFields(c: PContext, recNode: PNode, proc semConstructFields(c: PContext, recNode: PNode,
constrCtx: ObjConstrContext, constrCtx: var ObjConstrContext,
flags: TExprFlags): InitStatus = flags: TExprFlags): InitStatus =
result = initUnknown result = initUnknown
@ -182,10 +171,10 @@ proc semConstructFields(c: PContext, recNode: PNode,
let fields = branch[^1] let fields = branch[^1]
fieldsPresentInInitExpr(c, fields, constrCtx.initExpr) fieldsPresentInInitExpr(c, fields, constrCtx.initExpr)
template checkMissingFields(branchNode: PNode) = template collectMissingFields(branchNode: PNode) =
if branchNode != nil: if branchNode != nil:
let fields = branchNode[^1] let fields = branchNode[^1]
checkForMissingFields(c, fields, constrCtx) collectMissingFields(c, fields, constrCtx)
let discriminator = recNode[0] let discriminator = recNode[0]
internalAssert c.config, discriminator.kind == nkSym internalAssert c.config, discriminator.kind == nkSym
@ -299,7 +288,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
# When a branch is selected with a partial match, some of the fields # When a branch is selected with a partial match, some of the fields
# that were not initialized may be mandatory. We must check for this: # that were not initialized may be mandatory. We must check for this:
if result == initPartial: if result == initPartial:
checkMissingFields branchNode collectMissingFields branchNode
else: else:
result = initNone result = initNone
@ -313,18 +302,18 @@ proc semConstructFields(c: PContext, recNode: PNode,
# a result: # a result:
let defaultValue = newIntLit(c.graph, constrCtx.initExpr.info, 0) let defaultValue = newIntLit(c.graph, constrCtx.initExpr.info, 0)
let matchedBranch = recNode.pickCaseBranch defaultValue let matchedBranch = recNode.pickCaseBranch defaultValue
checkMissingFields matchedBranch collectMissingFields matchedBranch
else: else:
result = initPartial result = initPartial
if discriminatorVal.kind == nkIntLit: if discriminatorVal.kind == nkIntLit:
# When the discriminator is a compile-time value, we also know # When the discriminator is a compile-time value, we also know
# which branch will be selected: # which branch will be selected:
let matchedBranch = recNode.pickCaseBranch discriminatorVal let matchedBranch = recNode.pickCaseBranch discriminatorVal
if matchedBranch != nil: checkMissingFields matchedBranch if matchedBranch != nil: collectMissingFields matchedBranch
else: else:
# All bets are off. If any of the branches has a mandatory # All bets are off. If any of the branches has a mandatory
# fields we must produce an error: # fields we must produce an error:
for i in 1..<recNode.len: checkMissingFields recNode[i] for i in 1..<recNode.len: collectMissingFields recNode[i]
of nkSym: of nkSym:
let field = recNode.sym let field = recNode.sym
@ -337,6 +326,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
proc semConstructType(c: PContext, initExpr: PNode, proc semConstructType(c: PContext, initExpr: PNode,
t: PType, flags: TExprFlags): InitStatus = t: PType, flags: TExprFlags): InitStatus =
result = initUnknown result = initUnknown
var var
t = t t = t
constrCtx = ObjConstrContext(typ: t, initExpr: initExpr, constrCtx = ObjConstrContext(typ: t, initExpr: initExpr,
@ -345,13 +335,20 @@ proc semConstructType(c: PContext, initExpr: PNode,
let status = semConstructFields(c, t.n, constrCtx, flags) let status = semConstructFields(c, t.n, constrCtx, flags)
mergeInitStatus(result, status) mergeInitStatus(result, status)
if status in {initPartial, initNone, initUnknown}: if status in {initPartial, initNone, initUnknown}:
checkForMissingFields c, t.n, constrCtx collectMissingFields c, t.n, constrCtx
let base = t[0] let base = t[0]
if base == nil: break if base == nil: break
t = skipTypes(base, skipPtrs) t = skipTypes(base, skipPtrs)
constrCtx.requiresFullInit = constrCtx.requiresFullInit or constrCtx.requiresFullInit = constrCtx.requiresFullInit or
tfRequiresInit in t.flags tfRequiresInit in t.flags
# It's possible that the object was not fully initialized while
# specifying a .requiresInit. pragma:
if constrCtx.missingFields.len > 0:
localError(c.config, constrCtx.initExpr.info,
"The $1 type requires the following fields to be initialized: $2.",
[constrCtx.typ.sym.name.s, listSymbolNames(constrCtx.missingFields)])
proc checkDefaultConstruction(c: PContext, typ: PType, info: TLineInfo) = proc checkDefaultConstruction(c: PContext, typ: PType, info: TLineInfo) =
discard semConstructType(c, newNodeI(nkObjConstr, info), typ, {}) discard semConstructType(c, newNodeI(nkObjConstr, info), typ, {})
@ -381,13 +378,6 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
# branches will be reported as an error): # branches will be reported as an error):
let initResult = semConstructType(c, result, t, flags) let initResult = semConstructType(c, result, t, flags)
# It's possible that the object was not fully initialized while
# specifying a .requiresInit. pragma.
if tfRequiresInit in t.flags and initResult != initFull:
localError(c.config, n.info,
"object type uses the 'requiresInit' pragma, but not all fields " &
"have been initialized.")
# Since we were traversing the object fields, it's possible that # Since we were traversing the object fields, it's possible that
# not all of the fields specified in the constructor was visited. # not all of the fields specified in the constructor was visited.
# We'll check for such fields here: # We'll check for such fields here:

View file

@ -1,13 +1,17 @@
discard """ discard """
errormsg: "The Foo type requires the following fields to be initialized: bar" errormsg: "The Foo type requires the following fields to be initialized: bar, baz"
line: "13" line: "17"
""" """
{.experimental: "notnil".} {.experimental: "notnil".}
# bug #2355 # bug #2355
type type
Foo = object Base = object of RootObj
baz: ref int not nil
Foo = object of Base
foo: ref int foo: ref int
bar: ref int not nil bar: ref int not nil
var x: ref int = new(int) var x: ref int = new(int)
# Create instance without initializing the `bar` field # Create instance without initializing the `bar` field
var f = Foo(foo: x) var f = Foo(foo: x)