Skip to content

Commit

Permalink
Fix #20146: attach the original name if there is an import selection …
Browse files Browse the repository at this point in the history
…for an indent (#20163)

Fix #20146

Attach the original name to the tree if there is an imported term
selection for an indent.
sjrd authored May 6, 2024
2 parents 827df03 + d192e48 commit b0d32aa
Showing 3 changed files with 38 additions and 21 deletions.
46 changes: 26 additions & 20 deletions compiler/src/dotty/tools/dotc/transform/CheckUnused.scala
Original file line number Diff line number Diff line change
@@ -97,7 +97,8 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
ctx

override def prepareForSelect(tree: tpd.Select)(using Context): Context =
unusedDataApply(_.registerUsed(tree.symbol, Some(tree.name)))
val name = tree.removeAttachment(OriginalName).orElse(Some(tree.name))
unusedDataApply(_.registerUsed(tree.symbol, name))

override def prepareForBlock(tree: tpd.Block)(using Context): Context =
pushInBlockTemplatePackageDef(tree)
@@ -328,6 +329,8 @@ object CheckUnused:
*/
private val _key = Property.StickyKey[UnusedData]

val OriginalName = Property.StickyKey[Name]

class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper", _key)

class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining", _key)
@@ -680,36 +683,39 @@ 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] =
private def isInImport(imp: tpd.Import, isAccessible: Boolean, altName: 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 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
val qualTpe = qual.tpe
val dealiasedSym = sym.dealias
val simpleSelections = qualTpe.member(sym.name).alternatives
val selectionsToDealias = sels.flatMap(sel =>
qualTpe.member(sel.name.toTypeName).alternatives
::: qualTpe.member(sel.name.toTermName).alternatives)
def 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) && altName.map(n => n.toTermName == sel.rename).getOrElse(true))
def dealiasedSelector =
if isDerived then
sels.flatMap(sel => selectionsToDealias.map(m => (sel, m.symbol))).collect {
case (sel, sym) if sym.dealias == 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)
if sym.exists && qualHasSymbol && (!isAccessible || sym.isRenamedSymbol(altName)) then
selector.orElse(dealiasedSelector).orElse(givenSelector).orElse(wildcard) // 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)

private def dealias(symbol: Symbol)(using Context): Symbol =
if(symbol.isType && symbol.asType.denot.isAliasType) then
symbol.asType.typeRef.dealias.typeSymbol
else symbol
private def dealias(using Context): Symbol =
if sym.isType && sym.asType.denot.isAliasType then
sym.asType.typeRef.dealias.typeSymbol
else sym

/** Annotated with @unused */
private def isUnusedAnnot(using Context): Boolean =
sym.annotations.exists(a => a.symbol == ctx.definitions.UnusedAnnot)
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
@@ -51,6 +51,7 @@ import NullOpsDecorator.*
import cc.{CheckCaptures, isRetainsLike}
import config.Config
import config.MigrationVersion
import transform.CheckUnused.OriginalName

import scala.annotation.constructorOnly

@@ -662,7 +663,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val selection = untpd.cpy.Select(tree)(qualifier, name)
typed(selection, pt)
else if rawType.exists then
setType(ensureAccessible(rawType, superAccess = false, tree.srcPos))
val ref = setType(ensureAccessible(rawType, superAccess = false, tree.srcPos))
if ref.symbol.name != name then
ref.withAttachment(OriginalName, name)
else ref
else if name == nme._scope then
// gross hack to support current xml literals.
// awaiting a better implicits based solution for library-supported xml
7 changes: 7 additions & 0 deletions tests/warn/i20146.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//> using options -Wunused:imports

def test(list: List[Int]): Int =
import list.{head => first}
import list.{length => len} // warn
import list.{addString => add} // warn
first + list.length

0 comments on commit b0d32aa

Please sign in to comment.