manual_experimental_strictnotnil.rst 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. Strict not nil checking
  2. =========================
  3. .. default-role:: code
  4. .. include:: rstcommon.rst
  5. **Note:** This feature is experimental, you need to enable it with
  6. .. code-block:: nim
  7. {.experimental: "strictNotNil".}
  8. or
  9. .. code-block:: cmd
  10. nim c --experimental:strictNotNil <program>
  11. In the second case it would check builtin and imported modules as well.
  12. It checks the nilability of ref-like types and makes dereferencing safer based on flow typing and `not nil` annotations.
  13. Its implementation is different than the `notnil` one: defined under `strictNotNil`. Keep in mind the difference in option names, be careful with distinguishing them.
  14. We check several kinds of types for nilability:
  15. - ref types
  16. - pointer types
  17. - proc types
  18. - cstrings
  19. nil
  20. -------
  21. The default kind of nilability types is the nilable kind: they can have the value `nil`.
  22. If you have a non-nilable type `T`, you can use `T nil` to get a nilable type for it.
  23. not nil
  24. --------
  25. You can annotate a type where nil isn't a valid value with `not nil`.
  26. .. code-block:: nim
  27. type
  28. NilableObject = ref object
  29. a: int
  30. Object = NilableObject not nil
  31. Proc = (proc (x, y: int))
  32. proc p(x: Object) =
  33. echo x.a # ensured to dereference without an error
  34. # compiler catches this:
  35. p(nil)
  36. # and also this:
  37. var x: NilableObject
  38. if x.isNil:
  39. p(x)
  40. else:
  41. p(x) # ok
  42. If a type can include `nil` as a valid value, dereferencing values of the type
  43. is checked by the compiler: if a value which might be nil is derefenced, this
  44. produces a warning by default, you can turn this into an error using
  45. the compiler options `--warningAsError:strictNotNil`:option:.
  46. If a type is nilable, you should dereference its values only after a `isNil` or equivalent check.
  47. local turn on/off
  48. ---------------------
  49. You can still turn off nil checking on function/module level by using a `{.strictNotNil: off.}` pragma.
  50. Note: test that/TODO for code/manual.
  51. nilability state
  52. -----------------
  53. Currently a nilable value can be `Safe`, `MaybeNil` or `Nil` : we use internally `Parent` and `Unreachable` but this is an implementation detail(a parent layer has the actual nilability).
  54. - `Safe` means it shouldn't be nil at that point: e.g. after assignment to
  55. a non-nil value or `not a.isNil` check
  56. - `MaybeNil` means it might be nil, but it might not be nil: e.g. an argument,
  57. a call argument or a value after an `if` and `else`.
  58. - `Nil` means it should be nil at that point; e.g. after an assignment to
  59. `nil` or a `.isNil` check.
  60. - `Unreachable` means it shouldn't be possible to access this in this branch:
  61. so we do generate a warning as well.
  62. We show an error for each dereference (`[]`, `.field`, `[index]` `()` etc) which is of a tracked expression which is
  63. in `MaybeNil` or `Nil` state.
  64. type nilability
  65. ----------------
  66. Types are either nilable or non-nilable.
  67. When you pass a param or a default value, we use the type : for nilable types we return `MaybeNil`
  68. and for non-nilable `Safe`.
  69. TODO: fix the manual here. (This is not great, as default values for non-nilables and nilables are usually actually `nil` , so we should think a bit more about this section.)
  70. params rules
  71. ------------
  72. Param's nilability is detected based on type nilability. We use the type of the argument to detect the nilability.
  73. assignment rules
  74. -----------------
  75. Let's say we have `left = right`.
  76. When we assign, we pass the right's nilability to the left's expression. There should be special handling of aliasing and compound expressions which we specify in their sections. (Assignment is a possible alias `move` or `move out`).
  77. call args rules
  78. -----------------
  79. When we call with arguments, we have two cases when we might change the nilability.
  80. .. code-block:: nim
  81. callByVar(a)
  82. Here `callByVar` can re-assign `a`, so this might change `a`'s nilability, so we change it to `MaybeNil`.
  83. This is also a possible aliasing `move out` (moving out of a current alias set).
  84. .. code-block:: nim
  85. call(a)
  86. Here `call` can change a field or element of `a`, so if we have a dependant expression of `a` : e.g. `a.field`. Dependats become `MaybeNil`.
  87. branches rules
  88. ---------------
  89. Branches are the reason we do nil checking like this: with flow checking.
  90. Sources of brancing are `if`, `while`, `for`, `and`, `or`, `case`, `try` and combinations with `return`, `break`, `continue` and `raise`
  91. We create a new layer/"scope" for each branch where we map expressions to nilability. This happens when we "fork": usually on the beginning of a construct.
  92. When branches "join" we usually unify their expression maps or/and nilabilities.
  93. Merging usually merges maps and alias sets: nilabilities are merged like this:
  94. .. code-block:: nim
  95. template union(l: Nilability, r: Nilability): Nilability =
  96. ## unify two states
  97. if l == r:
  98. l
  99. else:
  100. MaybeNil
  101. Special handling is for `.isNil` and `== nil`, also for `not`, `and` and `or`.
  102. `not` reverses the nilability, `and` is similar to "forking" : the right expression is checked in the layer resulting from the left one and `or` is similar to "merging": the right and left expression should be both checked in the original layer.
  103. `isNil`, `== nil` make expressions `Nil`. If there is a `not` or `!= nil`, they make them `Safe`.
  104. We also reverse the nilability in the opposite branch: e.g. `else`.
  105. compound expressions: field, index expressions
  106. -----------------------------------------------
  107. We want to track also field(dot) and index(bracket) expressions.
  108. We track some of those compound expressions which might be nilable as dependants of their bases: `a.field` is changed if `a` is moved (re-assigned),
  109. similarly `a[index]` is dependent on `a` and `a.field.field` on `a.field`.
  110. When we move the base, we update dependants to `MaybeNil`. Otherwise we usually start with type nilability.
  111. When we call args, we update the nilability of their dependants to `MaybeNil` as the calls usually can change them.
  112. We might need to check for `strictFuncs` pure funcs and not do that then.
  113. For field expressions `a.field`, we calculate an integer value based on a hash of the tree and just accept equivalent trees as equivalent expressions.
  114. For item expression `a[index]`, we also calculate an integer value based on a hash of the tree and accept equivalent trees as equivalent expressions: for static values only.
  115. For now we support only constant indices: we dont track expression with no-const indices. For those we just report a warning even if they are safe for now: one can use a local variable to workaround. For loops this might be annoying: so one should be able to turn off locally the warning using the `{.warning[StrictCheckNotNil]:off}.`.
  116. For bracket expressions, in the future we might count `a[<any>]` as the same general expression.
  117. This means we should should the index but otherwise handle it the same for assign (maybe "aliasing" all the non-static elements) and differentiate only for static: e.g. `a[0]` and `a[1]`.
  118. element tracking
  119. -----------------
  120. When we assign an object construction, we should track the fields as well:
  121. .. code-block:: nim
  122. var a = Nilable(field: Nilable()) # a : Safe, a.field: Safe
  123. Usually we just track the result of an expression: probably this should apply for elements in other cases as well.
  124. Also related to tracking initialization of expressions/fields.
  125. unstructured control flow rules
  126. -------------------------------
  127. Unstructured control flow keywords as `return`, `break`, `continue`, `raise` mean that we jump from a branch out.
  128. This means that if there is code after the finishing of the branch, it would be ran if one hasn't hit the direct parent branch of those: so it is similar to an `else`. In those cases we should use the reverse nilabilities for the local to the condition expressions. E.g.
  129. .. code-block:: nim
  130. for a in c:
  131. if not a.isNil:
  132. b()
  133. break
  134. code # here a: Nil , because if not, we would have breaked
  135. aliasing
  136. ------------
  137. We support alias detection for local expressions.
  138. We track sets of aliased expressions. We start with all nilable local expressions in separate sets.
  139. Assignments and other changes to nilability can move / move out expressions of sets.
  140. `move`: Moving `left` to `right` means we remove `left` from its current set and unify it with the `right`'s set.
  141. This means it stops being aliased with its previous aliases.
  142. .. code-block:: nim
  143. var left = b
  144. left = right # moving left to right
  145. `move out`: Moving out `left` might remove it from the current set and ensure that it's in its own set as a single element.
  146. e.g.
  147. .. code-block:: nim
  148. var left = b
  149. left = nil # moving out
  150. initialization of non nilable and nilable values
  151. -------------------------------------------------
  152. TODO
  153. warnings and errors
  154. ---------------------
  155. We show an error for each dereference (`[]`, `.field`, `[index]` `()` etc) which is of a tracked expression which is
  156. in `MaybeNil` or `Nil` state.
  157. We might also show a history of the transitions and the reasons for them that might change the nilability of the expression.