Skip to content

Commit d64378b

Browse files
authored
Backport "Wunused: Include import selector bounds in unused checks" (#17355)
Backports #17323
2 parents 14237d3 + 12cd96e commit d64378b

File tree

4 files changed

+81
-12
lines changed

4 files changed

+81
-12
lines changed

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

+22-12
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,11 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
203203
override def traverse(tree: tpd.Tree)(using Context): Unit =
204204
val newCtx = if tree.symbol.exists then ctx.withOwner(tree.symbol) else ctx
205205
tree match
206-
case imp:tpd.Import =>
206+
case imp: tpd.Import =>
207207
unusedDataApply(_.registerImport(imp))
208+
imp.selectors.filter(_.isGiven).map(_.bound).collect {
209+
case untpd.TypedSplice(tree1) => tree1
210+
}.foreach(traverse(_)(using newCtx))
208211
traverseChildren(tree)(using newCtx)
209212
case ident: Ident =>
210213
prepareForIdent(ident)
@@ -450,13 +453,12 @@ object CheckUnused:
450453
val used = usedInScope.pop().toSet
451454
// used imports in this scope
452455
val imports = impInScope.pop()
453-
val kept = used.filterNot { t =>
454-
val (sym, isAccessible, optName, isDerived) = t
456+
val kept = used.filterNot { (sym, isAccessible, optName, isDerived) =>
455457
// keep the symbol for outer scope, if it matches **no** import
456458
// This is the first matching wildcard selector
457459
var selWildCard: Option[ImportSelector] = None
458460

459-
val exists = imports.exists { imp =>
461+
val matchedExplicitImport = imports.exists { imp =>
460462
sym.isInImport(imp, isAccessible, optName, isDerived) match
461463
case None => false
462464
case optSel@Some(sel) if sel.isWildcard =>
@@ -467,11 +469,11 @@ object CheckUnused:
467469
unusedImport -= sel
468470
true
469471
}
470-
if !exists && selWildCard.isDefined then
472+
if !matchedExplicitImport && selWildCard.isDefined then
471473
unusedImport -= selWildCard.get
472474
true // a matching import exists so the symbol won't be kept for outer scope
473475
else
474-
exists
476+
matchedExplicitImport
475477
}
476478

477479
// if there's an outer scope
@@ -611,12 +613,17 @@ object CheckUnused:
611613
* return true
612614
*/
613615
private def shouldSelectorBeReported(imp: tpd.Import, sel: ImportSelector)(using Context): Boolean =
614-
if ctx.settings.WunusedHas.strictNoImplicitWarn then
616+
ctx.settings.WunusedHas.strictNoImplicitWarn && (
615617
sel.isWildcard ||
616618
imp.expr.tpe.member(sel.name.toTermName).alternatives.exists(_.symbol.isOneOf(GivenOrImplicit)) ||
617619
imp.expr.tpe.member(sel.name.toTypeName).alternatives.exists(_.symbol.isOneOf(GivenOrImplicit))
618-
else
619-
false
620+
)
621+
622+
extension (tree: ImportSelector)
623+
def boundTpe: Type = tree.bound match {
624+
case untpd.TypedSplice(tree1) => tree1.tpe
625+
case _ => NoType
626+
}
620627

621628
extension (sym: Symbol)
622629
/** is accessible without import in current context */
@@ -629,7 +636,7 @@ object CheckUnused:
629636
&& c.owner.thisType.member(sym.name).alternatives.contains(sym)
630637
}
631638

632-
/** Given an import and accessibility, return an option of selector that match import<->symbol */
639+
/** Given an import and accessibility, return selector that matches import<->symbol */
633640
private def isInImport(imp: tpd.Import, isAccessible: Boolean, symName: Option[Name], isDerived: Boolean)(using Context): Option[ImportSelector] =
634641
val tpd.Import(qual, sels) = imp
635642
val dealiasedSym = dealias(sym)
@@ -642,9 +649,12 @@ object CheckUnused:
642649
def dealiasedSelector = if(isDerived) sels.flatMap(sel => selectionsToDealias.map(m => (sel, m.symbol))).collect {
643650
case (sel, sym) if dealias(sym) == dealiasedSym => sel
644651
}.headOption else None
645-
def wildcard = sels.find(sel => sel.isWildcard && ((sym.is(Given) == sel.isGiven) || sym.is(Implicit)))
652+
def givenSelector = if sym.is(Given) || sym.is(Implicit)
653+
then sels.filter(sel => sel.isGiven && !sel.bound.isEmpty).find(sel => sel.boundTpe =:= sym.info)
654+
else None
655+
def wildcard = sels.find(sel => sel.isWildcard && ((sym.is(Given) == sel.isGiven && sel.bound.isEmpty) || sym.is(Implicit)))
646656
if qualHasSymbol && (!isAccessible || sym.isRenamedSymbol(symName)) && sym.exists then
647-
selector.orElse(dealiasedSelector).orElse(wildcard) // selector with name or wildcard (or given)
657+
selector.orElse(dealiasedSelector).orElse(givenSelector).orElse(wildcard) // selector with name or wildcard (or given)
648658
else
649659
None
650660

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// scalac: -Wunused:all
2+
3+
package foo:
4+
class Foo[T]
5+
given Foo[Int] = new Foo[Int]
6+
7+
8+
package bar:
9+
import foo.{given foo.Foo[Int]} // error
10+
import foo.Foo
11+
12+
given Foo[Int] = ???
13+
14+
val repro: Foo[Int] = summon[Foo[Int]]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// scalac: "-Wunused:all"
2+
3+
import java.net.URI
4+
5+
object circelike {
6+
import scala.compiletime.summonInline
7+
import scala.deriving.Mirror
8+
9+
type Codec[T]
10+
type Configuration
11+
trait ConfiguredCodec[T]
12+
object ConfiguredCodec:
13+
inline final def derived[A](using conf: Configuration)(using
14+
inline mirror: Mirror.Of[A]
15+
): ConfiguredCodec[A] =
16+
new ConfiguredCodec[A]:
17+
val codec = summonInline[Codec[URI]] // simplification
18+
}
19+
20+
object foo {
21+
import circelike.{Codec, Configuration}
22+
23+
given Configuration = ???
24+
given Codec[URI] = ???
25+
}
26+
27+
object bar {
28+
import circelike.Codec
29+
import circelike.{Configuration, ConfiguredCodec}
30+
import foo.{given Configuration, given Codec[URI]}
31+
32+
case class Operator(url: URI) derives ConfiguredCodec
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// scalac: -Wunused:all
2+
3+
package foo:
4+
class Foo[T]
5+
given Foo[Int] = new Foo[Int]
6+
7+
8+
package bar:
9+
import foo.{given foo.Foo[Int]}
10+
import foo.Foo
11+
12+
val repro: Foo[Int] = summon[Foo[Int]]

0 commit comments

Comments
 (0)