Skip to content

Commit 75d4d24

Browse files
committed
Improve unused data usage
1 parent a8ad059 commit 75d4d24

File tree

2 files changed

+84
-72
lines changed

2 files changed

+84
-72
lines changed

Diff for: compiler/src/dotty/tools/dotc/transform/CheckUnused.scala

+68-71
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import dotty.tools.dotc.core.NameOps.isReplWrapperName
2121
import dotty.tools.dotc.core.Annotations
2222
import dotty.tools.dotc.core.Definitions
2323
import dotty.tools.dotc.core.NameKinds.WildcardParamName
24-
import dotty.tools.dotc.core.Symbols.Symbol
24+
import dotty.tools.dotc.core.Symbols.{Symbol, isDeprecated}
2525
import dotty.tools.dotc.report
2626
import dotty.tools.dotc.reporting.{Message, UnusedSymbol as UnusedSymbolMessage}
2727
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
@@ -30,19 +30,21 @@ import dotty.tools.dotc.util.{Property, SrcPos}
3030
import dotty.tools.dotc.util.Spans.Span
3131
import scala.util.chaining.given
3232

33+
import CheckUnused.*
34+
3335
/** A compiler phase that checks for unused imports or definitions.
3436
*
3537
* Every construct that introduces a name must have at least one corresponding reference.
3638
* The analysis is restricted to definitions of limited scope, i.e., private and local definitions.
3739
*/
38-
class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _key: Property.Key[CheckUnused.UnusedData]) extends MiniPhase:
39-
import CheckUnused.*
40+
class CheckUnused private (phaseMode: PhaseMode, suffix: String)(using Key) extends MiniPhase:
41+
4042
import UnusedData.*
4143

4244
private inline def ud(using ud: UnusedData): UnusedData = ud
4345

4446
private inline def go[U](inline op: UnusedData ?=> U)(using ctx: Context): ctx.type =
45-
ctx.property(_key) match
47+
ctx.property(summon[Key]) match
4648
case Some(ud) => op(using ud)
4749
case None =>
4850
ctx
@@ -59,11 +61,12 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
5961
// ========== SETUP ============
6062

6163
override def prepareForUnit(tree: tpd.Tree)(using Context): Context =
64+
val key = summon[Key]
6265
val data = UnusedData()
63-
tree.getAttachment(_key).foreach(oldData =>
66+
tree.getAttachment(key).foreach(oldData =>
6467
data.unusedAggregate = oldData.unusedAggregate
6568
)
66-
ctx.fresh.setProperty(_key, data).tap(_ => tree.putAttachment(_key, data))
69+
ctx.fresh.setProperty(key, data).tap(_ => tree.putAttachment(key, data))
6770

6871
// ========== END + REPORTING ==========
6972

@@ -75,14 +78,11 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
7578
tree
7679

7780
// ========== MiniPhase Prepare ==========
78-
override def prepareForOther(tree: tpd.Tree)(using Context): Context =
79-
// A standard tree traverser covers cases not handled by the Mega/MiniPhase
81+
override def prepareForOther(tree: tpd.Tree)(using Context): Context = go:
8082
traverser.traverse(tree)
81-
ctx
8283

83-
override def prepareForInlined(tree: tpd.Inlined)(using Context): Context =
84+
override def prepareForInlined(tree: tpd.Inlined)(using Context): Context = go:
8485
traverser.traverse(tree.call)
85-
ctx
8686

8787
override def prepareForIdent(tree: tpd.Ident)(using Context): Context = go:
8888
if tree.symbol.exists then
@@ -102,13 +102,13 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
102102
ud.registerUsed(tree.symbol, name, tree.qualifier.tpe, includeForImport = tree.qualifier.span.isSynthetic)
103103

104104
override def prepareForBlock(tree: tpd.Block)(using Context): Context =
105-
pushInBlockTemplatePackageDef(tree)
105+
pushScope(tree)
106106

107107
override def prepareForTemplate(tree: tpd.Template)(using Context): Context =
108-
pushInBlockTemplatePackageDef(tree)
108+
pushScope(tree)
109109

110110
override def prepareForPackageDef(tree: tpd.PackageDef)(using Context): Context =
111-
pushInBlockTemplatePackageDef(tree)
111+
pushScope(tree)
112112

113113
override def prepareForValDef(tree: tpd.ValDef)(using Context): Context = go:
114114
traverseAnnotations(tree.symbol)
@@ -137,9 +137,8 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
137137
traverseAnnotations(tree.symbol)
138138
ud.registerPatVar(tree)
139139

140-
override def prepareForTypeTree(tree: tpd.TypeTree)(using Context): Context =
140+
override def prepareForTypeTree(tree: tpd.TypeTree)(using Context): Context = go:
141141
if !tree.isInstanceOf[tpd.InferredTypeTree] then typeTraverser.traverse(tree.tpe)
142-
ctx
143142

144143
override def prepareForAssign(tree: tpd.Assign)(using Context): Context = go:
145144
val sym = tree.lhs.symbol
@@ -149,47 +148,47 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
149148
// ========== MiniPhase Transform ==========
150149

151150
override def transformBlock(tree: tpd.Block)(using Context): tpd.Tree =
152-
popOutBlockTemplatePackageDef()
151+
popScope(tree)
153152
tree
154153

155154
override def transformTemplate(tree: tpd.Template)(using Context): tpd.Tree =
156-
popOutBlockTemplatePackageDef()
155+
popScope(tree)
157156
tree
158157

159158
override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree =
160-
popOutBlockTemplatePackageDef()
159+
popScope(tree)
161160
tree
162161

163162
override def transformValDef(tree: tpd.ValDef)(using Context): tpd.Tree =
164-
go(ud.removeIgnoredUsage(tree.symbol))
163+
go:
164+
ud.removeIgnoredUsage(tree.symbol)
165165
tree
166166

167167
override def transformDefDef(tree: tpd.DefDef)(using Context): tpd.Tree =
168-
go(ud.removeIgnoredUsage(tree.symbol))
168+
go:
169+
ud.removeIgnoredUsage(tree.symbol)
169170
tree
170171

171172
override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree =
172-
go(ud.removeIgnoredUsage(tree.symbol))
173+
go:
174+
ud.removeIgnoredUsage(tree.symbol)
173175
tree
174176

177+
private def pushScope(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context = go:
178+
ud.pushScope(ScopeType.fromTree(tree))
175179

176-
// ---------- MiniPhase HELPERS -----------
177-
178-
private def pushInBlockTemplatePackageDef(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context = go:
179-
ud.pushScope(UnusedData.ScopeType.fromTree(tree))
180-
181-
private def popOutBlockTemplatePackageDef()(using Context): Context = go:
182-
ud.popScope()
180+
private def popScope(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context = go:
181+
ud.popScope(ScopeType.fromTree(tree))
183182

184183
/**
185184
* This traverse is the **main** component of this phase
186185
*
187186
* It traverse the tree the tree and gather the data in the
188187
* corresponding context property
188+
*
189+
* A standard tree traverser covers cases not handled by the Mega/MiniPhase
189190
*/
190191
private def traverser = new TreeTraverser:
191-
import tpd.*
192-
import UnusedData.ScopeType
193192

194193
// Register every import, definition and usage
195194
override def traverse(tree: tpd.Tree)(using Context): Unit =
@@ -201,17 +200,17 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
201200
case untpd.TypedSplice(tree1) => tree1
202201
}.foreach(traverse(_)(using newCtx))
203202
traverseChildren(tree)(using newCtx)
204-
case ident: Ident =>
203+
case ident: tpd.Ident =>
205204
prepareForIdent(ident)
206205
traverseChildren(tree)(using newCtx)
207-
case sel: Select =>
206+
case sel: tpd.Select =>
208207
prepareForSelect(sel)
209208
traverseChildren(tree)(using newCtx)
210209
case tree: (tpd.Block | tpd.Template | tpd.PackageDef) =>
211210
//! DIFFERS FROM MINIPHASE
212-
pushInBlockTemplatePackageDef(tree)
211+
pushScope(tree)
213212
traverseChildren(tree)(using newCtx)
214-
popOutBlockTemplatePackageDef()
213+
popScope(tree)
215214
case t: tpd.ValDef =>
216215
prepareForValDef(t)
217216
traverseChildren(tree)(using newCtx)
@@ -301,13 +300,14 @@ object CheckUnused:
301300
* The key used to retrieve the "unused entity" analysis metadata,
302301
* from the compilation `Context`
303302
*/
304-
private val _key = Property.StickyKey[UnusedData]
303+
private type Key = Property.StickyKey[UnusedData]
304+
private given Key = Property.StickyKey[UnusedData]
305305

306306
val OriginalName = Property.StickyKey[Name]
307307

308-
class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper", _key)
308+
class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper")
309309

310-
class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining", _key)
310+
class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining")
311311

312312
/** Track usages at a Context.
313313
*
@@ -321,12 +321,13 @@ object CheckUnused:
321321

322322
/** The current scope during the tree traversal */
323323
val currScopeType: Stack[ScopeType] = Stack(ScopeType.Other)
324+
inline def peekScopeType = currScopeType.top
324325

325326
var unusedAggregate: Option[UnusedResult] = None
326327

327328
/* IMPORTS */
328329
private val impInScope = Stack(ListBuffer.empty[ImportSelectorData])
329-
private val usedInScope = Stack(mut.Set.empty[Usage])
330+
private val usedInScope = Stack(mut.Map.empty[Symbol, ListBuffer[Usage]])
330331
private val usedInPosition = mut.Map.empty[Name, mut.Set[Symbol]]
331332
/* unused import collected during traversal */
332333
private val unusedImport = ListBuffer.empty[ImportSelectorData]
@@ -382,14 +383,19 @@ object CheckUnused:
382383
if sym.exists then
383384
usedDef += sym
384385
if includeForImport1 then
385-
usedInScope.top += Usage(sym, name, prefix, isDerived)
386+
addUsage(Usage(sym, name, prefix, isDerived))
386387
addIfExists(sym)
387388
addIfExists(sym.companionModule)
388389
addIfExists(sym.companionClass)
389390
if sym.sourcePos.exists then
390391
for n <- name do
391392
usedInPosition.getOrElseUpdate(n, mut.Set.empty) += sym
392393

394+
def addUsage(usage: Usage)(using Context): Unit =
395+
val usages = usedInScope.top.getOrElseUpdate(usage.symbol, ListBuffer.empty)
396+
if !usages.exists(cur => cur.name == usage.name && cur.isDerived == usage.isDerived && cur.prefix =:= usage.prefix)
397+
then usages += usage
398+
393399
/** Register a symbol that should be ignored */
394400
def addIgnoredUsage(sym: Symbol)(using Context): Unit =
395401
doNotRegister ++= sym.everySymbol
@@ -407,7 +413,7 @@ object CheckUnused:
407413
!tpd.languageImport(imp.expr).nonEmpty
408414
&& !imp.isGeneratedByEnum
409415
&& !isTransparentAndInline(imp)
410-
&& currScopeType.top != ScopeType.ReplWrapper // #18383 Do not report top-level import's in the repl as unused
416+
&& peekScopeType != ScopeType.ReplWrapper // #18383 Do not report top-level import's in the repl as unused
411417
then
412418
val qualTpe = imp.expr.tpe
413419

@@ -435,7 +441,7 @@ object CheckUnused:
435441
implicitParamInScope += memDef
436442
else if !paramsToSkip.contains(memDef.symbol) then
437443
explicitParamInScope += memDef
438-
else if currScopeType.top == ScopeType.Local then
444+
else if peekScopeType == ScopeType.Local then
439445
localDefInScope += memDef
440446
else if memDef.shouldReportPrivateDef then
441447
privateDefInScope += memDef
@@ -447,10 +453,9 @@ object CheckUnused:
447453

448454
/** enter a new scope */
449455
def pushScope(newScopeType: ScopeType): Unit =
450-
// unused imports :
451456
currScopeType.push(newScopeType)
452457
impInScope.push(ListBuffer.empty)
453-
usedInScope.push(mut.Set.empty)
458+
usedInScope.push(mut.Map.empty)
454459

455460
def registerSetVar(sym: Symbol): Unit =
456461
setVars += sym
@@ -460,20 +465,19 @@ object CheckUnused:
460465
*
461466
* - If there are imports in this scope check for unused ones
462467
*/
463-
def popScope()(using Context): Unit =
464-
currScopeType.pop()
465-
val usedInfos = usedInScope.pop()
468+
def popScope(scopeType: ScopeType)(using Context): Unit =
469+
assert(currScopeType.pop() == scopeType)
466470
val selDatas = impInScope.pop()
467471

468-
for usedInfo <- usedInfos do
469-
val Usage(sym, optName, prefix, isDerived) = usedInfo
470-
selDatas.find(sym.isInImport(_, optName, prefix, isDerived)) match
472+
for usedInfos <- usedInScope.pop().valuesIterator; usedInfo <- usedInfos do
473+
import usedInfo.*
474+
selDatas.find(symbol.isInImport(_, name, prefix, isDerived)) match
471475
case Some(sel) =>
472476
sel.markUsed()
473477
case None =>
474478
// Propagate the symbol one level up
475479
if usedInScope.nonEmpty then
476-
usedInScope.top += usedInfo
480+
addUsage(usedInfo)
477481
end for // each in usedInfos
478482

479483
for selData <- selDatas do
@@ -484,7 +488,7 @@ object CheckUnused:
484488
/** Leave the scope and return a result set of warnings.
485489
*/
486490
def getUnused(using Context): UnusedResult =
487-
popScope()
491+
popScope(ScopeType.Other) // sentinel
488492

489493
def isUsedInPosition(name: Name, span: Span): Boolean =
490494
usedInPosition.get(name) match
@@ -533,8 +537,6 @@ object CheckUnused:
533537

534538
UnusedResult(warnings.result)
535539
end getUnused
536-
//============================ HELPERS ====================================
537-
538540

539541
/**
540542
* Checks if import selects a def that is transparent and inline
@@ -634,14 +636,11 @@ object CheckUnused:
634636
if altName.exists(explicitName => selector.rename != explicitName.toTermName) then
635637
// if there is an explicit name, it must match
636638
false
637-
else
638-
(isDerived || prefix.typeSymbol.isPackageObject || selData.qualTpe =:= prefix) && (
639-
if isDerived then
640-
// See i15503i.scala, grep for "package foo.test.i17156"
641-
selData.allSymbolsDealiasedForNamed.contains(sym.dealiasAsType)
642-
else
643-
selData.allSymbolsForNamed.contains(sym)
644-
)
639+
else if isDerived then
640+
// See i15503i.scala, grep for "package foo.test.i17156"
641+
selData.allSymbolsDealiasedForNamed.contains(sym.dealiasAsType)
642+
else (prefix.typeSymbol.isPackageObject || selData.qualTpe =:= prefix) &&
643+
selData.allSymbolsForNamed.contains(sym)
645644
else
646645
// Wildcard
647646
if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then
@@ -668,9 +667,7 @@ object CheckUnused:
668667
val owner = sym.owner
669668
trivialDefs(owner) || // is a trivial def
670669
owner.isPrimaryConstructor ||
671-
owner.annotations.exists ( // @depreacated
672-
_.symbol == ctx.definitions.DeprecatedAnnot
673-
) ||
670+
owner.isDeprecated ||
674671
owner.isAllOf(Synthetic | PrivateLocal) ||
675672
owner.is(Accessor) ||
676673
owner.isOverridden
@@ -724,7 +721,7 @@ object CheckUnused:
724721
!sym.shouldNotReportParamOwner
725722

726723
private def shouldReportPrivateDef(using Context): Boolean =
727-
currScopeType.top == ScopeType.Template && !memDef.symbol.isConstructor && memDef.symbol.is(Private, butNot = SelfName | Synthetic | CaseAccessor)
724+
peekScopeType == ScopeType.Template && !memDef.symbol.isConstructor && memDef.symbol.is(Private, butNot = SelfName | Synthetic | CaseAccessor)
728725

729726
private def isUnsetVarDef(using Context): Boolean =
730727
val sym = memDef.symbol
@@ -751,11 +748,11 @@ object CheckUnused:
751748
object ScopeType:
752749
/** return the scope corresponding to the enclosing scope of the given tree */
753750
def fromTree(tree: tpd.Tree)(using Context): ScopeType = tree match
754-
case tree: tpd.Template => if tree.symbol.name.isReplWrapperName then ReplWrapper else Template
755-
case _:tpd.Block => Local
751+
case _: tpd.Template => if tree.symbol.name.isReplWrapperName then ReplWrapper else Template
752+
case _: tpd.Block => Local
756753
case _ => Other
757754

758-
final case class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector):
755+
final class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector):
759756
private var myUsed: Boolean = false
760757

761758
def markUsed(): Unit = myUsed = true
@@ -787,7 +784,7 @@ object CheckUnused:
787784
/** A symbol usage includes the name under which it was observed,
788785
* the prefix from which it was selected, and whether it is in a derived element.
789786
*/
790-
case class Usage(symbol: Symbol, name: Option[Name], prefix: Type, isDerived: Boolean)
787+
class Usage(val symbol: Symbol, val name: Option[Name], val prefix: Type, val isDerived: Boolean)
791788
end UnusedData
792789
extension (sym: Symbol)
793790
/** is accessible without import in current context */
@@ -808,5 +805,5 @@ object CheckUnused:
808805
case tp: NamedType => tp.prefix
809806
case tp: ClassInfo => tp.prefix
810807
case tp: TypeProxy => tp.superType.normalizedPrefix
811-
case _ => tp
808+
case _ => NoType
812809
end CheckUnused

0 commit comments

Comments
 (0)