discard """ cmd: "nim check $file" action: "reject" """ 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.unsafeAddr # {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