@@ -21,7 +21,7 @@ import dotty.tools.dotc.core.NameOps.isReplWrapperName
2121import dotty .tools .dotc .core .Annotations
2222import dotty .tools .dotc .core .Definitions
2323import dotty .tools .dotc .core .NameKinds .WildcardParamName
24- import dotty .tools .dotc .core .Symbols .Symbol
24+ import dotty .tools .dotc .core .Symbols .{ Symbol , isDeprecated }
2525import dotty .tools .dotc .report
2626import dotty .tools .dotc .reporting .{Message , UnusedSymbol as UnusedSymbolMessage }
2727import dotty .tools .dotc .transform .MegaPhase .MiniPhase
@@ -30,19 +30,21 @@ import dotty.tools.dotc.util.{Property, SrcPos}
3030import dotty .tools .dotc .util .Spans .Span
3131import 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
812809end CheckUnused
0 commit comments