@@ -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,21 @@ 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
+ && sym.span.exists
112
+ && curCtx.compilationUnit.trackedVarSpans.contains(sym.span.start)
113
+ // .reporting(i"tracked? $sym ${sym.span} = $result")
114
+ }
99
115
100
116
def afterPatternContext (sel : Tree , pat : Tree )(given ctx : Context ) = (sel, pat) match
101
117
case (TrackedRef (ref), Literal (Constant (null ))) => ctx.addNotNullRefs(Set (ref))
@@ -151,7 +167,7 @@ object Nullables with
151
167
* by `tree` yields `true` or `false`. Two empty sets if `tree` is not
152
168
* a condition.
153
169
*/
154
- private def notNullConditional (given Context ): NotNullConditional =
170
+ def notNullConditional (given Context ): NotNullConditional =
155
171
stripBlock(tree).getAttachment(NNConditional ) match
156
172
case Some (cond) if ! curCtx.erasedTypes => cond
157
173
case _ => NotNullConditional .empty
@@ -226,4 +242,52 @@ object Nullables with
226
242
227
243
private val analyzedOps = Set (nme.EQ , nme.NE , nme.eq, nme.ne, nme.ZAND , nme.ZOR , nme.UNARY_! )
228
244
245
+ /** The name offsets of all local mutable variables in the current compilation unit
246
+ * that have only reachable assignments. An assignment is reachable if the
247
+ * path of tree nodes between the block enclosing the variable declaration to
248
+ * the assignment consists only of if-expressions, while-expressions, block-expressions
249
+ * and type-ascriptions. Only reachable assignments are handled correctly in the
250
+ * nullability analysis. Therefore, variables with unreachable assignments can
251
+ * be assumed to be not-null only if their type asserts it.
252
+ */
253
+ def trackedVarSpans (given Context ): Set [Int ] =
254
+ import ast .untpd ._
255
+ object populate extends UntypedTreeTraverser with
256
+
257
+ /** The name offsets of variables that are tracked */
258
+ var tracked : Set [Int ] = Set .empty
259
+ /** The names of candidate variables in scope that might be tracked */
260
+ var candidates : Set [Name ] = Set .empty
261
+ /** An assignment to a variable that's not in reachable makes the variable ineligible for tracking */
262
+ var reachable : Set [Name ] = Set .empty
263
+
264
+ def traverse (tree : Tree )(implicit ctx : Context ) =
265
+ val savedReachable = reachable
266
+ tree match
267
+ case Block (stats, expr) =>
268
+ var shadowed : Set [Name ] = Set .empty
269
+ for case (stat : ValDef ) <- stats if stat.mods.is(Mutable ) do
270
+ if candidates.contains(stat.name) then shadowed += stat.name
271
+ else candidates += stat.name
272
+ reachable += stat.name
273
+ traverseChildren(tree)
274
+ for case (stat : ValDef ) <- stats if stat.mods.is(Mutable ) do
275
+ if candidates.contains(stat.name) then
276
+ tracked += stat.nameSpan.start // candidates that survive until here are tracked
277
+ candidates -= stat.name
278
+ candidates ++= shadowed
279
+ case Assign (Ident (name), rhs) =>
280
+ if ! reachable.contains(name) then candidates -= name // variable cannot be tracked
281
+ traverseChildren(tree)
282
+ case _ : (If | WhileDo | Typed ) =>
283
+ traverseChildren(tree) // assignments to candidate variables are OK here ...
284
+ case _ =>
285
+ reachable = Set .empty // ... but not here
286
+ traverseChildren(tree)
287
+ reachable = savedReachable
288
+
289
+ populate.traverse(curCtx.compilationUnit.untpdTree)
290
+ populate.tracked
291
+ .reporting(i " tracked vars: ${result.toList}%, % " )
292
+ end trackedVarSpans
229
293
end Nullables
0 commit comments