@@ -101,51 +101,31 @@ object Nullables with
101
101
case _ => None
102
102
end TrackedRef
103
103
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?
110
105
*
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`).
112
111
*
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`.
127
116
*
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
+ * ```
148
127
*
128
+ * Check `usedOutOfOrder` to see the explaination and example of "out of order".
149
129
* See more examples in `tests/explicit-nulls/neg/var-ref-in-closure.scala`.
150
130
*/
151
131
def isTracked (ref : TermRef )(given Context ) =
@@ -204,22 +184,57 @@ object Nullables with
204
184
205
185
given refOps : extension (ref : TermRef ) with
206
186
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
+ */
208
223
def usedOutOfOrder (given Context ): Boolean =
209
224
val refSym = ref.symbol
210
225
val refOwner = refSym.owner
211
226
212
- def enclosedInClosure (s : Symbol ): Boolean =
227
+ @ tailrec def usedWithinClosure (s : Symbol ): Boolean =
213
228
s != NoSymbol
214
229
&& s != refOwner
215
230
&& (s.isOneOf(Lazy | Method ) // not at the rhs of lazy ValDef or in a method (or lambda)
216
231
|| s.isClass // not in a class
217
232
// TODO: need to check by-name paramter
218
- || enclosedInClosure (s.owner))
233
+ || usedWithinClosure (s.owner))
219
234
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)
223
238
224
239
given treeOps : extension (tree : Tree ) with
225
240
0 commit comments