improvements for 'not nil' checking

This commit is contained in:
Araq 2013-06-13 01:40:11 +02:00
commit d4c91bbd85
9 changed files with 99 additions and 27 deletions

View file

@ -1175,11 +1175,19 @@ proc newSons(father: PNode, length: int) =
setlen(father.sons, length) setlen(father.sons, length)
proc propagateToOwner*(owner, elem: PType) = proc propagateToOwner*(owner, elem: PType) =
owner.flags = owner.flags + (elem.flags * {tfNeedsInit, tfHasShared, const HaveTheirOwnEmpty = {tySequence, tySet}
tfHasMeta, tfHasGCedMem}) owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta,
tfHasGCedMem})
if tfNotNil in elem.flags: if tfNotNil in elem.flags:
if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvokation}:
owner.flags.incl tfNotNil
elif owner.kind notin HaveTheirOwnEmpty:
owner.flags.incl tfNeedsInit owner.flags.incl tfNeedsInit
if tfNeedsInit in elem.flags:
if owner.kind in HaveTheirOwnEmpty: nil
else: owner.flags.incl tfNeedsInit
if tfShared in elem.flags: if tfShared in elem.flags:
owner.flags.incl tfHasShared owner.flags.incl tfHasShared

View file

@ -532,6 +532,13 @@ proc buildElse(n: PNode): PNode =
result.sons[1] = s result.sons[1] = s
result.sons[2] = n.sons[0] result.sons[2] = n.sons[0]
proc addDiscriminantFact*(m: var TModel, n: PNode) =
var fact = newNodeI(nkCall, n.info, 3)
fact.sons[0] = newSymNode(getSysMagic("==", mEqI))
fact.sons[1] = n.sons[0]
fact.sons[2] = n.sons[1]
m.add fact
proc addCaseBranchFacts*(m: var TModel, n: PNode, i: int) = proc addCaseBranchFacts*(m: var TModel, n: PNode, i: int) =
let branch = n.sons[i] let branch = n.sons[i]
if branch.kind == nkOfBranch: if branch.kind == nkOfBranch:

View file

