More precise error messages for uninitialized fields in the presence of inheritance
This commit is contained in:
parent
0521f98486
commit
7b7e42be54
3 changed files with 39 additions and 38 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue