Skip to content

Commit 10efba5

Browse files
committed
Respect prefix when checking if selector selects
1 parent 1efbb92 commit 10efba5

File tree

2 files changed

+69
-36
lines changed

2 files changed

+69
-36
lines changed

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

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,12 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
9999
def loopOnNormalizedPrefixes(prefix: Type, depth: Int): Unit =
100100
// limit to 10 as failsafe for the odd case where there is an infinite cycle
101101
if depth < 10 && prefix.exists then
102-
ud.registerUsed(prefix.classSymbol, None)
102+
ud.registerUsed(prefix.classSymbol, None, prefix)
103103
loopOnNormalizedPrefixes(prefix.normalizedPrefix, depth + 1)
104104

105-
loopOnNormalizedPrefixes(tree.typeOpt.normalizedPrefix, depth = 0)
106-
ud.registerUsed(tree.symbol, Some(tree.name))
105+
val prefix = tree.typeOpt.normalizedPrefix
106+
loopOnNormalizedPrefixes(prefix, depth = 0)
107+
ud.registerUsed(tree.symbol, Some(tree.name), prefix)
107108
}
108109
else if tree.hasType then
109110
unusedDataApply(_.registerUsed(tree.tpe.classSymbol, Some(tree.name)))
@@ -112,7 +113,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
112113

113114
override def prepareForSelect(tree: tpd.Select)(using Context): Context =
114115
val name = tree.removeAttachment(OriginalName)
115-
unusedDataApply(_.registerUsed(tree.symbol, name, includeForImport = tree.qualifier.span.isSynthetic))
116+
unusedDataApply(_.registerUsed(tree.symbol, name, tree.qualifier.tpe, includeForImport = tree.qualifier.span.isSynthetic))
116117

117118
override def prepareForBlock(tree: tpd.Block)(using Context): Context =
118119
pushInBlockTemplatePackageDef(tree)
@@ -352,46 +353,46 @@ object CheckUnused:
352353
* - usage
353354
*/
354355
private class UnusedData:
355-
import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack, ListBuffer => MutList}
356+
import collection.mutable as mut, mut.Stack, mut.ListBuffer
356357
import UnusedData.*
357358

358359
/** The current scope during the tree traversal */
359-
val currScopeType: MutStack[ScopeType] = MutStack(ScopeType.Other)
360+
val currScopeType: Stack[ScopeType] = Stack(ScopeType.Other)
360361

361362
var unusedAggregate: Option[UnusedResult] = None
362363

363364
/* IMPORTS */
364-
private val impInScope = MutStack(MutList[ImportSelectorData]())
365+
private val impInScope = Stack(ListBuffer.empty[ImportSelectorData])
365366
/**
366367
* We store the symbol along with their accessibility without import.
367368
* Accessibility to their definition in outer context/scope
368369
*
369370
* See the `isAccessibleAsIdent` extension method below in the file
370371
*/
371-
private val usedInScope = MutStack(MutSet[(Symbol, Option[Name], Boolean)]())
372-
private val usedInPosition = MutMap.empty[Name, MutSet[Symbol]]
372+
private val usedInScope = Stack(mut.Set.empty[(Symbol, Option[Name], Type, Boolean)])
373+
private val usedInPosition = mut.Map.empty[Name, mut.Set[Symbol]]
373374
/* unused import collected during traversal */
374-
private val unusedImport = MutList.empty[ImportSelectorData]
375+
private val unusedImport = ListBuffer.empty[ImportSelectorData]
375376

376377
/* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
377-
private val localDefInScope = MutList.empty[tpd.MemberDef]
378-
private val privateDefInScope = MutList.empty[tpd.MemberDef]
379-
private val explicitParamInScope = MutList.empty[tpd.MemberDef]
380-
private val implicitParamInScope = MutList.empty[tpd.MemberDef]
381-
private val patVarsInScope = MutList.empty[tpd.Bind]
378+
private val localDefInScope = ListBuffer.empty[tpd.MemberDef]
379+
private val privateDefInScope = ListBuffer.empty[tpd.MemberDef]
380+
private val explicitParamInScope = ListBuffer.empty[tpd.MemberDef]
381+
private val implicitParamInScope = ListBuffer.empty[tpd.MemberDef]
382+
private val patVarsInScope = ListBuffer.empty[tpd.Bind]
382383

383384
/** All variables sets*/
384-
private val setVars = MutSet[Symbol]()
385+
private val setVars = mut.Set.empty[Symbol]
385386

386387
/** All used symbols */
387-
private val usedDef = MutSet[Symbol]()
388+
private val usedDef = mut.Set.empty[Symbol]
388389
/** Do not register as used */
389-
private val doNotRegister = MutSet[Symbol]()
390+
private val doNotRegister = mut.Set.empty[Symbol]
390391

391392
/** Trivial definitions, avoid registering params */
392-
private val trivialDefs = MutSet[Symbol]()
393+
private val trivialDefs = mut.Set.empty[Symbol]
393394

394-
private val paramsToSkip = MutSet[Symbol]()
395+
private val paramsToSkip = mut.Set.empty[Symbol]
395396

396397

397398
def finishAggregation(using Context)(): Unit =
@@ -411,10 +412,10 @@ object CheckUnused:
411412
* The optional name will be used to target the right import
412413
* as the same element can be imported with different renaming
413414
*/
414-
def registerUsed(sym: Symbol, name: Option[Name], includeForImport: Boolean = true, isDerived: Boolean = false)(using Context): Unit =
415+
def registerUsed(sym: Symbol, name: Option[Name], prefix: Type = NoType, includeForImport: Boolean = true, isDerived: Boolean = false)(using Context): Unit =
415416
if sym.exists && !isConstructorOfSynth(sym) && !doNotRegister(sym) then
416417
if sym.isConstructor then
417-
registerUsed(sym.owner, None, includeForImport) // constructor are "implicitly" imported with the class
418+
registerUsed(sym.owner, None, prefix, includeForImport) // constructor are "implicitly" imported with the class
418419
else
419420
// If the symbol is accessible in this scope without an import, do not register it for unused import analysis
420421
val includeForImport1 =
@@ -425,13 +426,13 @@ object CheckUnused:
425426
if sym.exists then
426427
usedDef += sym
427428
if includeForImport1 then
428-
usedInScope.top += ((sym, name, isDerived))
429+
usedInScope.top += ((sym, name, prefix, isDerived))
429430
addIfExists(sym)
430431
addIfExists(sym.companionModule)
431432
addIfExists(sym.companionClass)
432433
if sym.sourcePos.exists then
433434
for n <- name do
434-
usedInPosition.getOrElseUpdate(n, MutSet.empty) += sym
435+
usedInPosition.getOrElseUpdate(n, mut.Set.empty) += sym
435436

436437
/** Register a symbol that should be ignored */
437438
def addIgnoredUsage(sym: Symbol)(using Context): Unit =
@@ -455,12 +456,12 @@ object CheckUnused:
455456
val qualTpe = imp.expr.tpe
456457

457458
// Put wildcard imports at the end, because they have lower priority within one Import
458-
val reorderdSelectors =
459+
val reorderedSelectors =
459460
val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard)
460461
nonWildcardSels ::: wildcardSels
461462

462463
val newDataInScope =
463-
for sel <- reorderdSelectors yield
464+
for sel <- reorderedSelectors yield
464465
val data = new ImportSelectorData(qualTpe, sel)
465466
if shouldSelectorBeReported(imp, sel) || isImportExclusion(sel) || isImportIgnored(imp, sel) then
466467
// Immediately mark the selector as used
@@ -492,8 +493,8 @@ object CheckUnused:
492493
def pushScope(newScopeType: ScopeType): Unit =
493494
// unused imports :
494495
currScopeType.push(newScopeType)
495-
impInScope.push(MutList())
496-
usedInScope.push(MutSet())
496+
impInScope.push(ListBuffer.empty)
497+
usedInScope.push(mut.Set.empty)
497498

