diff options
-rw-r--r-- | compiler/ast.nim | 14 | ||||
-rw-r--r-- | compiler/guards.nim | 7 | ||||
-rw-r--r-- | compiler/sempass2.nim | 57 | ||||
-rw-r--r-- | compiler/semstmts.nim | 8 | ||||
-rw-r--r-- | compiler/semtypes.nim | 1 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 31 | ||||
-rw-r--r-- | tests/manyloc/keineschweine/lib/sg_packets.nim | 6 | ||||
-rw-r--r-- | todo.txt | 1 | ||||
-rw-r--r-- | web/news.txt | 1 |
9 files changed, 99 insertions, 27 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index f4b1b84f5..e4f7f6d98 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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 diff --git a/compiler/guards.nim b/compiler/guards.nim index d4cb34f36..aece63b19 100644 --- a/compiler/guards.nim +++ b/compiler/guards.nim @@ -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: diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 50286d399..2c87c3b2c 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -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]) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 75ab9920d..47b591d83 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -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) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 769e2ab7d..7efa207a8 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -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: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index b90e73a64..6fb250179 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -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: diff --git a/tests/manyloc/keineschweine/lib/sg_packets.nim b/tests/manyloc/keineschweine/lib/sg_packets.nim index 30e83c147..625436cb6 100644 --- a/tests/manyloc/keineschweine/lib/sg_packets.nim +++ b/tests/manyloc/keineschweine/lib/sg_packets.nim @@ -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]) diff --git a/todo.txt b/todo.txt index 83af59b87..4bdd004cd 100644 --- a/todo.txt +++ b/todo.txt @@ -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 diff --git a/web/news.txt b/web/news.txt index 98d3298b9..fdbb156a7 100644 --- a/web/news.txt +++ b/web/news.txt @@ -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 |