@@ -101,51 +101,31 @@ object Nullables with
101101 case _ => None
102102 end TrackedRef
103103
104- /** Is given reference tracked for nullability?
105- * This is the case if the reference is a path to an immutable val, or if it refers
106- * to a local mutable variable where all assignments to the variable are _reachable_
107- * (in the sense of how it is defined in assignmentSpans). We use this function to decide
108- * whether we need to compute the NotNullInfo and add it to the context. This requires the
109- * use of a mutable variable is not out of order.
104+ /** Is the given reference tracked for nullability?
110105 *
111- * When dealing with local mutable variables, there are two questions:
106+ * This is the case if one of the following holds:
107+ * 1) The reference is a path to an immutable `val`.
108+ * 2) The reference is to a mutable variable, in which case all assignments to it must be
109+ * reachable (in the sense of how it is defined in assignmentSpans) _and_ the variable
110+ * must not be used "out of order" (in the sense specified by `usedOutOfOrder`).
112111 *
113- * 1. Whether to track a local mutable variable during flow typing.
114- * We track a local mutable variable iff the variable is not assigned in a closure.
115- * For example, in the following code `x` is assigned to by the closure `y`, so we do not
116- * do flow typing on `x`.
117- * ```scala
118- * var x: String|Null = ???
119- * def y = {
120- * x = null
121- * }
122- * if (x != null) {
123- * // y can be called here, which break the fact
124- * val a: String = x // error: x is captured and mutated by the closure, not trackable
125- * }
126- * ```
112+ * Whether to track a local mutable variable during flow typing?
113+ * We track a local mutable variable iff the variable is not assigned in a closure.
114+ * For example, in the following code `x` is assigned to by the closure `y`, so we do not
115+ * do flow typing on `x`.
127116 *
128- * 2. Whether to generate and use flow typing on a specific _use_ of a local mutable variable.
129- * We only want to do flow typing on a use that belongs to the same method as the definition
130- * of the local variable.
131- * For example, in the following code, even `x` is not assigned to by a closure, but we can only
132- * use flow typing in one of the occurrences (because the other occurrence happens within a nested
133- * closure).
134- * ```scala
135- * var x: String|Null = ???
136- * def y = {
137- * if (x != null) {
138- * // not safe to use the fact (x != null) here
139- * // since y can be executed at the same time as the outer block
140- * val _: String = x
141- * }
142- * }
143- * if (x != null) {
144- * val a: String = x // ok to use the fact here
145- * x = null
146- * }
147- * ```
117+ * ```scala
118+ * var x: String|Null = ???
119+ * def y = {
120+ * x = null
121+ * }
122+ * if (x != null) {
123+ * // y can be called here, which break the fact
124+ * val a: String = x // error: x is captured and mutated by the closure, not trackable
125+ * }
126+ * ```
148127 *
128+ * Check `usedOutOfOrder` to see the explaination and example of "out of order".
149129 * See more examples in `tests/explicit-nulls/neg/var-ref-in-closure.scala`.
150130 */
151131 def isTracked (ref : TermRef )(given Context ) =
@@ -204,22 +184,57 @@ object Nullables with
204184
205185 given refOps : extension (ref : TermRef ) with
206186
207- /* Is the use of a mutable variable out of order */
187+ /** Is the use of a mutable variable out of order
188+ *
189+ * Whether to generate and use flow typing on a specific _use_ of a local mutable variable?
190+ * We only want to do flow typing on a use that belongs to the same method as the definition
191+ * of the local variable.
192+ * For example, in the following code, even `x` is not assigned to by a closure, but we can only
193+ * use flow typing in one of the occurrences (because the other occurrence happens within a nested
194+ * closure).
195+ * ```scala
196+ * var x: String|Null = ???
197+ * def y = {
198+ * if (x != null) {
199+ * // not safe to use the fact (x != null) here
200+ * // since y can be executed at the same time as the outer block
201+ * val _: String = x
202+ * }
203+ * }
204+ * if (x != null) {
205+ * val a: String = x // ok to use the fact here
206+ * x = null
207+ * }
208+ * ```
209+ *
210+ * Another example:
211+ * ```scala
212+ * var x: String|Null = ???
213+ * if (x != null) {
214+ * def f: String = {
215+ * val y: String = x // error: the use of x is out of order
216+ * y
217+ * }
218+ * x = null
219+ * val y: String = f // danger
220+ * }
221+ * ```
222+ */
208223 def usedOutOfOrder (given Context ): Boolean =
209224 val refSym = ref.symbol
210225 val refOwner = refSym.owner
211226
212- def enclosedInClosure (s : Symbol ): Boolean =
227+ @ tailrec def usedWithinClosure (s : Symbol ): Boolean =
213228 s != NoSymbol
214229 && s != refOwner
215230 && (s.isOneOf(Lazy | Method ) // not at the rhs of lazy ValDef or in a method (or lambda)
216231 || s.isClass // not in a class
217232 // TODO: need to check by-name paramter
218- || enclosedInClosure (s.owner))
233+ || usedWithinClosure (s.owner))
219234
220- refSym.is(Mutable )
221- && refSym.owner .isTerm
222- && enclosedInClosure (curCtx.owner)
235+ refSym.is(Mutable ) // if it is immutable, we don't need to check the rest conditions
236+ && refOwner .isTerm
237+ && usedWithinClosure (curCtx.owner)
223238
224239 given treeOps : extension (tree : Tree ) with
225240
0 commit comments