From 5246e16134265d88d8dd89f34191d48b41703859 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Fri, 29 Sep 2023 12:25:50 +0200 Subject: [PATCH] Fix false positive in WUnused for renamed path-dependent imports (attempt 2) --- .../tools/dotc/transform/CheckUnused.scala | 46 ++++++++++--------- tests/pos/i18366.scala | 10 ++++ 2 files changed, 34 insertions(+), 22 deletions(-) create mode 100644 tests/pos/i18366.scala diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index eb3c37622e0d..77f773167eee 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -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 */ @@ -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 @@ -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 = @@ -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) diff --git a/tests/pos/i18366.scala b/tests/pos/i18366.scala new file mode 100644 index 000000000000..698510ad13a2 --- /dev/null +++ b/tests/pos/i18366.scala @@ -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() \ No newline at end of file