@@ -7,6 +7,9 @@ import Types._, Contexts._, Symbols._, Decorators._, Constants._
7
7
import annotation .tailrec
8
8
import StdNames .nme
9
9
import util .Property
10
+ import Names .Name
11
+ import util .Spans .Span
12
+ import Flags .Mutable
10
13
11
14
/** Operations for implementing a flow analysis for nullability */
12
15
object Nullables with
@@ -94,8 +97,20 @@ object Nullables with
94
97
case _ => None
95
98
end TrackedRef
96
99
97
- /** Is given reference tracked for nullability? */
98
- def isTracked (ref : TermRef )(given Context ) = ref.isStable
100
+ /** Is given reference tracked for nullability?
101
+ * This is the case if the reference is a path to an immutable val,
102
+ * or if it refers to a local mutable variable where all assignments
103
+ * to the variable are reachable.
104
+ */
105
+ def isTracked (ref : TermRef )(given Context ) =
106
+ ref.isStable
107
+ || { val sym = ref.symbol
108
+ sym.is(Mutable )
109
+ && sym.owner.isTerm
110
+ && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod
111
+ && curCtx.compilationUnit.trackedVarSpans.contains(sym.span.start)
112
+ // .reporting(i"tracked? $sym ${sym.span} = $result")
113
+ }
99
114
100
115
def afterPatternContext (sel : Tree , pat : Tree )(given ctx : Context ) = (sel, pat) match
101
116
case (TrackedRef (ref), Literal (Constant (null ))) => ctx.addNotNullRefs(Set (ref))
@@ -151,7 +166,7 @@ object Nullables with
151
166
* by `tree` yields `true` or `false`. Two empty sets if `tree` is not
152
167
* a condition.
153
168
*/
154
- private def notNullConditional (given Context ): NotNullConditional =
169
+ def notNullConditional (given Context ): NotNullConditional =
155
170
stripBlock(tree).getAttachment(NNConditional ) match
156
171
case Some (cond) if ! curCtx.erasedTypes => cond
157
172
case _ => NotNullConditional .empty
@@ -226,4 +241,52 @@ object Nullables with
226
241
227
242
private val analyzedOps = Set (nme.EQ , nme.NE , nme.eq, nme.ne, nme.ZAND , nme.ZOR , nme.UNARY_! )
228
243
244
+ /** The spans of all local mutable variables in the current compilation unit
245
+ * that have only reachable assignments. An assignment is reachable if the
246
+ * path of tree nodes between the block enclosing the variable declaration to
247
+ * the assignment consists only of if-expressions, while-expressions, block-expressions
248
+ * and type-ascriptions. Only reachable assignments are handled correctly in the
249
+ * nullability analysis. Therefore, variables with unreachable assignments can
250
+ * be assumed to be not-null only if their type asserts it.
251
+ */
252
+ def trackedVarSpans (given Context ): Set [Int ] =
253
+ import ast .untpd ._
254
+ object populate extends UntypedTreeTraverser with
255
+
256
+ /** The name spans of variables that are tracked */
257
+ var tracked : Set [Int ] = Set .empty
258
+ /** The names of candidate variables in scope that might be tracked */
259
+ var candidates : Set [Name ] = Set .empty
260
+ /** An assignment to a variable that's not in reachable makes the variable ineligible for tracking */
261
+ var reachable : Set [Name ] = Set .empty
262
+
263
+ def traverse (tree : Tree )(implicit ctx : Context ) =
264
+ val savedReachable = reachable
265
+ tree match
266
+ case Block (stats, expr) =>
267
+ var shadowed : Set [Name ] = Set .empty
268
+ for case (stat : ValDef ) <- stats if stat.mods.is(Mutable ) do
269
+ if candidates.contains(stat.name) then shadowed += stat.name
270
+ else candidates += stat.name
271
+ reachable += stat.name
272
+ traverseChildren(tree)
273
+ for case (stat : ValDef ) <- stats if stat.mods.is(Mutable ) do
274
+ if candidates.contains(stat.name) then
275
+ tracked += stat.nameSpan.start // candidates that survive until here are tracked
276
+ candidates -= stat.name
277
+ candidates ++= shadowed
278
+ case Assign (Ident (name), rhs) =>
279
+ if ! reachable.contains(name) then candidates -= name // variable cannot be tracked
280
+ traverseChildren(tree)
281
+ case _ : (If | WhileDo | Typed ) =>
282
+ traverseChildren(tree) // assignments to candidate variables are OK here ...
283
+ case _ =>
284
+ reachable = Set .empty // ... but not here
285
+ traverseChildren(tree)
286
+ reachable = savedReachable
287
+
288
+ populate.traverse(curCtx.compilationUnit.untpdTree)
289
+ populate.tracked
290
+ .reporting(i " tracked vars: ${result.toList}%, % " )
291
+ end trackedVarSpans
229
292
end Nullables
0 commit comments