summary refs log tree commit diff stats
path: root/tests/strictnotnil
diff options
context:
space:
mode:
Diffstat (limited to 'tests/strictnotnil')
-rw-r--r--tests/strictnotnil/tnilcheck.nim381
-rw-r--r--tests/strictnotnil/tnilcheck_no_warnings.nim182
2 files changed, 563 insertions, 0 deletions
diff --git a/tests/strictnotnil/tnilcheck.nim b/tests/strictnotnil/tnilcheck.nim
new file mode 100644
index 000000000..b8057de74
--- /dev/null
+++ b/tests/strictnotnil/tnilcheck.nim
@@ -0,0 +1,381 @@
+discard """
+action: compile
+"""
+
+import tables
+
+{.experimental: "strictNotNil".}
+
+type
+  Nilable* = ref object
+    a*: int
+    field*: Nilable
+    
+  NonNilable* = Nilable not nil
+
+  Nilable2* = nil NonNilable
+
+
+# proc `[]`(a: Nilable, b: int): Nilable =
+#   nil
+
+
+# Nilable tests
+
+
+
+# test deref
+proc testDeref(a: Nilable) =
+  echo a.a > 0 #[tt.Warning
+       ^ can't deref a, it might be nil
+  ]#
+
+
+
+# # # test if else
+proc testIfElse(a: Nilable) =
+  if a.isNil:
+    echo a.a #[tt.Warning
+         ^ can't deref a, it is nil
+    ]#
+  else:
+    echo a.a # ok
+
+proc testIfNoElse(a: Nilable) =
+  if a.isNil:
+    echo a.a #[tt.Warning
+         ^ can't deref a, it is nil
+         ]#
+  echo a.a #[tt.Warning
+       ^ can't deref a, it might be nil
+   ]#
+
+proc testIfReturn(a: Nilable) =
+  if not a.isNil:
+    return
+  echo a.a #[tt.Warning
+       ^ can't deref a, it is nil
+  ]#
+
+proc testIfBreak(a: seq[Nilable]) =
+  for b in a:
+    if not b.isNil:
+      break
+    echo b.a #[tt.Warning
+         ^ can't deref b, it is nil
+    ]#
+
+proc testIfContinue(a: seq[Nilable]) =
+  for b in a:
+    if not b.isNil:
+      continue
+    echo b.a #[tt.Warning
+         ^ can't deref b, it is nil
+    ]#
+
+proc testIfRaise(a: Nilable) =
+  if not a.isNil:
+    raise newException(ValueError, "")
+  echo a.a #[tt.Warning
+       ^ can't deref a, it is nil
+  ]#
+
+proc testIfElif(a: Nilable) =
+  var c = 0
+  if c == 0:
+    echo a.a #[tt.Warning
+         ^ can't deref a, it might be nil
+      ]#
+  elif c == 1:
+    echo a.a #[tt.Warning
+         ^ can't deref a, it might be nil
+      ]#
+  elif not a.isNil:
+    echo a.a # ok
+  elif c == 2:
+    echo 0
+  else:
+    echo a.a #[tt.Warning
+         ^ can't deref a, it is nil
+    ]#
+
+proc testAssignUnify(a: Nilable, b: int) =
+  var a2 = a
+  if b == 0:
+    a2 = Nilable()
+  echo a2.a #[tt.Warning
+       ^ can't deref a2, it might be nil
+  ]#
+
+
+# # test assign in branch and unifiying that with the main block after end of branch
+proc testAssignUnifyNil(a: Nilable, b: int) =
+  var a2 = a
+  if b == 0:
+    a2 = nil
+  echo a2.a #[tt.Warning
+       ^ can't deref a2, it might be nil
+  ]#
+
+# test loop
+proc testForLoop(a: Nilable) =
+  var b = Nilable()
+  for i in 0 .. 5:
+    echo b.a #[tt.Warning
+         ^ can't deref b, it might be nil
+    ]#
+    if i == 2:
+      b = a
+  echo b.a #[tt.Warning
+       ^ can't deref b, it might be nil
+  ]#
+
+
+
+# # TODO implement this after discussion
+# # proc testResultCompoundNonNilableElement(a: Nilable): (NonNilable, NonNilable) = #[t t.Warning
+# #      ^ result might be not initialized, so it or an element might be nil
+# # ]#
+# #   if not a.isNil:
+# #     result[0] = a #[t t.Warning
+# #                 ^ can't assign nilable to non nilable: it might be nil
+# #     #]
+
+# # proc testNonNilDeref(a: NonNilable) =
+# #   echo a.a # ok
+
+
+
+# # # not only calls: we can use partitions for dependencies for field aliases
+# # # so we can detect on change what does this affect or was this mutated between us and the original field
+
+# # proc testRootAliasField(a: Nilable) =
+# #   var aliasA = a
+# #   if not a.isNil and not a.field.isNil:
+# #     aliasA.field = nil
+# #     # a.field = nil
+# #     # aliasA = nil 
+# #     echo a.field.a # [tt.Warning
+# #          ^ can't deref a.field, it might be nil
+# #     ]#
+
+
+proc testAliasChanging(a: Nilable) =
+  var b = a
+  var aliasA = b
+  b = Nilable()
+  if not b.isNil:
+    echo aliasA.a #[tt.Warning
+         ^ can't deref aliasA, it might be nil
+    ]#
+
+# # TODO
+# # proc testAliasUnion(a: Nilable) =
+# #   var a2 = a
+# #   var b = a2
+# #   if a.isNil:
+# #     b = Nilable()
+# #     a2 = nil
+# #   else:
+# #     a2 = Nilable()
+# #     b = a2
+# #   if not b.isNil:
+# #     echo a2.a #[ tt.Warning
+# #          ^ can't deref a2, it might be nil
+# #     ]#
+
+# # TODO after alias support
+# #proc callVar(a: var Nilable) =
+# #  a.field = nil
+
+
+# # TODO ptr support
+# # proc testPtrAlias(a: Nilable) =
+# #   # pointer to a: hm.
+# #   # alias to a?
+# #   var ptrA = a.addr # {0, 1} 
+# #   if not a.isNil: # {0, 1}
+# #     ptrA[] = nil # {0, 1} 0: MaybeNil 1: MaybeNil
+# #     echo a.a #[ tt.Warning
+# #          ^ can't deref a, it might be nil
+# #     ]#
+
+# # TODO field stuff
+# # currently it just doesnt support dot, so accidentally it shows a warning but because that
+# # not alias i think
+# # proc testFieldAlias(a: Nilable) =
+# #   var b = a # {0, 1} {2} 
+# #   if not a.isNil and not a.field.isNil: # {0, 1} {2}
+# #     callVar(b) # {0, 1} {2} 0: Safe 1: Safe
+# #     echo a.field.a #[ tt.Warning
+# #           ^ can't deref a.field, it might be nil
+# #     ]#
+# #
+# # proc testUniqueHashTree(a: Nilable): Nilable =
+# #   # TODO what would be a clash
+# #   var field = 0
+# #   if not a.isNil and not a.field.isNil:
+# #     # echo a.field.a
+# #     echo a[field].a
+# #   result = Nilable()
+  
+# # proc testSeparateShadowingResult(a: Nilable): Nilable =
+# #   result = Nilable()
+# #   if not a.isNil:
+# #     var result: Nilable = nil
+# #   echo result.a
+
+
+proc testCStringDeref(a: cstring) =
+  echo a[0] #[tt.Warning
+       ^ can't deref a, it might be nil
+  ]#
+
+
+proc testNilablePtr(a: ptr int) =
+  if not a.isNil:
+    echo a[] # ok
+  echo a[] #[tt.Warning
+       ^ can't deref a, it might be nil
+  ]#
+
+# # proc testNonNilPtr(a: ptr int not nil) =
+# #   echo a[] # ok
+
+proc raiseCall: NonNilable = #[tt.Warning
+^ return value is nil
+]#
+  raise newException(ValueError, "raise for test") 
+
+# proc testTryCatch(a: Nilable) =
+#   var other = a
+#   try:
+#     other = raiseCall()
+#   except:
+#     discard
+#   echo other.a #[ tt.Warning
+#             ^ can't deref other, it might be nil
+#   ]#
+
+# # proc testTryCatchDetectNoRaise(a: Nilable) =
+# #   var other = Nilable()
+# #   try:
+# #     other = nil
+# #     other = a
+# #     other = Nilable()
+# #   except:
+# #     other = nil
+# #   echo other.a # ok
+
+# # proc testTryCatchDetectFinally =
+# #   var other = Nilable()
+# #   try:
+# #     other = nil
+# #     other = Nilable()
+# #   except:
+# #     other = Nilable()
+# #   finally:
+# #     other = nil
+# #   echo other.a # can't deref other: it is nil
+
+# # proc testTryCatchDetectNilableWithRaise(b: bool) =
+# #   var other = Nilable()
+# #   try:
+# #     if b:
+# #       other = nil
+# #     else:
+# #       other = Nilable()
+# #       var other2 = raiseCall()
+# #   except:
+# #     echo other.a # ok
+
+# #   echo other.a # can't deref a: it might be nil
+
+# # proc testRaise(a: Nilable) =
+# #   if a.isNil:
+# #     raise newException(ValueError, "a == nil")
+# #   echo a.a # ok
+
+
+# # proc testBlockScope(a: Nilable) =
+# #   var other = a
+# #   block:
+# #     var other = Nilable()
+# #     echo other.a # ok
+# #   echo other.a # can't deref other: it might be nil
+
+# # ok we can't really get the nil value from here, so should be ok
+# # proc testDirectRaiseCall: NonNilable =
+# #   var a = raiseCall()
+# #   result = NonNilable()
+
+# # proc testStmtList =
+# #   var a = Nilable()
+# #   block:
+# #     a = nil
+# #     a = Nilable()
+# #   echo a.a # ok
+
+proc callChange(a: Nilable) =
+  if not a.isNil:
+    a.field = nil
+
+proc testCallChangeField =
+  var a = Nilable()
+  a.field = Nilable()
+  callChange(a)
+  echo a.field.a #[ tt.Warning
+        ^ can't deref a.field, it might be nil
+       ]#
+
+proc testReassignVarWithField =
+  var a = Nilable()
+  a.field = Nilable()
+  echo a.field.a # ok
+  a = Nilable()
+  echo a.field.a #[ tt.Warning
+        ^ can't deref a.field, it might be nil
+        ]#
+
+
+proc testItemDeref(a: var seq[Nilable]) =
+  echo a[0].a #[tt.Warning
+        ^ can't deref a[0], it might be nil
+       ]#
+  a[0] = Nilable() # good: now .. if we dont track, how do we know 
+  echo a[0].a # ok
+  echo a[1].a #[tt.Warning
+        ^ can't deref a[1], it might be nil
+  ]#
+  var b = 1
+  if a[b].isNil:
+    echo a[1].a #[tt.Warning
+          ^ can't deref a[1], it might be nil
+    ]#
+    var c = 0
+    echo a[c].a #[tt.Warning
+          ^ can't deref a[c], it might be nil
+    ]#
+
+  # known false positive
+  if not a[b].isNil:
+    echo a[b].a #[tt.Warning
+          ^ can't deref a[b], it might be nil
+    ]#
+
+  const c = 0
+  if a[c].isNil:
+    echo a[0].a #[tt.Warning
+          ^ can't deref a[0], it is nil
+    ]#
+  a[c] = Nilable()
+  echo a[0].a # ok
+
+
+
+# # # proc test10(a: Nilable) =
+# # #   if not a.isNil and not a.b.isNil:
+# # #     c_memset(globalA.addr, 0, globalA.sizeOf.csize_t)
+# # #     globalA = nil
+# # #     echo a.a # can't deref a: it might be nil
+
diff --git a/tests/strictnotnil/tnilcheck_no_warnings.nim b/tests/strictnotnil/tnilcheck_no_warnings.nim
new file mode 100644
index 000000000..5ec9bc575
--- /dev/null
+++ b/tests/strictnotnil/tnilcheck_no_warnings.nim
@@ -0,0 +1,182 @@
+discard """
+cmd: "nim check --warningAsError:StrictNotNil $file"
+action: "compile"
+"""
+
+import tables
+
+{.experimental: "strictNotNil".}
+
+type
+  Nilable* = ref object
+    a*: int
+    field*: Nilable
+    
+  NonNilable* = Nilable not nil
+
+  Nilable2* = nil NonNilable
+
+
+# proc `[]`(a: Nilable, b: int): Nilable =
+#   nil
+
+
+# Nilable tests
+
+
+
+# # test and
+proc testAnd(a: Nilable) =
+  echo not a.isNil and a.a > 0 # ok
+
+
+# test else branch and inferring not isNil
+# proc testElse(a: Nilable, b: int) =
+#   if a.isNil:
+#     echo 0
+#   else:
+#     echo a.a
+
+# test that here we can infer that n can't be nil anymore
+proc testNotNilAfterAssign(a: Nilable, b: int) =
+  var n = a # a: MaybeNil n: MaybeNil
+  if n.isNil: # n: Safe a: MaybeNil
+    n = Nilable() # n: Safe a: MaybeNil
+  echo n.a # ok
+
+proc callVar(a: var Nilable) =
+   a = nil
+
+proc testVarAlias(a: Nilable) = # a: 0 aliasA: 1 {0} {1} 
+  var aliasA = a # {0, 1} 0 MaybeNil 1 MaybeNil
+  if not a.isNil: # {0, 1} 0 Safe 1 Safe
+    callVar(aliasA) # {0, 1} 0 MaybeNil 1 MaybeNil
+    # if aliasA stops being in alias: it might be nil, but then a is still not nil
+    # if not: it cant be nil as it still points here
+    echo a.a # ok 
+
+proc testAliasCheck(a: Nilable) =
+  var aliasA = a
+  if not a.isNil:
+    echo aliasA.a # ok
+
+proc testFieldCheck(a: Nilable) =
+  if not a.isNil and not a.field.isNil:
+    echo a.field.a # ok
+
+proc testTrackField =
+  var a = Nilable(field: Nilable())
+  echo a.field.a # ok
+
+proc testNonNilDeref(a: NonNilable) =
+  echo a.a # ok
+
+# # not only calls: we can use partitions for dependencies for field aliases
+# # so we can detect on change what does this affect or was this mutated between us and the original field
+
+
+# proc testUniqueHashTree(a: Nilable): Nilable =
+#   # TODO what would be a clash
+#   var field = 0
+#   if not a.isNil and not a.field.isNil:
+#     # echo a.field.a
+#     echo a[field].a
+#   result = Nilable()
+  
+proc testSeparateShadowingResult(a: Nilable): Nilable =
+  result = Nilable()
+  if not a.isNil:
+    var result: Nilable = nil
+  echo result.a
+
+
+proc testNonNilCString(a: cstring not nil) =
+  echo a[0] # ok
+
+proc testNonNilPtr(a: ptr int not nil) =
+  echo a[] # ok
+
+
+# proc testTryCatchDetectNoRaise(a: Nilable) =
+#   var other = Nilable()
+#   try:
+#     other = nil
+#     other = a
+#     other = Nilable()
+#   except:
+#     other = nil
+#   echo other.a # ok
+
+# proc testTryCatchDetectFinally =
+#   var other = Nilable()
+#   try:
+#     other = nil
+#     other = Nilable()
+#   except:
+#     other = Nilable()
+#   finally:
+#     other = nil
+#   echo other.a # can't deref other: it is nil
+
+# proc testTryCatchDetectNilableWithRaise(b: bool) =
+#   var other = Nilable()
+#   try:
+#     if b:
+#       other = nil
+#     else:
+#       other = Nilable()
+#       var other2 = raiseCall()
+#   except:
+#     echo other.a # ok
+
+#   echo other.a # can't deref a: it might be nil
+
+proc testRaise(a: Nilable) =
+  if a.isNil:
+    raise newException(ValueError, "a == nil")
+  echo a.a # ok
+
+# proc testBlockScope(a: Nilable) =
+#   var other = a
+#   block:
+#     var other = Nilable()
+#     echo other.a # ok
+#   echo other.a # can't deref other: it might be nil
+
+# # (ask Araq about this: not supported yet) ok we can't really get the nil value from here, so should be ok
+# proc testDirectRaiseCall: NonNilable =
+#   var a = raiseCall()
+#   result = NonNilable()
+
+proc testStmtList =
+  var a = Nilable()
+  block:
+    a = nil
+    a = Nilable()
+  echo a.a # ok
+
+proc testItemDerefNoWarning(a: var seq[Nilable]) =
+  a[0] = Nilable() # good: now .. if we dont track, how do we know 
+  echo a[0].a # ok
+  var b = 1
+
+  const c = 0
+  a[c] = Nilable()
+  echo a[0].a # ok
+
+# proc callChange(a: Nilable) =
+#   a.field = nil
+
+# proc testCallAlias =
+#   var a = Nilable(field: Nilable())
+#   callChange(a)
+#   echo a.field.a # can't deref a.field, it might be nil
+
+# # proc test10(a: Nilable) =
+# #   if not a.isNil and not a.b.isNil:
+# #     c_memset(globalA.addr, 0, globalA.sizeOf.csize_t)
+# #     globalA = nil
+# #     echo a.a # can't deref a: it might be nil
+
+var nilable: Nilable
+var withField = Nilable(a: 0, field: Nilable())