Turn the warning for uninitialized (result) variables into errors
This commit is contained in:
parent
e0bb78553a
commit
1b570f2b18
7 changed files with 103 additions and 17 deletions
|
|
@ -1396,6 +1396,9 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType =
|
||||||
|
|
||||||
proc exactReplica*(t: PType): PType = copyType(t, t.owner, true)
|
proc exactReplica*(t: PType): PType = copyType(t, t.owner, true)
|
||||||
|
|
||||||
|
template requiresInit*(t: PType): bool =
|
||||||
|
t.flags * {tfRequiresInit, tfHasRequiresInit, tfNotNil} != {}
|
||||||
|
|
||||||
proc copySym*(s: PSym): PSym =
|
proc copySym*(s: PSym): PSym =
|
||||||
result = newSym(s.kind, s.name, s.owner, s.info, s.options)
|
result = newSym(s.kind, s.name, s.owner, s.info, s.options)
|
||||||
#result.ast = nil # BUGFIX; was: s.ast which made problems
|
#result.ast = nil # BUGFIX; was: s.ast which made problems
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ type
|
||||||
errGeneralParseError,
|
errGeneralParseError,
|
||||||
errNewSectionExpected,
|
errNewSectionExpected,
|
||||||
errInvalidDirectiveX,
|
errInvalidDirectiveX,
|
||||||
|
errProveInit,
|
||||||
errGenerated,
|
errGenerated,
|
||||||
errUser,
|
errUser,
|
||||||
warnCannotOpenFile,
|
warnCannotOpenFile,
|
||||||
|
|
@ -63,6 +64,7 @@ const
|
||||||
errGeneralParseError: "general parse error",
|
errGeneralParseError: "general parse error",
|
||||||
errNewSectionExpected: "new section expected",
|
errNewSectionExpected: "new section expected",
|
||||||
errInvalidDirectiveX: "invalid directive: '$1'",
|
errInvalidDirectiveX: "invalid directive: '$1'",
|
||||||
|
errProveInit: "Cannot prove that '$1' is initialized.",
|
||||||
errGenerated: "$1",
|
errGenerated: "$1",
|
||||||
errUser: "$1",
|
errUser: "$1",
|
||||||
warnCannotOpenFile: "cannot open '$1'",
|
warnCannotOpenFile: "cannot open '$1'",
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,9 @@ proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): strin
|
||||||
proc missingMandatoryFields(c: PContext, fieldsRecList: PNode,
|
proc missingMandatoryFields(c: PContext, fieldsRecList: PNode,
|
||||||
constrCtx: ObjConstrContext): string =
|
constrCtx: ObjConstrContext): string =
|
||||||
for r in directFieldsInRecList(fieldsRecList):
|
for r in directFieldsInRecList(fieldsRecList):
|
||||||
if constrCtx.requiresFullInit or sfRequiresInit in r.sym.flags or
|
if constrCtx.requiresFullInit or
|
||||||
{tfNotNil, tfRequiresInit, tfHasRequiresInit} * r.sym.typ.flags != {}:
|
sfRequiresInit in r.sym.flags or
|
||||||
|
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:
|
if result.len == 0:
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ proc initVar(a: PEffects, n: PNode; volatileCheck: bool) =
|
||||||
proc initVarViaNew(a: PEffects, n: PNode) =
|
proc initVarViaNew(a: PEffects, n: PNode) =
|
||||||
if n.kind != nkSym: return
|
if n.kind != nkSym: return
|
||||||
let s = n.sym
|
let s = n.sym
|
||||||
if {tfRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}:
|
if {tfRequiresInit, tfHasRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}:
|
||||||
# 'x' is not nil, but that doesn't mean its "not nil" children
|
# 'x' is not nil, but that doesn't mean its "not nil" children
|
||||||
# are initialized:
|
# are initialized:
|
||||||
initVar(a, n, volatileCheck=true)
|
initVar(a, n, volatileCheck=true)
|
||||||
|
|
@ -253,8 +253,8 @@ proc useVar(a: PEffects, n: PNode) =
|
||||||
# If the variable is explicitly marked as .noinit. do not emit any error
|
# If the variable is explicitly marked as .noinit. do not emit any error
|
||||||
a.init.add s.id
|
a.init.add s.id
|
||||||
elif s.id notin a.init:
|
elif s.id notin a.init:
|
||||||
if {tfRequiresInit, tfNotNil} * s.typ.flags != {}:
|
if s.typ.requiresInit:
|
||||||
message(a.config, n.info, warnProveInit, s.name.s)
|
localError(a.config, n.info, errProveInit, s.name.s)
|
||||||
else:
|
else:
|
||||||
message(a.config, n.info, warnUninit, s.name.s)
|
message(a.config, n.info, warnUninit, s.name.s)
|
||||||
# prevent superfluous warnings about the same variable:
|
# prevent superfluous warnings about the same variable:
|
||||||
|
|
@ -838,13 +838,13 @@ proc track(tracked: PEffects, n: PNode) =
|
||||||
# may not look like an assignment, but it is:
|
# may not look like an assignment, but it is:
|
||||||
let arg = n[1]
|
let arg = n[1]
|
||||||
initVarViaNew(tracked, arg)
|
initVarViaNew(tracked, arg)
|
||||||
if arg.typ.len != 0 and {tfRequiresInit} * arg.typ.lastSon.flags != {}:
|
if arg.typ.len != 0 and {tfRequiresInit, tfHasRequiresInit} * arg.typ.lastSon.flags != {}:
|
||||||
if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and
|
if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and
|
||||||
n[2].intVal == 0:
|
n[2].intVal == 0:
|
||||||
# var s: seq[notnil]; newSeq(s, 0) is a special case!
|
# var s: seq[notnil]; newSeq(s, 0) is a special case!
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
message(tracked.config, arg.info, warnProveInit, $arg)
|
localError(tracked.config, arg.info, errProveInit, $arg)
|
||||||
|
|
||||||
# check required for 'nim check':
|
# check required for 'nim check':
|
||||||
if n[1].typ.len > 0:
|
if n[1].typ.len > 0:
|
||||||
|
|
@ -1203,12 +1203,11 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
||||||
createTypeBoundOps(t, typ, param.info)
|
createTypeBoundOps(t, typ, param.info)
|
||||||
|
|
||||||
if not isEmptyType(s.typ[0]) and
|
if not isEmptyType(s.typ[0]) and
|
||||||
({tfRequiresInit, tfNotNil} * s.typ[0].flags != {} or
|
(s.typ[0].requiresInit or s.typ[0].skipTypes(abstractInst).kind == tyVar) and
|
||||||
s.typ[0].skipTypes(abstractInst).kind == tyVar) and
|
s.kind in {skProc, skFunc, skConverter, skMethod}:
|
||||||
s.kind in {skProc, skFunc, skConverter, skMethod}:
|
|
||||||
var res = s.ast[resultPos].sym # get result symbol
|
var res = s.ast[resultPos].sym # get result symbol
|
||||||
if res.id notin t.init:
|
if res.id notin t.init:
|
||||||
message(g.config, body.info, warnProveInit, "result")
|
localError(g.config, body.info, errProveInit, "result")
|
||||||
let p = s.ast[pragmasPos]
|
let p = s.ast[pragmasPos]
|
||||||
let raisesSpec = effectSpec(p, wRaises)
|
let raisesSpec = effectSpec(p, wRaises)
|
||||||
if not isNil(raisesSpec):
|
if not isNil(raisesSpec):
|
||||||
|
|
|
||||||
|
|
@ -335,8 +335,7 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
|
||||||
suggestSym(c.config, info, result, c.graph.usageSym)
|
suggestSym(c.config, info, result, c.graph.usageSym)
|
||||||
|
|
||||||
proc checkNilable(c: PContext; v: PSym) =
|
proc checkNilable(c: PContext; v: PSym) =
|
||||||
if {sfGlobal, sfImportc} * v.flags == {sfGlobal} and
|
if {sfGlobal, sfImportc} * v.flags == {sfGlobal} and v.typ.requiresInit:
|
||||||
{tfNotNil, tfRequiresInit} * v.typ.flags != {}:
|
|
||||||
if v.astdef.isNil:
|
if v.astdef.isNil:
|
||||||
message(c.config, v.info, warnProveInit, v.name.s)
|
message(c.config, v.info, warnProveInit, v.name.s)
|
||||||
elif tfNotNil in v.typ.flags and not v.astdef.typ.isNil and tfNotNil notin v.astdef.typ.flags:
|
elif tfNotNil in v.typ.flags and not v.astdef.typ.isNil and tfNotNil notin v.astdef.typ.flags:
|
||||||
|
|
@ -485,7 +484,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||||
var a = n[i]
|
var a = n[i]
|
||||||
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
||||||
if a.kind == nkCommentStmt: continue
|
if a.kind == nkCommentStmt: continue
|
||||||
if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
|
if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config)
|
||||||
checkMinSonsLen(a, 3, c.config)
|
checkMinSonsLen(a, 3, c.config)
|
||||||
|
|
||||||
var typ: PType = nil
|
var typ: PType = nil
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,8 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
|
||||||
if isPure and (let conflict = strTableInclReportConflict(symbols, e); conflict != nil):
|
if isPure and (let conflict = strTableInclReportConflict(symbols, e); conflict != nil):
|
||||||
wrongRedefinition(c, e.info, e.name.s, conflict.info)
|
wrongRedefinition(c, e.info, e.name.s, conflict.info)
|
||||||
inc(counter)
|
inc(counter)
|
||||||
if tfNotNil in e.typ.flags and not hasNull: incl(result.flags, tfRequiresInit)
|
if tfNotNil in e.typ.flags and not hasNull:
|
||||||
|
result.flags.incl tfRequiresInit
|
||||||
|
|
||||||
proc semSet(c: PContext, n: PNode, prev: PType): PType =
|
proc semSet(c: PContext, n: PNode, prev: PType): PType =
|
||||||
result = newOrPrevType(tySet, prev, c)
|
result = newOrPrevType(tySet, prev, c)
|
||||||
|
|
@ -769,6 +770,8 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
|
||||||
f.typ = typ
|
f.typ = typ
|
||||||
f.position = pos
|
f.position = pos
|
||||||
f.options = c.config.options
|
f.options = c.config.options
|
||||||
|
if sfRequiresInit in f.flags:
|
||||||
|
rectype.flags.incl tfHasRequiresInit
|
||||||
if fieldOwner != nil and
|
if fieldOwner != nil and
|
||||||
{sfImportc, sfExportc} * fieldOwner.flags != {} and
|
{sfImportc, sfExportc} * fieldOwner.flags != {} and
|
||||||
not hasCaseFields and f.loc.r == nil:
|
not hasCaseFields and f.loc.r == nil:
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,14 @@ type
|
||||||
a {.requiresInit.}: int
|
a {.requiresInit.}: int
|
||||||
b: string
|
b: string
|
||||||
|
|
||||||
|
PartialRequiresInitRef = ref PartialRequiresInit
|
||||||
|
|
||||||
FullRequiresInit {.requiresInit.} = object
|
FullRequiresInit {.requiresInit.} = object
|
||||||
a: int
|
a: int
|
||||||
b: int
|
b: int
|
||||||
|
|
||||||
|
FullRequiresInitRef = ref FullRequiresInit
|
||||||
|
|
||||||
FullRequiresInitWithParent {.requiresInit.} = object of THasNotNils
|
FullRequiresInitWithParent {.requiresInit.} = object of THasNotNils
|
||||||
e: int
|
e: int
|
||||||
d: int
|
d: int
|
||||||
|
|
@ -72,8 +76,13 @@ var nilRef: TRefObj
|
||||||
var notNilRef = TRefObj(x: 20)
|
var notNilRef = TRefObj(x: 20)
|
||||||
|
|
||||||
proc makeHasNotNils: ref THasNotNils =
|
proc makeHasNotNils: ref THasNotNils =
|
||||||
result.a = TRefObj(x: 10)
|
(ref THasNotNils)(a: TRefObj(x: 10),
|
||||||
result.b = TRefObj(x: 20)
|
b: TRefObj(x: 20))
|
||||||
|
|
||||||
|
proc userDefinedDefault(T: typedesc): T =
|
||||||
|
# We'll use that to make sure the user cannot cheat
|
||||||
|
# with constructing requiresInit types
|
||||||
|
discard
|
||||||
|
|
||||||
accept TObj()
|
accept TObj()
|
||||||
accept TObj(choice: A)
|
accept TObj(choice: A)
|
||||||
|
|
@ -125,7 +134,17 @@ 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()
|
||||||
|
accept PartialRequiresInitRef(a: 10, b: "x")
|
||||||
|
accept PartialRequiresInitRef(a: 20)
|
||||||
|
reject PartialRequiresInitRef(b: "x")
|
||||||
|
reject PartialRequiresInitRef()
|
||||||
|
accept((ref PartialRequiresInit)(a: 10, b: "x"))
|
||||||
|
accept((ref PartialRequiresInit)(a: 20))
|
||||||
|
reject((ref PartialRequiresInit)(b: "x"))
|
||||||
|
reject((ref PartialRequiresInit)())
|
||||||
|
|
||||||
reject default(PartialRequiresInit)
|
reject default(PartialRequiresInit)
|
||||||
|
reject userDefinedDefault(PartialRequiresInit)
|
||||||
reject:
|
reject:
|
||||||
var obj: PartialRequiresInit
|
var obj: PartialRequiresInit
|
||||||
|
|
||||||
|
|
@ -133,7 +152,17 @@ accept FullRequiresInit(a: 10, b: 20)
|
||||||
reject FullRequiresInit(a: 10)
|
reject FullRequiresInit(a: 10)
|
||||||
reject FullRequiresInit(b: 20)
|
reject FullRequiresInit(b: 20)
|
||||||
reject FullRequiresInit()
|
reject FullRequiresInit()
|
||||||
|
accept FullRequiresInitRef(a: 10, b: 20)
|
||||||
|
reject FullRequiresInitRef(a: 10)
|
||||||
|
reject FullRequiresInitRef(b: 20)
|
||||||
|
reject FullRequiresInitRef()
|
||||||
|
accept((ref FullRequiresInit)(a: 10, b: 20))
|
||||||
|
reject((ref FullRequiresInit)(a: 10))
|
||||||
|
reject((ref FullRequiresInit)(b: 20))
|
||||||
|
reject((ref FullRequiresInit)())
|
||||||
|
|
||||||
reject default(FullRequiresInit)
|
reject default(FullRequiresInit)
|
||||||
|
reject userDefinedDefault(FullRequiresInit)
|
||||||
reject:
|
reject:
|
||||||
var obj: FullRequiresInit
|
var obj: FullRequiresInit
|
||||||
|
|
||||||
|
|
@ -144,6 +173,7 @@ reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, e: 10, d: 20) #
|
||||||
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 default(FullRequiresInitWithParent)
|
reject default(FullRequiresInitWithParent)
|
||||||
|
reject userDefinedDefault(FullRequiresInitWithParent)
|
||||||
reject:
|
reject:
|
||||||
var obj: FullRequiresInitWithParent
|
var obj: FullRequiresInitWithParent
|
||||||
|
|
||||||
|
|
@ -153,6 +183,55 @@ accept default(TNestedChoices)
|
||||||
accept:
|
accept:
|
||||||
var obj: TNestedChoices
|
var obj: TNestedChoices
|
||||||
|
|
||||||
|
reject:
|
||||||
|
# This proc is illegal, because it tries to produce
|
||||||
|
# a default object of a type that requires initialization:
|
||||||
|
proc defaultHasNotNils: THasNotNils =
|
||||||
|
discard
|
||||||
|
|
||||||
|
reject:
|
||||||
|
# You cannot cheat by using the result variable to specify
|
||||||
|
# only some of the fields
|
||||||
|
proc invalidPartialTHasNotNils: THasNotNils =
|
||||||
|
result.c = nilRef
|
||||||
|
|
||||||
|
reject:
|
||||||
|
# The same applies for requiresInit types
|
||||||
|
proc invalidPartialRequiersInit: PartialRequiresInit =
|
||||||
|
result.b = "x"
|
||||||
|
|
||||||
|
# All code paths must return a value when the result requires initialization:
|
||||||
|
reject:
|
||||||
|
proc ifWithoutAnElse: THasNotNils =
|
||||||
|
if stdin.readLine == "":
|
||||||
|
return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
|
||||||
|
|
||||||
|
accept:
|
||||||
|
# All code paths must return a value when the result requires initialization:
|
||||||
|
proc wellFormedIf: THasNotNils =
|
||||||
|
if stdin.readLine == "":
|
||||||
|
return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
|
||||||
|
else:
|
||||||
|
return THasNotNIls(a: notNilRef, b: notNilRef)
|
||||||
|
|
||||||
|
reject:
|
||||||
|
proc caseWithoutAllCasesCovered: FullRequiresInit =
|
||||||
|
# Please note that these is no else branch here:
|
||||||
|
case stdin.readLine
|
||||||
|
of "x":
|
||||||
|
return FullRequiresInit(a: 10, b: 20)
|
||||||
|
of "y":
|
||||||
|
return FullRequiresInit(a: 30, b: 40)
|
||||||
|
|
||||||
|
accept:
|
||||||
|
proc wellFormedCase: FullRequiresInit =
|
||||||
|
case stdin.readLine
|
||||||
|
of "x":
|
||||||
|
result = FullRequiresInit(a: 10, b: 20)
|
||||||
|
else:
|
||||||
|
# Mixing result and return is fine:
|
||||||
|
return FullRequiresInit(a: 30, b: 40)
|
||||||
|
|
||||||
# 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
|
||||||
reject TNestedChoices(outerChoice: false, innerChoice: x) # XXX: The error message is not very good here
|
reject TNestedChoices(outerChoice: false, innerChoice: x) # XXX: The error message is not very good here
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue