@@ -7,6 +7,9 @@ import Types._, Contexts._, Symbols._, Decorators._, Constants._
77import annotation .tailrec
88import StdNames .nme
99import util .Property
10+ import Names .Name
11+ import util .Spans .Span
12+ import Flags .Mutable
1013
1114/** Operations for implementing a flow analysis for nullability */
1215object Nullables with
@@ -94,8 +97,21 @@ object Nullables with
9497 case _ => None
9598 end TrackedRef
9699
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+ }
99115
100116 def afterPatternContext (sel : Tree , pat : Tree )(given ctx : Context ) = (sel, pat) match
101117 case (TrackedRef (ref), Literal (Constant (null ))) => ctx.addNotNullRefs(Set (ref))
@@ -151,7 +167,7 @@ object Nullables with
151167 * by `tree` yields `true` or `false`. Two empty sets if `tree` is not
152168 * a condition.
153169 */
154- private def notNullConditional (given Context ): NotNullConditional =
170+ def notNullConditional (given Context ): NotNullConditional =
155171 stripBlock(tree).getAttachment(NNConditional ) match
156172 case Some (cond) if ! curCtx.erasedTypes => cond
157173 case _ => NotNullConditional .empty
@@ -226,4 +242,52 @@ object Nullables with
226242
227243 private val analyzedOps = Set (nme.EQ , nme.NE , nme.eq, nme.ne, nme.ZAND , nme.ZOR , nme.UNARY_! )
228244
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
229293end Nullables
0 commit comments