Skip to content

Commit 0acba32

Browse files
committed
Edit comments
1 parent 7dc2e76 commit 0acba32

File tree

2 files changed

+66
-49
lines changed

2 files changed

+66
-49
lines changed

compiler/src/dotty/tools/dotc/typer/Nullables.scala

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,10 @@ class Typer extends Namer
358358
def toNotNullTermRef(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match
359359
case ref @ OrNull(tpnn) : TermRef
360360
if pt != AssignProto && // Ensure it is not the lhs of Assign
361-
!ref.usedOutOfOrder &&
362-
ctx.notNullInfos.impliesNotNull(ref) =>
361+
ctx.notNullInfos.impliesNotNull(ref) &&
362+
// If a reference is in the context, it is already trackable at the point we add it.
363+
// Hence, we don't use isTracked in the next line, because checking use out of order is enough.
364+
!ref.usedOutOfOrder =>
363365
tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tpnn))
364366
case _ =>
365367
tree

0 commit comments

Comments
 (0)