Skip to content

Commit

Permalink
Backport "Fix wunused false positive when deriving alias type" (#17276)
Browse files Browse the repository at this point in the history
Backports #17157
  • Loading branch information
Kordyjan authored Apr 17, 2023
2 parents ae37e68 + ec298fa commit 5e866e3
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 12 deletions.
40 changes: 28 additions & 12 deletions compiler/src/dotty/tools/dotc/transform/CheckUnused.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import dotty.tools.dotc.core.Annotations
import dotty.tools.dotc.core.Definitions
import dotty.tools.dotc.core.NameKinds.WildcardParamName
import dotty.tools.dotc.core.Symbols.Symbol

import dotty.tools.dotc.core.StdNames.nme


/**
Expand Down Expand Up @@ -109,6 +109,9 @@ class CheckUnused extends MiniPhase:
traverseAnnotations(tree.symbol)
if !tree.symbol.is(Module) then
ud.registerDef(tree)
if tree.name.mangledString.startsWith(nme.derived.mangledString + "$")
&& tree.typeOpt != NoType then
ud.registerUsed(tree.typeOpt.typeSymbol, None, true)
ud.addIgnoredUsage(tree.symbol)
}

Expand Down Expand Up @@ -304,7 +307,7 @@ object CheckUnused:
*
* See the `isAccessibleAsIdent` extension method below in the file
*/
private val usedInScope = MutStack(MutSet[(Symbol,Boolean, Option[Name])]())
private val usedInScope = MutStack(MutSet[(Symbol,Boolean, Option[Name], Boolean)]())
private val usedInPosition = MutSet[(SrcPos, Name)]()
/* unused import collected during traversal */
private val unusedImport = MutSet[ImportSelector]()
Expand Down Expand Up @@ -347,14 +350,14 @@ object CheckUnused:
* The optional name will be used to target the right import
* as the same element can be imported with different renaming
*/
def registerUsed(sym: Symbol, name: Option[Name])(using Context): Unit =
def registerUsed(sym: Symbol, name: Option[Name], isDerived: Boolean = false)(using Context): Unit =
if !isConstructorOfSynth(sym) && !doNotRegister(sym) then
if sym.isConstructor && sym.exists then
registerUsed(sym.owner, None) // constructor are "implicitly" imported with the class
else
usedInScope.top += ((sym, sym.isAccessibleAsIdent, name))
usedInScope.top += ((sym.companionModule, sym.isAccessibleAsIdent, name))
usedInScope.top += ((sym.companionClass, sym.isAccessibleAsIdent, name))
usedInScope.top += ((sym, sym.isAccessibleAsIdent, name, isDerived))
usedInScope.top += ((sym.companionModule, sym.isAccessibleAsIdent, name, isDerived))
usedInScope.top += ((sym.companionClass, sym.isAccessibleAsIdent, name, isDerived))
name.map(n => usedInPosition += ((sym.sourcePos, n)))

/** Register a symbol that should be ignored */
Expand Down Expand Up @@ -408,15 +411,15 @@ object CheckUnused:
// used symbol in this scope
val used = usedInScope.pop().toSet
// used imports in this scope
val imports = impInScope.pop().toSet
val imports = impInScope.pop()
val kept = used.filterNot { t =>
val (sym, isAccessible, optName) = t
val (sym, isAccessible, optName, isDerived) = t
// keep the symbol for outer scope, if it matches **no** import
// This is the first matching wildcard selector
var selWildCard: Option[ImportSelector] = None

val exists = imports.exists { imp =>
sym.isInImport(imp, isAccessible, optName) match
sym.isInImport(imp, isAccessible, optName, isDerived) match
case None => false
case optSel@Some(sel) if sel.isWildcard =>
if selWildCard.isEmpty then selWildCard = optSel
Expand Down Expand Up @@ -587,16 +590,29 @@ object CheckUnused:
}

/** Given an import and accessibility, return an option of selector that match import<->symbol */
private def isInImport(imp: tpd.Import, isAccessible: Boolean, symName: Option[Name])(using Context): Option[ImportSelector] =
private def isInImport(imp: tpd.Import, isAccessible: Boolean, symName: Option[Name], isDerived: Boolean)(using Context): Option[ImportSelector] =
val tpd.Import(qual, sels) = imp
val qualHasSymbol = qual.tpe.member(sym.name).alternatives.map(_.symbol).contains(sym)
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 wildcard = sels.find(sel => sel.isWildcard && ((sym.is(Given) == sel.isGiven) || sym.is(Implicit)))
if qualHasSymbol && !isAccessible && sym.exists then
selector.orElse(wildcard) // selector with name or wildcard (or given)
selector.orElse(dealiasedSelector).orElse(wildcard) // selector with name or wildcard (or given)
else
None


private def dealias(symbol: Symbol)(using Context): Symbol =
if(symbol.isType && symbol.asType.denot.isAliasType) then
symbol.asType.typeRef.dealias.typeSymbol
else symbol
/** Annotated with @unused */
private def isUnusedAnnot(using Context): Boolean =
sym.annotations.exists(a => a.symbol == ctx.definitions.UnusedAnnot)
Expand Down
14 changes: 14 additions & 0 deletions tests/neg-custom-args/fatal-warnings/i15503i.scala
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,17 @@ package foo.test.i16679b:
import Foo.x
case class CoolClass(i: Int)
println(summon[myPackage.CaseClassName[CoolClass]])

package foo.test.i17156:
package a:
trait Foo[A]
object Foo:
inline def derived[T]: Foo[T] = new Foo{}

package b:
import a.Foo
type Xd[A] = Foo[A]

package c:
import b.Xd
trait Z derives Xd

0 comments on commit 5e866e3

Please sign in to comment.