@ -74,7 +74,6 @@ type
bottom: int bottom: int
owner: PSym owner: PSym
init: seq[int] # list of initialized variables init: seq[int] # list of initialized variables
# coming soon: "guard" tracking for 'let' variables
guards: TModel # nested guards guards: TModel # nested guards
PEffects = var TEffects PEffects = var TEffects
@ -89,11 +88,19 @@ proc initVar(a: PEffects, n: PNode) =
if x == s.id: return if x == s.id: return
a.init.add s.id a.init.add s.id
proc initVarViaNew(a: PEffects, n: PNode) =
if n.kind != nkSym: return
let s = n.sym
if {tfNeedsInit, tfNotNil} * s.typ.flags == {tfNotNil}:
# 'x' is not nil, but that doesn't mean it's not nil children
# are initialized:
initVarViaNew(a, n)
proc useVar(a: PEffects, n: PNode) = proc useVar(a: PEffects, n: PNode) =
let s = n.sym let s = n.sym
if isLocalVar(a, s): if isLocalVar(a, s):
if s.id notin a.init: if s.id notin a.init:
if tfNeedsInit in s.typ.flags: if {tfNeedsInit, tfNotNil} * s.typ.flags != {}:
when true: when true:
Message(n.info, warnProveInit, s.name.s) Message(n.info, warnProveInit, s.name.s)
else: else:
@ -298,6 +305,18 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
let tagSpec = effectSpec(pragma, wTags) let tagSpec = effectSpec(pragma, wTags)
mergeTags(tracked, tagSpec, n) mergeTags(tracked, tagSpec, n)
proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
let n = n.skipConv
if paramType != nil and tfNotNil in paramType.flags and
n.typ != nil and tfNotNil notin n.typ.flags:
case impliesNotNil(tracked.guards, n)
of impUnknown:
Message(n.info, errGenerated,
"cannot prove '$1' is not nil" % n.renderTree)
of impNo:
Message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
of impYes: discard
proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
let op = n.typ let op = n.typ
if op != nil and op.kind == tyProc and n.kind != nkNilLit: if op != nil and op.kind == tyProc and n.kind != nkNilLit:
@ -315,15 +334,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
else: else:
mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeEffects(tracked, effectList.sons[exceptionEffects], n)
mergeTags(tracked, effectList.sons[tagEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n)
if paramType != nil: notNilCheck(tracked, n, paramType)
if tfNotNil in paramType.flags and op != nil and tfNotNil notin op.flags:
case impliesNotNil(tracked.guards, n)
of impUnknown:
Message(n.info, errGenerated,
"cannot prove '$1' is not nil" % n.renderTree)
of impNo:
Message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
of impYes: discard
proc breaksBlock(n: PNode): bool = proc breaksBlock(n: PNode): bool =
case n.kind case n.kind
@ -456,8 +467,7 @@ proc track(tracked: PEffects, n: PNode) =
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize,
mNewSeq, mShallowCopy}: mNewSeq, mShallowCopy}:
# may not look like an assignment, but it is: # may not look like an assignment, but it is:
initVar(tracked, n.sons[1]) initVarViaNew(tracked, n.sons[1])
# XXX new(objWithNotNil) is not initialized properly!
for i in 0 .. <safeLen(n): for i in 0 .. <safeLen(n):
track(tracked, n.sons[i]) track(tracked, n.sons[i])
of nkCheckedFieldExpr: of nkCheckedFieldExpr:
@ -471,11 +481,17 @@ proc track(tracked: PEffects, n: PNode) =
initVar(tracked, n.sons[0]) initVar(tracked, n.sons[0])
invalidateFacts(tracked.guards, n.sons[0]) invalidateFacts(tracked.guards, n.sons[0])
track(tracked, n.sons[0]) track(tracked, n.sons[0])
notNilCheck(tracked, n.sons[1], n.sons[0].typ)
of nkVarSection: of nkVarSection:
for child in n: for child in n:
if child.kind == nkIdentDefs and lastSon(child).kind != nkEmpty: let last = lastSon(child)
track(tracked, lastSon(child)) if child.kind == nkIdentDefs and last.kind != nkEmpty:
for i in 0 .. child.len-3: initVar(tracked, child.sons[i]) track(tracked, last)
for i in 0 .. child.len-3:
initVar(tracked, child.sons[i])
notNilCheck(tracked, last, child.sons[i].typ)
# since 'var (a, b): T = ()' is not even allowed, there is always type
# inference for (a, b) and thus no nil checking is necessary.
of nkCaseStmt: trackCase(tracked, n) of nkCaseStmt: trackCase(tracked, n)
of nkIfStmt, nkIfExpr: trackIf(tracked, n) of nkIfStmt, nkIfExpr: trackIf(tracked, n)
of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n.sons[1]) of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n.sons[1])
@ -498,6 +514,15 @@ proc track(tracked: PEffects, n: PNode) =
for i in 0 .. <len(n): for i in 0 .. <len(n):
track(tracked, n.sons[i]) track(tracked, n.sons[i])
setLen(tracked.init, oldState) setLen(tracked.init, oldState)
of nkObjConstr:
track(tracked, n.sons[0])
let oldFacts = tracked.guards.len
for i in 1 .. <len(n):
let x = n.sons[i]
track(tracked, x)
if sfDiscriminant in x.sons[0].sym.flags:
addDiscriminantFact(tracked.guards, x)
setLen(tracked.guards, oldFacts)
else: else:
for i in 0 .. <safeLen(n): track(tracked, n.sons[i]) for i in 0 .. <safeLen(n): track(tracked, n.sons[i])

View file

@ -315,6 +315,13 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
result = semIdentWithPragma(c, kind, n, {}) result = semIdentWithPragma(c, kind, n, {})
suggestSym(n, result) suggestSym(n, result)
proc checkNilable(v: PSym) =
if sfGlobal in v.flags and {tfNotNil, tfNeedsInit} * v.typ.flags != {}:
if v.ast.isNil:
Message(v.info, warnProveInit, v.name.s)
elif tfNotNil in v.typ.flags and tfNotNil notin v.ast.typ.flags:
Message(v.info, warnProveInit, v.name.s)
proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
var b: PNode var b: PNode
result = copyNode(n) result = copyNode(n)
@ -390,6 +397,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
else: else:
v.typ = tup.sons[j] v.typ = tup.sons[j]
b.sons[j] = newSymNode(v) b.sons[j] = newSymNode(v)
checkNilable(v)
proc semConst(c: PContext, n: PNode): PNode = proc semConst(c: PContext, n: PNode): PNode =
result = copyNode(n) result = copyNode(n)

View file

@ -865,7 +865,6 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
if result.kind in NilableTypes and n.sons[2].kind == nkNilLit: if result.kind in NilableTypes and n.sons[2].kind == nkNilLit:
result = freshType(result, prev) result = freshType(result, prev)
result.flags.incl(tfNotNil) result.flags.incl(tfNotNil)
result.flags.incl(tfNeedsInit)
else: else:
LocalError(n.info, errGenerated, "invalid type") LocalError(n.info, errGenerated, "invalid type")
else: else:

