Skip to content

Commit 9bee756

Browse files
authored
Improve checking LHS of Assign (#22977)
Fixes #22970 Simple example showing how awkward this is compared to a traversal, where there is more control when descending. Previously, it "ignored" the LHS of Assign to avoid taking `x = y` as ref to `x` instead of assignment to. Now, "mark" the LHS as assignment, so `transformIdent` and `Select` set a "mode" flag, such that `x` is correctly taken as either a ref or assign to `x`. Probably there is more complex syntax to handle as LHS, but I haven't had coffee yet.
1 parent c5d5214 commit 9bee756

File tree

2 files changed

+28
-8
lines changed

2 files changed

+28
-8
lines changed

compiler/src/dotty/tools/dotc/transform/CheckUnused.scala

+20-8
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
5252
tree
5353

5454
override def transformIdent(tree: Ident)(using Context): tree.type =
55+
refInfos.isAssignment = tree.hasAttachment(AssignmentTarget)
5556
if tree.symbol.exists then
5657
// if in an inline expansion, resolve at summonInline (synthetic pos) or in an enclosing call site
5758
val resolving =
@@ -68,10 +69,12 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
6869
resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject)
6970
else if tree.hasType then
7071
resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject)
72+
refInfos.isAssignment = false
7173
tree
7274

7375
// import x.y; y may be rewritten x.y, also import x.z as y
7476
override def transformSelect(tree: Select)(using Context): tree.type =
77+
refInfos.isAssignment = tree.hasAttachment(AssignmentTarget)
7578
val name = tree.removeAttachment(OriginalName).getOrElse(nme.NO_NAME)
7679
inline def isImportable = tree.qualifier.srcPos.isSynthetic
7780
&& tree.qualifier.tpe.match
@@ -92,6 +95,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
9295
resolveUsage(tree.symbol, name, tree.qualifier.tpe)
9396
else if !ignoreTree(tree) then
9497
refUsage(tree.symbol)
98+
refInfos.isAssignment = false
9599
tree
96100

97101
override def transformLiteral(tree: Literal)(using Context): tree.type =
@@ -113,13 +117,10 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
113117
ctx
114118

115119
override def prepareForAssign(tree: Assign)(using Context): Context =
116-
tree.lhs.putAttachment(Ignore, ()) // don't take LHS reference as a read
120+
tree.lhs.putAttachment(AssignmentTarget, ()) // don't take LHS reference as a read
117121
ctx
118122
override def transformAssign(tree: Assign)(using Context): tree.type =
119-
tree.lhs.removeAttachment(Ignore)
120-
val sym = tree.lhs.symbol
121-
if sym.exists then
122-
refInfos.asss.addOne(sym)
123+
tree.lhs.removeAttachment(AssignmentTarget)
123124
tree
124125

125126
override def prepareForMatch(tree: Match)(using Context): Context =
@@ -291,7 +292,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
291292
// if sym is not an enclosing element, record the reference
292293
def refUsage(sym: Symbol)(using Context): Unit =
293294
if !ctx.outersIterator.exists(cur => cur.owner eq sym) then
294-
refInfos.refs.addOne(sym)
295+
refInfos.addRef(sym)
295296

296297
/** Look up a reference in enclosing contexts to determine whether it was introduced by a definition or import.
297298
* The binding of highest precedence must then be correct.
@@ -341,7 +342,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
341342
&& ctxsym.thisType.member(sym.name).hasAltWith(d => d.containsSym(sym) && !name.exists(_ != d.name))
342343

343344
// Avoid spurious NoSymbol and also primary ctors which are never warned about.
344-
// Selections C.this.toString should be already excluded, but backtopped here for eq, etc.
345+
// Selections C.this.toString should be already excluded, but backstopped here for eq, etc.
345346
if !sym.exists || sym.isPrimaryConstructor || sym.isEffectiveRoot || defn.topClasses(sym.owner) then return
346347

347348
// Find the innermost, highest precedence. Contexts have no nesting levels but assume correctness.
@@ -392,7 +393,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
392393
end while
393394
// record usage and possibly an import
394395
if !enclosed then
395-
refInfos.refs.addOne(sym)
396+
refInfos.addRef(sym)
396397
if candidate != NoContext && candidate.isImportContext && importer != null then
397398
refInfos.sels.put(importer, ())
398399
end resolveUsage
@@ -417,6 +418,9 @@ object CheckUnused:
417418
/** Ignore reference. */
418419
val Ignore = Property.StickyKey[Unit]
419420

421+
/** Tree is LHS of Assign. */
422+
val AssignmentTarget = Property.StickyKey[Unit]
423+
420424
class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper")
421425

422426
class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining")
@@ -461,6 +465,14 @@ object CheckUnused:
461465

462466
val inlined = Stack.empty[SrcPos] // enclosing call.srcPos of inlined code (expansions)
463467
var inliners = 0 // depth of inline def (not inlined yet)
468+
469+
// instead of refs.addOne, use addRef to distinguish a read from a write to var
470+
var isAssignment = false
471+
def addRef(sym: Symbol): Unit =
472+
if isAssignment then
473+
asss.addOne(sym)
474+
else
475+
refs.addOne(sym)
464476
end RefInfos
465477

466478
// Names are resolved by definitions and imports, which have four precedence levels:

tests/warn/i15503a.scala

+8
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,11 @@ package ancient:
460460
}
461461
}
462462
end ancient
463+
464+
object `i22970 assign lhs was ignored`:
465+
object X:
466+
var global = 0
467+
object Main:
468+
import X.global // no warn
469+
def main(args: Array[String]): Unit =
470+
global = 1

0 commit comments

Comments
 (0)