Don't allow 'var x: T' for objects that require initialization
This commit is contained in:
parent
7652aede41
commit
05a0ec4adb
4 changed files with 63 additions and 37 deletions
|
|
@ -58,6 +58,24 @@ proc isArrayConstr(n: PNode): bool {.inline.} =
|
||||||
result = n.kind == nkBracket and
|
result = n.kind == nkBracket and
|
||||||
n.typ.skipTypes(abstractInst).kind == tyArray
|
n.typ.skipTypes(abstractInst).kind == tyArray
|
||||||
|
|
||||||
|
type
|
||||||
|
ObjConstrContext = object
|
||||||
|
typ: PType # The constructed type
|
||||||
|
initExpr: PNode # The init expression (nkObjConstr)
|
||||||
|
requiresFullInit: bool # A `requiresInit` derived type will
|
||||||
|
# set this to true while visiting
|
||||||
|
# parent types.
|
||||||
|
|
||||||
|
InitStatus = enum # This indicates the result of object construction
|
||||||
|
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
|
||||||
|
|
||||||
|
proc semConstructType(c: PContext, initExpr: PNode,
|
||||||
|
t: PType, flags: TExprFlags): InitStatus
|
||||||
|
|
||||||
template semIdeForTemplateOrGenericCheck(conf, n, requiresCheck) =
|
template semIdeForTemplateOrGenericCheck(conf, n, requiresCheck) =
|
||||||
# we check quickly if the node is where the cursor is
|
# we check quickly if the node is where the cursor is
|
||||||
when defined(nimsuggest):
|
when defined(nimsuggest):
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,6 @@
|
||||||
|
|
||||||
# included from sem.nim
|
# included from sem.nim
|
||||||
|
|
||||||
type
|
|
||||||
InitStatus = 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
|
|
||||||
|
|
||||||
proc mergeInitStatus(existing: var InitStatus, newStatus: InitStatus) =
|
proc mergeInitStatus(existing: var InitStatus, newStatus: InitStatus) =
|
||||||
case newStatus
|
case newStatus
|
||||||
of initConflict:
|
of initConflict:
|
||||||
|
|
@ -136,12 +128,12 @@ 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, initExpr: PNode,
|
proc missingMandatoryFields(c: PContext, fieldsRecList: PNode,
|
||||||
requiresFullInit = false): string =
|
constrCtx: ObjConstrContext): string =
|
||||||
for r in directFieldsInRecList(fieldsRecList):
|
for r in directFieldsInRecList(fieldsRecList):
|
||||||
if requiresFullInit or sfRequiresInit in r.sym.flags or
|
if constrCtx.requiresFullInit or sfRequiresInit in r.sym.flags or
|
||||||
{tfNotNil, tfRequiresInit, tfHasRequiresInit} * r.sym.typ.flags != {}:
|
{tfNotNil, tfRequiresInit, tfHasRequiresInit} * r.sym.typ.flags != {}:
|
||||||
let assignment = locateFieldInInitExpr(c, r.sym, initExpr)
|
let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr)
|
||||||
if assignment == nil:
|
if assignment == nil:
|
||||||
if result.len == 0:
|
if result.len == 0:
|
||||||
result = r.sym.name.s
|
result = r.sym.name.s
|
||||||
|
|
@ -149,34 +141,35 @@ proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode,
|
||||||
result.add ", "
|
result.add ", "
|
||||||
result.add r.sym.name.s
|
result.add r.sym.name.s
|
||||||
|
|
||||||
proc checkForMissingFields(c: PContext, recList, initExpr: PNode,
|
proc checkForMissingFields(c: PContext, recList: PNode,
|
||||||
requiresFullInit = false) =
|
constrCtx: ObjConstrContext) =
|
||||||
let missing = missingMandatoryFields(c, recList, initExpr, requiresFullInit)
|
let missing = missingMandatoryFields(c, recList, constrCtx)
|
||||||
if missing.len > 0:
|
if missing.len > 0:
|
||||||
localError(c.config, initExpr.info, "fields not initialized: $1.", [missing])
|
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,
|
||||||
initExpr: PNode, flags: TExprFlags,
|
constrCtx: ObjConstrContext,
|
||||||
requiresFullInit = false): InitStatus =
|
flags: TExprFlags): InitStatus =
|
||||||
result = initUnknown
|
result = initUnknown
|
||||||
|
|
||||||
case recNode.kind
|
case recNode.kind
|
||||||
of nkRecList:
|
of nkRecList:
|
||||||
for field in recNode:
|
for field in recNode:
|
||||||
let status = semConstructFields(c, field, initExpr,
|
let status = semConstructFields(c, field, constrCtx, flags)
|
||||||
flags, requiresFullInit)
|
|
||||||
mergeInitStatus(result, status)
|
mergeInitStatus(result, status)
|
||||||
|
|
||||||
of nkRecCase:
|
of nkRecCase:
|
||||||
template fieldsPresentInBranch(branchIdx: int): string =
|
template fieldsPresentInBranch(branchIdx: int): string =
|
||||||
let branch = recNode[branchIdx]
|
let branch = recNode[branchIdx]
|
||||||
let fields = branch[^1]
|
let fields = branch[^1]
|
||||||
fieldsPresentInInitExpr(c, fields, initExpr)
|
fieldsPresentInInitExpr(c, fields, constrCtx.initExpr)
|
||||||
|
|
||||||
template checkMissingFields(branchNode: PNode) =
|
template checkMissingFields(branchNode: PNode) =
|
||||||
if branchNode != nil:
|
if branchNode != nil:
|
||||||
let fields = branchNode[^1]
|
let fields = branchNode[^1]
|
||||||
checkForMissingFields(c, fields, initExpr, requiresFullInit)
|
checkForMissingFields(c, fields, constrCtx)
|
||||||
|
|
||||||
let discriminator = recNode[0]
|
let discriminator = recNode[0]
|
||||||
internalAssert c.config, discriminator.kind == nkSym
|
internalAssert c.config, discriminator.kind == nkSym
|
||||||
|
|
@ -184,14 +177,13 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
|
|
||||||
for i in 1..<recNode.len:
|
for i in 1..<recNode.len:
|
||||||
let innerRecords = recNode[i][^1]
|
let innerRecords = recNode[i][^1]
|
||||||
let status = semConstructFields(c, innerRecords, initExpr,
|
let status = semConstructFields(c, innerRecords, constrCtx, flags)
|
||||||
flags, requiresFullInit)
|
|
||||||
if status notin {initNone, initUnknown}:
|
if status notin {initNone, initUnknown}:
|
||||||
mergeInitStatus(result, status)
|
mergeInitStatus(result, status)
|
||||||
if selectedBranch != -1:
|
if selectedBranch != -1:
|
||||||
let prevFields = fieldsPresentInBranch(selectedBranch)
|
let prevFields = fieldsPresentInBranch(selectedBranch)
|
||||||
let currentFields = fieldsPresentInBranch(i)
|
let currentFields = fieldsPresentInBranch(i)
|
||||||
localError(c.config, initExpr.info,
|
localError(c.config, constrCtx.initExpr.info,
|
||||||
("The fields '$1' and '$2' cannot be initialized together, " &
|
("The fields '$1' and '$2' cannot be initialized together, " &
|
||||||
"because they are from conflicting branches in the case object.") %
|
"because they are from conflicting branches in the case object.") %
|
||||||
[prevFields, currentFields])
|
[prevFields, currentFields])
|
||||||
|
|
@ -202,7 +194,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
if selectedBranch != -1:
|
if selectedBranch != -1:
|
||||||
template badDiscriminatorError =
|
template badDiscriminatorError =
|
||||||
let fields = fieldsPresentInBranch(selectedBranch)
|
let fields = fieldsPresentInBranch(selectedBranch)
|
||||||
localError(c.config, initExpr.info,
|
localError(c.config, constrCtx.initExpr.info,
|
||||||
("cannot prove that it's safe to initialize $1 with " &
|
("cannot prove that it's safe to initialize $1 with " &
|
||||||
"the runtime value for the discriminator '$2' ") %
|
"the runtime value for the discriminator '$2' ") %
|
||||||
[fields, discriminator.sym.name.s])
|
[fields, discriminator.sym.name.s])
|
||||||
|
|
@ -210,7 +202,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
|
|
||||||
template wrongBranchError(i) =
|
template wrongBranchError(i) =
|
||||||
let fields = fieldsPresentInBranch(i)
|
let fields = fieldsPresentInBranch(i)
|
||||||
localError(c.config, initExpr.info,
|
localError(c.config, constrCtx.initExpr.info,
|
||||||
"a case selecting discriminator '$1' with value '$2' " &
|
"a case selecting discriminator '$1' with value '$2' " &
|
||||||
"appears in the object construction, but the field(s) $3 " &
|
"appears in the object construction, but the field(s) $3 " &
|
||||||
"are in conflict with this value.",
|
"are in conflict with this value.",
|
||||||
|
|
@ -226,7 +218,8 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
let flags = flags*{efAllowDestructor} + {efPreferStatic,
|
let flags = flags*{efAllowDestructor} + {efPreferStatic,
|
||||||
efPreferNilResult}
|
efPreferNilResult}
|
||||||
var discriminatorVal = semConstrField(c, flags,
|
var discriminatorVal = semConstrField(c, flags,
|
||||||
discriminator.sym, initExpr)
|
discriminator.sym,
|
||||||
|
constrCtx.initExpr)
|
||||||
if discriminatorVal != nil:
|
if discriminatorVal != nil:
|
||||||
discriminatorVal = discriminatorVal.skipHidden
|
discriminatorVal = discriminatorVal.skipHidden
|
||||||
if discriminatorVal.kind notin nkLiterals and (
|
if discriminatorVal.kind notin nkLiterals and (
|
||||||
|
|
@ -295,13 +288,15 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
else:
|
else:
|
||||||
result = initNone
|
result = initNone
|
||||||
let discriminatorVal = semConstrField(c, flags + {efPreferStatic},
|
let discriminatorVal = semConstrField(c, flags + {efPreferStatic},
|
||||||
discriminator.sym, initExpr)
|
discriminator.sym,
|
||||||
|
constrCtx.initExpr)
|
||||||
if discriminatorVal == nil:
|
if discriminatorVal == nil:
|
||||||
# None of the branches were explicitly selected by the user and no
|
# None of the branches were explicitly selected by the user and no
|
||||||
# value was given to the discrimator. We can assume that it will be
|
# value was given to the discrimator. We can assume that it will be
|
||||||
# initialized to zero and this will select a particular branch as
|
# initialized to zero and this will select a particular branch as
|
||||||
# a result:
|
# a result:
|
||||||
let matchedBranch = recNode.pickCaseBranch newIntLit(c.graph, initExpr.info, 0)
|
let defaultValue = newIntLit(c.graph, constrCtx.initExpr.info, 0)
|
||||||
|
let matchedBranch = recNode.pickCaseBranch defaultValue
|
||||||
checkMissingFields matchedBranch
|
checkMissingFields matchedBranch
|
||||||
else:
|
else:
|
||||||
result = initPartial
|
result = initPartial
|
||||||
|
|
@ -317,7 +312,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
|
|
||||||
of nkSym:
|
of nkSym:
|
||||||
let field = recNode.sym
|
let field = recNode.sym
|
||||||
let e = semConstrField(c, flags, field, initExpr)
|
let e = semConstrField(c, flags, field, constrCtx.initExpr)
|
||||||
result = if e != nil: initFull else: initNone
|
result = if e != nil: initFull else: initNone
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -328,17 +323,18 @@ proc semConstructType(c: PContext, initExpr: PNode,
|
||||||
result = initUnknown
|
result = initUnknown
|
||||||
var
|
var
|
||||||
t = t
|
t = t
|
||||||
requiresFullInit = tfRequiresInit in t.flags
|
constrCtx = ObjConstrContext(typ: t, initExpr: initExpr,
|
||||||
|
requiresFullInit: tfRequiresInit in t.flags)
|
||||||
while true:
|
while true:
|
||||||
let status = semConstructFields(c, t.n, initExpr,
|
let status = semConstructFields(c, t.n, constrCtx, flags)
|
||||||
flags, requiresFullInit)
|
|
||||||
mergeInitStatus(result, status)
|
mergeInitStatus(result, status)
|
||||||
if status in {initPartial, initNone, initUnknown}:
|
if status in {initPartial, initNone, initUnknown}:
|
||||||
checkForMissingFields c, t.n, initExpr, requiresFullInit
|
checkForMissingFields 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)
|
||||||
requiresFullInit = requiresFullInit or tfRequiresInit in t.flags
|
constrCtx.requiresFullInit = constrCtx.requiresFullInit or
|
||||||
|
tfRequiresInit in t.flags
|
||||||
|
|
||||||
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
var t = semTypeNode(c, n[0], nil)
|
var t = semTypeNode(c, n[0], nil)
|
||||||
|
|
|
||||||
|
|
@ -614,7 +614,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||||
if tup.kind == tyTuple: setVarType(c, v, tup[j])
|
if tup.kind == tyTuple: setVarType(c, v, tup[j])
|
||||||
else: v.typ = tup
|
else: v.typ = tup
|
||||||
b[j] = newSymNode(v)
|
b[j] = newSymNode(v)
|
||||||
checkNilable(c, v)
|
if def.kind == nkEmpty:
|
||||||
|
if v.typ.kind in {tyObject, tyTuple}:
|
||||||
|
discard semConstructType(c, newNodeI(nkObjConstr, v.info), v.typ, {})
|
||||||
|
else:
|
||||||
|
checkNilable(c, v)
|
||||||
if sfCompileTime in v.flags:
|
if sfCompileTime in v.flags:
|
||||||
var x = newNodeI(result.kind, v.info)
|
var x = newNodeI(result.kind, v.info)
|
||||||
x.add result[i]
|
x.add result[i]
|
||||||
|
|
|
||||||
|
|
@ -125,10 +125,14 @@ accept PartialRequiresInit(a: 10, b: "x")
|
||||||
accept PartialRequiresInit(a: 20)
|
accept PartialRequiresInit(a: 20)
|
||||||
reject PartialRequiresInit(b: "x")
|
reject PartialRequiresInit(b: "x")
|
||||||
reject PartialRequiresInit()
|
reject PartialRequiresInit()
|
||||||
|
reject:
|
||||||
|
var obj: PartialRequiresInit
|
||||||
|
|
||||||
accept FullRequiresInit(a: 10, b: 20)
|
accept FullRequiresInit(a: 10, b: 20)
|
||||||
reject FullRequiresInit(a: 10)
|
reject FullRequiresInit(a: 10)
|
||||||
reject FullRequiresInit(b: 20)
|
reject FullRequiresInit(b: 20)
|
||||||
|
reject:
|
||||||
|
var obj: FullRequiresInit
|
||||||
|
|
||||||
accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: notNilRef, e: 10, d: 20)
|
accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: notNilRef, e: 10, d: 20)
|
||||||
accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10, d: 20)
|
accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10, d: 20)
|
||||||
|
|
@ -136,9 +140,13 @@ reject FullRequiresInitWithParent(a: notNilRef, b: nil, c: nil, e: 10, d: 20) #
|
||||||
reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, e: 10, d: 20) # c should not be missing
|
reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, e: 10, d: 20) # c should not be missing
|
||||||
reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10) # d should not be missing
|
reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10) # d should not be missing
|
||||||
reject FullRequiresInitWithParent()
|
reject FullRequiresInitWithParent()
|
||||||
|
reject:
|
||||||
|
var obj: FullRequiresInitWithParent
|
||||||
|
|
||||||
# this will be accepted, because the false outer branch will be taken and the inner A branch
|
# this will be accepted, because the false outer branch will be taken and the inner A branch
|
||||||
accept TNestedChoices()
|
accept TNestedChoices()
|
||||||
|
accept:
|
||||||
|
var obj: TNestedChoices
|
||||||
|
|
||||||
# but if we supply a run-time value for the inner branch, the compiler won't be able to prove
|
# 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
|
# that the notnil field was initialized
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue