summary refs log tree commit diff stats
path: root/compiler/nilcheck.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/nilcheck.nim')
-rw-r--r--compiler/nilcheck.nim188
1 files changed, 97 insertions, 91 deletions
diff --git a/compiler/nilcheck.nim b/compiler/nilcheck.nim
index 23f403589..7e0efc34b 100644
--- a/compiler/nilcheck.nim
+++ b/compiler/nilcheck.nim
@@ -7,13 +7,16 @@
 #    distribution, for details about the copyright.
 #
 
-import ast, renderer, intsets, tables, msgs, options, lineinfos, strformat, idents, treetab, hashes
-import sequtils, strutils, std / sets
+import ast, renderer, msgs, options, lineinfos, idents, treetab
+import std/[intsets, tables, sequtils, strutils, sets, strformat, hashes]
+
+when defined(nimPreviewSlimSystem):
+  import std/assertions
 
 # IMPORTANT: notes not up to date, i'll update this comment again
-# 
+#
 # notes:
-# 
+#
 # Env: int => nilability
 # a = b
 #   nilability a <- nilability b
@@ -111,7 +114,7 @@ type
   Symbol = distinct int
 
   ## the index of an expression in the pre-indexed sequence of those
-  ExprIndex = distinct int16 
+  ExprIndex = distinct int16
 
   ## the set index
   SetIndex = distinct int
@@ -131,7 +134,7 @@ type
   ## the context for the checker: an instance for each procedure
   NilCheckerContext = ref object
     # abstractTime: AbstractTime
-    # partitions: Partitions 
+    # partitions: Partitions
     # symbolGraphs: Table[Symbol, ]
     symbolIndices: Table[Symbol, ExprIndex] ## index for each symbol
     expressions: SeqOfDistinct[ExprIndex, PNode] ## a sequence of pre-indexed expressions
@@ -306,6 +309,7 @@ proc symbol(n: PNode): Symbol =
   # echo "symbol ", n, " ", n.kind, " ", result.int
 
 func `$`(map: NilMap): string =
+  result = ""
   var now = map
   var stack: seq[NilMap] = @[]
   while not now.isNil:
@@ -360,7 +364,7 @@ func `$`(a: Symbol): string =
   $(a.int)
 
 template isConstBracket(n: PNode): bool =
-  n.kind == nkBracketExpr and n[1].kind in nkLiterals 
+  n.kind == nkBracketExpr and n[1].kind in nkLiterals
 
 proc index(ctx: NilCheckerContext, n: PNode): ExprIndex =
   # echo "n ", n, " ", n.kind
@@ -373,7 +377,7 @@ proc index(ctx: NilCheckerContext, n: PNode): ExprIndex =
     #echo n.kind
     # internalError(ctx.config, n.info, "expected " & $a & " " & $n & " to have a index")
     return noExprIndex
-    # 
+    #
   #ctx.symbolIndices[symbol(n)]
 
 
@@ -384,7 +388,7 @@ proc aliasSet(ctx: NilCheckerContext, map: NilMap, index: ExprIndex): IntSet =
   result = map.sets[map.setIndices[index]]
 
 
-    
+
 proc store(map: NilMap, ctx: NilCheckerContext, index: ExprIndex, value: Nilability, kind: TransitionKind, info: TLineInfo, node: PNode = nil) =
   if index == noExprIndex:
     return
@@ -413,8 +417,8 @@ proc moveOut(ctx: NilCheckerContext, map: NilMap, target: PNode) =
   if targetSetIndex != noSetIndex:
     var targetSet = map.sets[targetSetIndex]
     if targetSet.len > 1:
-      var other: ExprIndex
-    
+      var other: ExprIndex = default(ExprIndex)
+
       for element in targetSet:
         if element.ExprIndex != targetIndex:
           other = element.ExprIndex
@@ -440,7 +444,7 @@ proc move(ctx: NilCheckerContext, map: NilMap, target: PNode, assigned: PNode) =
   #echo "move ", target, " ", assigned
   var targetIndex = ctx.index(target)
   var assignedIndex: ExprIndex
-  var targetSetIndex = map.setIndices[targetIndex] 
+  var targetSetIndex = map.setIndices[targetIndex]
   var assignedSetIndex: SetIndex
   if assigned.kind == nkSym:
     assignedIndex = ctx.index(assigned)
@@ -494,16 +498,16 @@ proc checkCall(n, ctx, map): Check =
   # check args and handle possible mutations
 
   var isNew = false
-  result.map = map
+  result = Check(map: map)
   for i, child in n:
     discard check(child, ctx, map)
-    
+
     if i > 0:
       # var args make a new map with MaybeNil for our node
       # as it might have been mutated
       # TODO similar for normal refs and fields: find dependent exprs: brackets
-      
-      if child.kind == nkHiddenAddr and not child.typ.isNil and child.typ.kind == tyVar and child.typ[0].kind == tyRef:
+
+      if child.kind == nkHiddenAddr and not child.typ.isNil and child.typ.kind == tyVar and child.typ.elementType.kind == tyRef:
         if not isNew:
           result.map = newNilMap(map)
           isNew = true
@@ -526,7 +530,7 @@ proc checkCall(n, ctx, map): Check =
               isNew = true
             moveOutDependants(ctx, result.map, child)
             storeDependants(ctx, result.map, child, MaybeNil)
-        
+
   if n[0].kind == nkSym and n[0].sym.magic == mNew:
     # new hidden deref?
     var value = if n[1].kind == nkHiddenDeref: n[1][0] else: n[1]
@@ -552,13 +556,13 @@ template event(b: History): string =
   of TSafe: "it is safe here as it returns false for isNil"
   of TPotentialAlias: "it might be changed directly or through an alias"
   of TDependant: "it might be changed because its base might be changed"
-  
+
 proc derefWarning(n, ctx, map; kind: Nilability) =
   ## a warning for potentially unsafe dereference
   if n.info in ctx.warningLocations:
     return
   ctx.warningLocations.incl(n.info)
-  var a: seq[History]
+  var a: seq[History] = @[]
   if n.kind == nkSym:
     a = history(map, ctx.index(n))
   var res = ""
@@ -587,14 +591,14 @@ proc handleNilability(check: Check; n, ctx, map) =
   else:
     when defined(nilDebugInfo):
       message(ctx.config, n.info, hintUser, "can deref " & $n)
-    
+
 proc checkDeref(n, ctx, map): Check =
   ## check dereference: deref n should be ok only if n is Safe
   result = check(n[0], ctx, map)
-  
+
   handleNilability(result, n[0], ctx, map)
 
-    
+
 proc checkRefExpr(n, ctx; check: Check): Check =
   ## check ref expressions: TODO not sure when this happens
   result = check
@@ -625,7 +629,7 @@ proc checkBracketExpr(n, ctx, map): Check =
   result = check(n[1], ctx, result.map)
   result = checkRefExpr(n, ctx, result)
   # echo n, " ", result.nilability
-  
+
 
 template union(l: Nilability, r: Nilability): Nilability =
   ## unify two states
@@ -654,7 +658,7 @@ proc findCommonParent(l: NilMap, r: NilMap): NilMap =
   result = l.parent
   while not result.isNil:
     var rparent = r.parent
-    while not rparent.isNil:    
+    while not rparent.isNil:
       if result == rparent:
         return result
       rparent = rparent.parent
@@ -666,17 +670,17 @@ proc union(ctx: NilCheckerContext, l: NilMap, r: NilMap): NilMap =
   ## what if they are from different parts of the same tree
   ## e.g.
   ## a -> b -> c
-  ##   -> b1 
+  ##   -> b1
   ## common then?
-  ## 
+  ##
   if l.isNil:
     return r
   elif r.isNil:
     return l
-  
+
   let common = findCommonParent(l, r)
   result = newNilMap(common, ctx.expressions.len.int)
-  
+
   for index, value in l:
     let h = history(r, index)
     let info = if h.len > 0: h[^1].info else: TLineInfo(line: 0) # assert h.len > 0
@@ -715,11 +719,11 @@ proc checkAsgn(target: PNode, assigned: PNode; ctx, map): Check =
     result = check(assigned, ctx, map)
   else:
     result = Check(nilability: typeNilability(target.typ), map: map)
-  
+
   # we need to visit and check those, but we don't use the result for now
   # is it possible to somehow have another event happen here?
   discard check(target, ctx, map)
-  
+
   if result.map.isNil:
     result.map = map
   if target.kind in {nkSym, nkDotExpr} or isConstBracket(target):
@@ -738,8 +742,8 @@ proc checkAsgn(target: PNode, assigned: PNode; ctx, map): Check =
           if symbol(elementNode) in ctx.symbolIndices:
             var elementIndex = ctx.index(elementNode)
             result.map.store(ctx, elementIndex, value, TAssign, target.info, elementNode)
-      
-    
+
+
 proc checkReturn(n, ctx, map): Check =
   ## check return
   # return n same as result = n; return ?
@@ -749,10 +753,11 @@ proc checkReturn(n, ctx, map): Check =
 
 proc checkIf(n, ctx, map): Check =
   ## check branches based on condition
+  result = default(Check)
   var mapIf: NilMap = map
-  
+
   # first visit the condition
-  
+
   # the structure is not If(Elif(Elif, Else), Else)
   # it is
   # If(Elif, Elif, Else)
@@ -762,10 +767,10 @@ proc checkIf(n, ctx, map): Check =
   # the state of the conditions: negating conditions before the current one
   var layerHistory = newNilMap(mapIf)
   # the state after branch effects
-  var afterLayer: NilMap
+  var afterLayer: NilMap = nil
   # the result nilability for expressions
   var nilability = Safe
-  
+
   for branch in n.sons:
     var branchConditionLayer = newNilMap(layerHistory)
     var branchLayer: NilMap
@@ -779,7 +784,7 @@ proc checkIf(n, ctx, map): Check =
     else:
       branchLayer = layerHistory
       code = branch
-        
+
     let branchCheck = checkBranch(code, ctx, branchLayer)
     # handles nil afterLayer -> returns branchCheck.map
     afterLayer = ctx.union(afterLayer, branchCheck.map)
@@ -796,7 +801,7 @@ proc checkIf(n, ctx, map): Check =
       result.map = ctx.union(layerHistory, afterLayer)
       result.nilability = Safe # no expr?
     else:
-      # similar to else: because otherwise we are jumping out of 
+      # similar to else: because otherwise we are jumping out of
       # the branch, so no union with the mapIf (we dont continue if the condition was true)
       # here it also doesn't matter for the parent branch what happened in the branch, e.g. assigning to nil
       # as if we continue there, we haven't entered the branch probably
@@ -820,8 +825,8 @@ proc checkFor(n, ctx, map): Check =
   # echo namedMapDebugInfo(ctx, map)
   var check2 = check(n.sons[2], ctx, m)
   var map2 = check2.map
-  
-  result.map = ctx.union(map0, m)
+
+  result = Check(map: ctx.union(map0, m))
   result.map = ctx.union(result.map, map2)
   result.nilability = Safe
 
@@ -848,20 +853,21 @@ proc checkWhile(n, ctx, map): Check =
   var map1 = m.copyMap()
   var check2 = check(n.sons[1], ctx, m)
   var map2 = check2.map
-  
-  result.map = ctx.union(map0, map1)
+
+  result = Check(map: ctx.union(map0, map1))
   result.map = ctx.union(result.map, map2)
   result.nilability = Safe
-  
+
 proc checkInfix(n, ctx, map): Check =
   ## check infix operators in condition
   ##   a and b : map is based on a; next b
   ##   a or b : map is an union of a and b's
   ##   a == b : use checkCondition
   ##   else: no change, just check args
+  result = default(Check)
   if n[0].kind == nkSym:
-    var mapL: NilMap
-    var mapR: NilMap
+    var mapL: NilMap = nil
+    var mapR: NilMap = nil
     if n[0].sym.magic notin {mAnd, mEqRef}:
       mapL = checkCondition(n[1], ctx, map, false, false)
       mapR = checkCondition(n[2], ctx, map, false, false)
@@ -882,7 +888,7 @@ proc checkInfix(n, ctx, map): Check =
           result.map = checkCondition(n[2], ctx, map, false, false)
         elif $n[1] == "false":
           result.map = checkCondition(n[2], ctx, map, true, false)
-      
+
       if result.map.isNil:
         result.map = map
     else:
@@ -894,7 +900,7 @@ proc checkInfix(n, ctx, map): Check =
 proc checkIsNil(n, ctx, map; isElse: bool = false): Check =
   ## check isNil calls
   ## update the map depending on if it is not isNil or isNil
-  result.map = newNilMap(map)
+  result = Check(map: newNilMap(map))
   let value = n[1]
   result.map.store(ctx, ctx.index(n[1]), if not isElse: Nil else: Safe, TArg, n.info, n)
 
@@ -906,24 +912,24 @@ proc infix(ctx: NilCheckerContext, l: PNode, r: PNode, magic: TMagic): PNode =
     else: ""
 
   var cache = newIdentCache()
-  var op = newSym(skVar, cache.getIdent(name), nextId ctx.idgen, nil, r.info)
+  var op = newSym(skVar, cache.getIdent(name), ctx.idgen, nil, r.info)
 
   op.magic = magic
   result = nkInfix.newTree(
     newSymNode(op, r.info),
     l,
     r)
-  result.typ = newType(tyBool, nextId ctx.idgen, nil)
+  result.typ = newType(tyBool, ctx.idgen, nil)
 
 proc prefixNot(ctx: NilCheckerContext, node: PNode): PNode =
   var cache = newIdentCache()
-  var op = newSym(skVar, cache.getIdent("not"), nextId ctx.idgen, nil, node.info)
+  var op = newSym(skVar, cache.getIdent("not"), ctx.idgen, nil, node.info)
 
   op.magic = mNot
   result = nkPrefix.newTree(
     newSymNode(op, node.info),
     node)
-  result.typ = newType(tyBool, nextId ctx.idgen, nil)
+  result.typ = newType(tyBool, ctx.idgen, nil)
 
 proc infixEq(ctx: NilCheckerContext, l: PNode, r: PNode): PNode =
   infix(ctx, l, r, mEqRef)
@@ -942,9 +948,9 @@ proc checkCase(n, ctx, map): Check =
   #   c2
   # also a == true is a , a == false is not a
   let base = n[0]
-  result.map = map.copyMap()
+  result = Check(map: map.copyMap())
   result.nilability = Safe
-  var a: PNode
+  var a: PNode = nil
   for child in n:
     case child.kind:
     of nkOfBranch:
@@ -1016,7 +1022,7 @@ proc checkTry(n, ctx, map): Check =
   let tryCheck = check(n[0], ctx, currentMap)
   newMap = ctx.union(currentMap, tryCheck.map)
   canRaise = n[0].canRaise
-  
+
   var afterTryMap = newMap
   for a, branch in n:
     if a > 0:
@@ -1026,7 +1032,7 @@ proc checkTry(n, ctx, map): Check =
         let childCheck = check(branch[0], ctx, newMap)
         newMap = ctx.union(newMap, childCheck.map)
         hasFinally = true
-      of nkExceptBranch:        
+      of nkExceptBranch:
         if canRaise:
           let childCheck = check(branch[^1], ctx, newMap)
           newMap = ctx.union(newMap, childCheck.map)
@@ -1069,7 +1075,7 @@ proc reverse(kind: TransitionKind): TransitionKind =
   of TNil: TSafe
   of TSafe: TNil
   of TPotentialAlias: TPotentialAlias
-  else: 
+  else:
     kind
     # raise newException(ValueError, "expected TNil or TSafe")
 
@@ -1079,41 +1085,41 @@ proc reverseDirect(map: NilMap): NilMap =
   # because conditions should've stored their changes there
   # b: Safe (not b.isNil)
   # b: Parent Parent
-  # b: Nil (b.isNil)  
+  # b: Nil (b.isNil)
 
   # layer block
   # [ Parent ] [ Parent ]
-  #   if -> if state 
+  #   if -> if state
   #   layer -> reverse
   #   older older0 new
   #   older new
   #  [ b Nil ] [ Parent ]
   #  elif
   #  [ b Nil, c Nil] [ Parent ]
-  #  
+  #
 
-  # if b.isNil: 
+  # if b.isNil:
   #   # [ b Safe]
   #   c = A() # Safe
-  # elif not b.isNil: 
+  # elif not b.isNil:
   #   # [ b Safe ] + [b Nil] MaybeNil Unreachable
   #   # Unreachable defer can't deref b, it is unreachable
   #   discard
   # else:
-  #   b 
+  #   b
+
 
-  
-#  if 
+#  if
 
 
 
   # if: we just pass the map with a new layer for its block
   # elif: we just pass the original map but with a new layer is the reverse of the previous popped layer (?)
-  # elif: 
+  # elif:
   # else: we just pass the original map but with a new layer which is initialized as the reverse of the
   #   top layer of else
   # else:
-  #    
+  #
   # [ b MaybeNil ] [b Parent] [b Parent] [b Safe] [b Nil] []
   # Safe
   # c == 1
@@ -1181,7 +1187,7 @@ proc checkResult(n, ctx, map) =
   of Unreachable:
     message(ctx.config, n.info, warnStrictNotNil, "return value is unreachable")
   of Safe, Parent:
-    discard    
+    discard
 
 proc checkBranch(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
   result = check(n, ctx, map)
@@ -1191,7 +1197,7 @@ proc checkBranch(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
 
 proc check(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
   assert not map.isNil
-  
+
   # echo "check n ", n, " ", n.kind
   # echo "map ", namedMapDebugInfo(ctx, map)
   case n.kind:
@@ -1214,12 +1220,12 @@ proc check(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
     result = check(n.sons[1], ctx, map)
   of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange,
      nkBracket, nkCurly, nkPar, nkTupleConstr, nkClosure, nkObjConstr, nkElse:
-    result.map = map
+    result = Check(map: map)
     if n.kind in {nkObjConstr, nkTupleConstr}:
       # TODO deeper nested elements?
       # A(field: B()) #
-      # field: Safe -> 
-      var elements: seq[(PNode, Nilability)]
+      # field: Safe ->
+      var elements: seq[(PNode, Nilability)] = @[]
       for i, child in n:
         result = check(child, ctx, result.map)
         if i > 0:
@@ -1230,7 +1236,7 @@ proc check(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
     else:
       for child in n:
         result = check(child, ctx, result.map)
-    
+
   of nkDotExpr:
     result = checkDotExpr(n, ctx, map)
   of nkDerefExpr, nkHiddenDeref:
@@ -1239,12 +1245,12 @@ proc check(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
     result = check(n.sons[0], ctx, map)
   of nkIfStmt, nkIfExpr:
     result = checkIf(n, ctx, map)
-  of nkAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     result = checkAsgn(n[0], n[1], ctx, map)
-  of nkVarSection:
-    result.map = map
+  of nkVarSection, nkLetSection:
+    result = Check(map: map)
     for child in n:
-      result = checkAsgn(child[0], child[2], ctx, result.map)
+      result = checkAsgn(child[0].skipPragmaExpr, child[2], ctx, result.map)
   of nkForStmt:
     result = checkFor(n, ctx, map)
   of nkCaseStmt:
@@ -1260,32 +1266,32 @@ proc check(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
   of nkNone..pred(nkSym), succ(nkSym)..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef,
       nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
       nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
-      nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr:
-    
+      nkExportStmt, nkPragma, nkCommentStmt, nkBreakState,
+      nkTypeOfExpr, nkMixinStmt, nkBindStmt:
+
     discard "don't follow this : same as varpartitions"
     result = Check(nilability: Nil, map: map)
   else:
 
     var elementMap = map.copyMap()
-    var elementCheck: Check
-    elementCheck.map = elementMap
+    var elementCheck = Check(map: elementMap)
     for element in n:
       elementCheck = check(element, ctx, elementCheck.map)
 
     result = Check(nilability: Nil, map: elementCheck.map)
 
 
-  
-  
+
+
 proc typeNilability(typ: PType): Nilability =
   assert not typ.isNil
   # echo "typeNilability ", $typ.flags, " ", $typ.kind
   result = if tfNotNil in typ.flags:
     Safe
-  elif typ.kind in {tyRef, tyCString, tyPtr, tyPointer}:
-    # 
+  elif typ.kind in {tyRef, tyCstring, tyPtr, tyPointer}:
+    #
     # tyVar ? tyVarargs ? tySink ? tyLent ?
-    # TODO spec? tests? 
+    # TODO spec? tests?
     MaybeNil
   else:
     Safe
@@ -1329,7 +1335,7 @@ proc preVisit(ctx: NilCheckerContext, s: PSym, body: PNode, conf: ConfigRef) =
   ctx.symbolIndices = {resultId: resultExprIndex}.toTable()
   var cache = newIdentCache()
   ctx.expressions = SeqOfDistinct[ExprIndex, PNode](@[newIdentNode(cache.getIdent("result"), s.ast.info)])
-  var emptySet: IntSet # set[ExprIndex]
+  var emptySet: IntSet = initIntSet() # set[ExprIndex]
   ctx.dependants = SeqOfDistinct[ExprIndex, IntSet](@[emptySet])
   for i, arg in s.typ.n.sons:
     if i > 0:
@@ -1354,15 +1360,15 @@ proc checkNil*(s: PSym; body: PNode; conf: ConfigRef, idgen: IdGenerator) =
   var context = NilCheckerContext(config: conf, idgen: idgen)
   context.preVisit(s, body, conf)
   var map = newNilMap(nil, context.symbolIndices.len)
-  
+
   for i, child in s.typ.n.sons:
     if i > 0:
       if child.kind != nkSym:
         continue
       map.store(context, context.index(child), typeNilability(child.typ), TArg, child.info, child)
 
-  map.store(context, resultExprIndex, if not s.typ[0].isNil and s.typ[0].kind == tyRef: Nil else: Safe, TResult, s.ast.info)
-    
+  map.store(context, resultExprIndex, if not s.typ.returnType.isNil and s.typ.returnType.kind == tyRef: Nil else: Safe, TResult, s.ast.info)
+
   # echo "checking ", s.name.s, " ", filename
 
   let res = check(body, context, map)
@@ -1374,8 +1380,8 @@ proc checkNil*(s: PSym; body: PNode; conf: ConfigRef, idgen: IdGenerator) =
       res.map.store(context, resultExprIndex, Safe, TAssign, s.ast.info)
 
   # TODO check for nilability result
-  # (ANotNil, BNotNil) : 
+  # (ANotNil, BNotNil) :
   # do we check on asgn nilability at all?
 
-  if not s.typ[0].isNil and s.typ[0].kind == tyRef and tfNotNil in s.typ[0].flags:
+  if not s.typ.returnType.isNil and s.typ.returnType.kind == tyRef and tfNotNil in s.typ.returnType.flags:
     checkResult(s.ast, context, res.map)