View file

@ -48,6 +48,9 @@ type
isFromIntLit, # conversion *from* int literal; proven safe isFromIntLit, # conversion *from* int literal; proven safe
isEqual isEqual
const
isNilConversion = isConvertible # maybe 'isIntConv' fits better?
proc markUsed*(n: PNode, s: PSym) proc markUsed*(n: PNode, s: PSym)
proc initCandidateAux(c: var TCandidate, callee: PType) {.inline.} = proc initCandidateAux(c: var TCandidate, callee: PType) {.inline.} =
@ -471,6 +474,8 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
else: else:
result = typeRel(c, f.sons[0], a.sons[0]) result = typeRel(c, f.sons[0], a.sons[0])
if result < isGeneric: result = isNone if result < isGeneric: result = isNone
elif tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
else: nil else: nil
of tyOrdinal: of tyOrdinal:
@ -506,6 +511,8 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
of tyPtr: of tyPtr:
result = typeRel(c, base(f), base(a)) result = typeRel(c, base(f), base(a))
if result <= isConvertible: result = isNone if result <= isConvertible: result = isNone
elif tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
else: nil else: nil
of tyRef: of tyRef:
@ -513,13 +520,21 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
of tyRef: of tyRef:
result = typeRel(c, base(f), base(a)) result = typeRel(c, base(f), base(a))
if result <= isConvertible: result = isNone if result <= isConvertible: result = isNone
elif tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
else: nil else: nil
of tyProc: of tyProc:
result = procTypeRel(c, f, a) result = procTypeRel(c, f, a)
if result != isNone and tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
of tyPointer: of tyPointer:
case a.kind case a.kind
of tyPointer: result = isEqual of tyPointer:
if tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
else:
result = isEqual
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
of tyProc: of tyProc:
if a.callConv != ccClosure: result = isConvertible if a.callConv != ccClosure: result = isConvertible
@ -527,13 +542,21 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
else: nil else: nil
of tyString: of tyString:
case a.kind case a.kind
of tyString: result = isEqual of tyString:
if tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
else:
result = isEqual
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
else: nil else: nil
of tyCString: of tyCString:
# conversion from string to cstring is automatic: # conversion from string to cstring is automatic:
case a.Kind case a.Kind
of tyCString: result = isEqual of tyCString:
if tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
else:
result = isEqual
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
of tyString: result = isConvertible of tyString: result = isConvertible
of tyPtr: of tyPtr:

View file

@ -55,7 +55,8 @@ idPacket(ZoneQuery, 'Q',
tuple[pad: char = '\0']) tuple[pad: char = '\0'])
type SpawnKind = enum type SpawnKind = enum
SpawnItem = 1'i8, SpawnVehicle, SpawnObject SpawnDummy,
SpawnItem, SpawnVehicle, SpawnObject
forwardPacketT(SpawnKind, int8) forwardPacketT(SpawnKind, int8)
defPacket(ScSpawn, tuple[ defPacket(ScSpawn, tuple[
kind: SpawnKind; id: uint16; record: uint16; amount: uint16]) kind: SpawnKind; id: uint16; record: uint16; amount: uint16])
@ -64,7 +65,8 @@ defPacket(ScSpawn, tuple[
type TAssetType* = enum type TAssetType* = enum
FZoneCfg = 1'i8, FGraphics, FSound FDummy,
FZoneCfg, FGraphics, FSound
forwardPacketT(TAssetType, int8) forwardPacketT(TAssetType, int8)
forwardPacket(MD5Digest, array[0..15, int8]) forwardPacket(MD5Digest, array[0..15, int8])

View file

@ -2,7 +2,6 @@ version 0.9.4
============= =============
- make 'bind' default for templates and introduce 'mixin' - make 'bind' default for templates and introduce 'mixin'
- 'not nil' checking for globals
- prove array accesses - prove array accesses
- special rule for ``[]=`` - special rule for ``[]=``
- ``=`` should be overloadable; requires specialization for ``=``; general - ``=`` should be overloadable; requires specialization for ``=``; general

View file

@ -29,6 +29,7 @@ Compiler Additions
- The compiler can now warn about "uninitialized" variables. (There are no - The compiler can now warn about "uninitialized" variables. (There are no
real uninitialized variables in Nimrod as they are initialized to binary real uninitialized variables in Nimrod as they are initialized to binary
zero). Activate via ``{.warning[Uninit]:on.}``. zero). Activate via ``{.warning[Uninit]:on.}``.
- The compiler now enforces the ``not nil`` constraint.
Language Additions Language Additions