improvements for 'not nil' checking

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

View File

@@ -1175,10 +1175,18 @@ proc newSons(father: PNode, length: int) =
setlen(father.sons, length)
proc propagateToOwner*(owner, elem: PType) =
owner.flags = owner.flags + (elem.flags * {tfNeedsInit, tfHasShared,
tfHasMeta, tfHasGCedMem})
const HaveTheirOwnEmpty = {tySequence, tySet}
owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta,
tfHasGCedMem})
if tfNotNil in elem.flags:
owner.flags.incl tfNeedsInit
if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvokation}:
owner.flags.incl tfNotNil
elif owner.kind notin HaveTheirOwnEmpty:
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:
owner.flags.incl tfHasShared

View File

@@ -532,6 +532,13 @@ proc buildElse(n: PNode): PNode =
result.sons[1] = s
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) =
let branch = n.sons[i]
if branch.kind == nkOfBranch:

View File

@@ -74,7 +74,6 @@ type
bottom: int
owner: PSym
init: seq[int] # list of initialized variables
# coming soon: "guard" tracking for 'let' variables
guards: TModel # nested guards
PEffects = var TEffects
@@ -89,11 +88,19 @@ proc initVar(a: PEffects, n: PNode) =
if x == s.id: return
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) =
let s = n.sym
if isLocalVar(a, s):
if s.id notin a.init:
if tfNeedsInit in s.typ.flags:
if {tfNeedsInit, tfNotNil} * s.typ.flags != {}:
when true:
Message(n.info, warnProveInit, s.name.s)
else:
@@ -298,6 +305,18 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
let tagSpec = effectSpec(pragma, wTags)
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) =
let op = n.typ
if op != nil and op.kind == tyProc and n.kind != nkNilLit:
@@ -315,15 +334,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
else:
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
mergeTags(tracked, effectList.sons[tagEffects], n)
if paramType != nil:
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
notNilCheck(tracked, n, paramType)
proc breaksBlock(n: PNode): bool =
case n.kind
@@ -456,8 +467,7 @@ proc track(tracked: PEffects, n: PNode) =
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize,
mNewSeq, mShallowCopy}:
# may not look like an assignment, but it is:
initVar(tracked, n.sons[1])
# XXX new(objWithNotNil) is not initialized properly!
initVarViaNew(tracked, n.sons[1])
for i in 0 .. <safeLen(n):
track(tracked, n.sons[i])
of nkCheckedFieldExpr:
@@ -471,11 +481,17 @@ proc track(tracked: PEffects, n: PNode) =
initVar(tracked, n.sons[0])
invalidateFacts(tracked.guards, n.sons[0])
track(tracked, n.sons[0])
notNilCheck(tracked, n.sons[1], n.sons[0].typ)
of nkVarSection:
for child in n:
if child.kind == nkIdentDefs and lastSon(child).kind != nkEmpty:
track(tracked, lastSon(child))
for i in 0 .. child.len-3: initVar(tracked, child.sons[i])
let last = lastSon(child)
if child.kind == nkIdentDefs and last.kind != nkEmpty:
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 nkIfStmt, nkIfExpr: trackIf(tracked, n)
of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n.sons[1])
@@ -498,6 +514,15 @@ proc track(tracked: PEffects, n: PNode) =
for i in 0 .. <len(n):
track(tracked, n.sons[i])
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:
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, {})
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 =
var b: PNode
result = copyNode(n)
@@ -390,6 +397,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
else:
v.typ = tup.sons[j]
b.sons[j] = newSymNode(v)
checkNilable(v)
proc semConst(c: PContext, n: PNode): PNode =
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:
result = freshType(result, prev)
result.flags.incl(tfNotNil)
result.flags.incl(tfNeedsInit)
else:
LocalError(n.info, errGenerated, "invalid type")
else:

View File

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

View File

@@ -55,7 +55,8 @@ idPacket(ZoneQuery, 'Q',
tuple[pad: char = '\0'])
type SpawnKind = enum
SpawnItem = 1'i8, SpawnVehicle, SpawnObject
SpawnDummy,
SpawnItem, SpawnVehicle, SpawnObject
forwardPacketT(SpawnKind, int8)
defPacket(ScSpawn, tuple[
kind: SpawnKind; id: uint16; record: uint16; amount: uint16])
@@ -64,7 +65,8 @@ defPacket(ScSpawn, tuple[
type TAssetType* = enum
FZoneCfg = 1'i8, FGraphics, FSound
FDummy,
FZoneCfg, FGraphics, FSound
forwardPacketT(TAssetType, 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'
- 'not nil' checking for globals
- prove array accesses
- special rule for ``[]=``
- ``=`` 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
real uninitialized variables in Nimrod as they are initialized to binary
zero). Activate via ``{.warning[Uninit]:on.}``.
- The compiler now enforces the ``not nil`` constraint.
Language Additions