Skip to content

Commit

Permalink
Fix false positive in WUnused for renamed path-dependent imports (att…
Browse files Browse the repository at this point in the history
…empt 2)
  • Loading branch information
szymon-rd committed Sep 29, 2023
1 parent 982c91b commit 5246e16
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 22 deletions.
46 changes: 24 additions & 22 deletions compiler/src/dotty/tools/dotc/transform/CheckUnused.scala
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ object CheckUnused:
* - usage
*/
private class UnusedData:
import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack}
import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack, ListBuffer => MutList}
import UnusedData.*

/** The current scope during the tree traversal */
Expand All @@ -346,7 +346,7 @@ object CheckUnused:
var unusedAggregate: Option[UnusedResult] = None

/* IMPORTS */
private val impInScope = MutStack(MutSet[tpd.Import]())
private val impInScope = MutStack(MutList[tpd.Import]())
/**
* We store the symbol along with their accessibility without import.
* Accessibility to their definition in outer context/scope
Expand Down Expand Up @@ -449,7 +449,7 @@ object CheckUnused:
def pushScope(newScopeType: ScopeType): Unit =
// unused imports :
currScopeType.push(newScopeType)
impInScope.push(MutSet())
impInScope.push(MutList())
usedInScope.push(MutSet())

def registerSetVar(sym: Symbol): Unit =
Expand Down Expand Up @@ -664,25 +664,27 @@ object CheckUnused:

/** Given an import and accessibility, return selector that matches import<->symbol */
private def isInImport(imp: tpd.Import, isAccessible: Boolean, symName: Option[Name], isDerived: Boolean)(using Context): Option[ImportSelector] =
val tpd.Import(qual, sels) = imp
val dealiasedSym = dealias(sym)
val simpleSelections = qual.tpe.member(sym.name).alternatives
val typeSelections = sels.flatMap(n => qual.tpe.member(n.name.toTypeName).alternatives)
val termSelections = sels.flatMap(n => qual.tpe.member(n.name.toTermName).alternatives)
val selectionsToDealias = typeSelections ::: termSelections
val qualHasSymbol = simpleSelections.map(_.symbol).contains(sym) || (simpleSelections ::: selectionsToDealias).map(_.symbol).map(dealias).contains(dealiasedSym)
def selector = sels.find(sel => (sel.name.toTermName == sym.name || sel.name.toTypeName == sym.name) && symName.map(n => n.toTermName == sel.rename).getOrElse(true))
def dealiasedSelector = if(isDerived) sels.flatMap(sel => selectionsToDealias.map(m => (sel, m.symbol))).collect {
case (sel, sym) if dealias(sym) == dealiasedSym => sel
}.headOption else None
def givenSelector = if sym.is(Given) || sym.is(Implicit)
then sels.filter(sel => sel.isGiven && !sel.bound.isEmpty).find(sel => sel.boundTpe =:= sym.info)
else None
def wildcard = sels.find(sel => sel.isWildcard && ((sym.is(Given) == sel.isGiven && sel.bound.isEmpty) || sym.is(Implicit)))
if qualHasSymbol && (!isAccessible || sym.isRenamedSymbol(symName)) && sym.exists then
selector.orElse(dealiasedSelector).orElse(givenSelector).orElse(wildcard) // selector with name or wildcard (or given)
else
None
val tpd.Import(qual, sels) = imp
val dealiasedSym = dealias(sym)
val simpleSelections = qual.tpe.member(sym.name).alternatives
val typeSelections = sels.flatMap(n => qual.tpe.member(n.name.toTypeName).alternatives)
val termSelections = sels.flatMap(n => qual.tpe.member(n.name.toTermName).alternatives)
val sameTermPath = qual.isTerm && sym.exists && sym.owner.isType && qual.tpe.typeSymbol == sym.owner.asType
val selectionsToDealias = typeSelections ::: termSelections
val renamedSelection = if sameTermPath then sels.find(sel => sel.imported.name == sym.name) else None
val qualHasSymbol = simpleSelections.map(_.symbol).contains(sym) || (simpleSelections ::: selectionsToDealias).map(_.symbol).map(dealias).contains(dealiasedSym) || renamedSelection.isDefined
def selector = sels.find(sel => (sel.name.toTermName == sym.name || sel.name.toTypeName == sym.name) && symName.map(n => n.toTermName == sel.rename).getOrElse(true))
def dealiasedSelector = if(isDerived) sels.flatMap(sel => selectionsToDealias.map(m => (sel, m.symbol))).collect {
case (sel, sym) if dealias(sym) == dealiasedSym => sel
}.headOption else None
def givenSelector = if sym.is(Given) || sym.is(Implicit)
then sels.filter(sel => sel.isGiven && !sel.bound.isEmpty).find(sel => sel.boundTpe =:= sym.info)
else None
def wildcard = sels.find(sel => sel.isWildcard && ((sym.is(Given) == sel.isGiven && sel.bound.isEmpty) || sym.is(Implicit)))
if qualHasSymbol && (!isAccessible || sym.isRenamedSymbol(symName)) && sym.exists then
selector.orElse(dealiasedSelector).orElse(givenSelector).orElse(wildcard).orElse(renamedSelection) // selector with name or wildcard (or given)
else
None

private def isRenamedSymbol(symNameInScope: Option[Name])(using Context) =
sym.name != nme.NO_NAME && symNameInScope.exists(_.toSimpleName != sym.name.toSimpleName)
Expand Down
10 changes: 10 additions & 0 deletions tests/pos/i18366.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//> using options -Xfatal-warnings -Wunused:all

trait Builder {
def foo(): Unit
}

def repro =
val builder: Builder = ???
import builder.{foo => bar}
bar()

0 comments on commit 5246e16

Please sign in to comment.