498499
def registerSetVar(sym: Symbol): Unit =
499500
setVars += sym
@@ -509,18 +510,16 @@ object CheckUnused:
509510
val selDatas = impInScope.pop()
510511

511512
for usedInfo <- usedInfos do
512-
val (sym, optName, isDerived) = usedInfo
513-
val usedData = selDatas.find { selData =>
514-
sym.isInImport(selData, optName, isDerived)
515-
}
513+
val (sym, optName, prefix, isDerived) = usedInfo
514+
val usedData = selDatas.find(sym.isInImport(_, optName, prefix, isDerived))
516515
usedData match
517516
case Some(data) =>
518517
data.markUsed()
519518
case None =>
520519
// Propagate the symbol one level up
521520
if usedInScope.nonEmpty then
522521
usedInScope.top += usedInfo
523-
end for // each in `used`
522+
end for // each in usedInfos
524523

525524
for selData <- selDatas do
526525
if !selData.isUsed then
@@ -705,7 +704,7 @@ object CheckUnused:
705704
}
706705

707706
/** Given an import and accessibility, return selector that matches import<->symbol */
708-
private def isInImport(selData: ImportSelectorData, altName: Option[Name], isDerived: Boolean)(using Context): Boolean =
707+
private def isInImport(selData: ImportSelectorData, altName: Option[Name], prefix: Type, isDerived: Boolean)(using Context): Boolean =
709708
assert(sym.exists)
710709

711710
val selector = selData.selector
@@ -714,12 +713,13 @@ object CheckUnused:
714713
if altName.exists(explicitName => selector.rename != explicitName.toTermName) then
715714
// if there is an explicit name, it must match
716715
false
717-
else
716+
else selData.qualTpe =:= prefix && (
718717
if isDerived then
719718
// See i15503i.scala, grep for "package foo.test.i17156"
720719
selData.allSymbolsDealiasedForNamed.contains(dealias(sym))
721720
else
722721
selData.allSymbolsForNamed.contains(sym)
722+
)
723723
else
724724
// Wildcard
725725
if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then
@@ -730,6 +730,7 @@ object CheckUnused:
730730
// Further check that the symbol is a given or implicit and conforms to the bound
731731
sym.isOneOf(Given | Implicit)
732732
&& (selector.bound.isEmpty || sym.info <:< selector.boundTpe)
733+
&& selData.qualTpe =:= prefix
733734
else
734735
// Normal wildcard, check that the symbol is not a given (but can be implicit)
735736
!sym.is(Given)
@@ -833,7 +834,7 @@ object CheckUnused:
833834
case _:tpd.Block => Local
834835
case _ => Other
835836

836-
final class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector):
837+
final case class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector):
837838
private var myUsed: Boolean = false
838839

839840
def markUsed(): Unit = myUsed = true

tests/warn/i19657.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//> using options -Wunused:imports
2+
3+
trait Schema[A]
4+
5+
case class Foo()
6+
case class Bar()
7+
8+
trait SchemaGenerator[A] {
9+
given Schema[A] = new Schema[A]{}
10+
}
11+
12+
object FooCodec extends SchemaGenerator[Foo]
13+
object BarCodec extends SchemaGenerator[Bar]
14+
15+
def summonSchemas(using Schema[Foo], Schema[Bar]) = ()
16+
17+
def summonSchema(using Schema[Foo]) = ()
18+
19+
def `i19657 check prefix to pick selector`: Unit =
20+
import FooCodec.given
21+
import BarCodec.given
22+
summonSchemas
23+
24+
def `i19657 regression test`: Unit =
25+
import FooCodec.given
26+
import BarCodec.given // warn
27+
summonSchema
28+
29+
def `i19657 check prefix to pick specific selector`: Unit =
30+
import FooCodec.given_Schema_A
31+
import BarCodec.given_Schema_A
32+
summonSchemas

0 commit comments

Comments
 (0)