Skip to content

Commit aae9ec1

Browse files
committed
Wunused: Include import selector bounds in unused checks
closes lampepfl#17314
1 parent b70698d commit aae9ec1

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
@@ -202,8 +202,11 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
202202
override def traverse(tree: tpd.Tree)(using Context): Unit =
203203
val newCtx = if tree.symbol.exists then ctx.withOwner(tree.symbol) else ctx
204204
tree match
205-
case imp:tpd.Import =>
205+
case imp: tpd.Import =>
206206
unusedDataApply(_.registerImport(imp))
207+
imp.selectors.filter(_.isGiven).map(_.bound).collect {
208+
case untpd.TypedSplice(tree1) => tree1
209+
}.foreach(traverse(_)(using newCtx))
207210
traverseChildren(tree)(using newCtx)
208211
case ident: Ident =>
209212
prepareForIdent(ident)
@@ -449,13 +452,12 @@ object CheckUnused:
449452
val used = usedInScope.pop().toSet
450453
// used imports in this scope
451454
val imports = impInScope.pop()
452-
val kept = used.filterNot { t =>
453-
val (sym, isAccessible, optName, isDerived) = t
455+
val kept = used.filterNot { (sym, isAccessible, optName, isDerived) =>
454456
// keep the symbol for outer scope, if it matches **no** import
455457
// This is the first matching wildcard selector
456458
var selWildCard: Option[ImportSelector] = None
457459

458-
val exists = imports.exists { imp =>
460+
val matchedExplicitImport = imports.exists { imp =>
459461
sym.isInImport(imp, isAccessible, optName, isDerived) match
460462
case None => false
461463
case optSel@Some(sel) if sel.isWildcard =>
@@ -466,11 +468,11 @@ object CheckUnused:
466468
unusedImport -= sel
467469
true
468470
}
469-
if !exists && selWildCard.isDefined then
471+
if !matchedExplicitImport && selWildCard.isDefined then
470472
unusedImport -= selWildCard.get
471473
true // a matching import exists so the symbol won't be kept for outer scope
472474
else
473-
exists
475+
matchedExplicitImport
474476
}
475477

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

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

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

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)