Skip to content

Commit 5ef12f7

Browse files
committed
Attachment tracks derivation
1 parent b5cdf4d commit 5ef12f7

File tree

2 files changed

+60
-67
lines changed

2 files changed

+60
-67
lines changed

compiler/src/dotty/tools/dotc/transform/CheckUnused.scala

Lines changed: 49 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ package dotty.tools.dotc.transform
33
import scala.annotation.tailrec
44

55
import dotty.tools.uncheckedNN
6-
import dotty.tools.dotc.ast.tpd
7-
import dotty.tools.dotc.ast.tpd.{Inlined, TreeTraverser}
6+
import dotty.tools.dotc.ast.tpd, tpd.{Ident, Inlined, Select, Tree, TreeTraverser}
87
import dotty.tools.dotc.ast.untpd, untpd.ImportSelector
98
import dotty.tools.dotc.config.ScalaSettings
109
import dotty.tools.dotc.core.Contexts.*
@@ -112,14 +111,27 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
112111
pushScope(tree)
113112

114113
override def prepareForValDef(tree: tpd.ValDef)(using Context): Context =
114+
preparing:
115+
ud.addIgnoredUsage(tree.symbol)
116+
117+
override def transformValDef(tree: tpd.ValDef)(using Context): tpd.Tree =
115118
preparing:
116119
traverseAnnotations(tree.symbol)
117-
// do not register the ValDef generated for `object`
118-
if !tree.symbol.is(Module) then
120+
if !tree.symbol.is(Module) then // do not register the ValDef generated for `object`
119121
ud.registerDef(tree)
120122
if tree.name.startsWith("derived$") && tree.hasType then
121-
ud.registerUsed(tree.tpe.typeSymbol, name = None, tree.tpe.importPrefix, isDerived = true)
122-
ud.addIgnoredUsage(tree.symbol)
123+
def core(t: Tree): (Symbol, Option[Name], Type) = t match
124+
case Ident(name) => (t.tpe.typeSymbol, Some(name), t.tpe.underlyingPrefix)
125+
case Select(t, _) => core(t)
126+
case _ => (NoSymbol, None, NoType)
127+
tree.getAttachment(OriginalTypeClass) match
128+
case Some(orig) =>
129+
val (typsym, name, prefix) = core(orig)
130+
ud.registerUsed(typsym, name, prefix.skipPackageObject)
131+
case _ =>
132+
ud.removeIgnoredUsage(tree.symbol)
133+
tree
134+
123135

124136
override def prepareForDefDef(tree: tpd.DefDef)(using Context): Context =
125137
preparing:
@@ -166,11 +178,6 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
166178
popScope(tree)
167179
tree
168180

169-
override def transformValDef(tree: tpd.ValDef)(using Context): tpd.Tree =
170-
preparing:
171-
ud.removeIgnoredUsage(tree.symbol)
172-
tree
173-
174181
override def transformDefDef(tree: tpd.DefDef)(using Context): tpd.Tree =
175182
preparing:
176183
ud.removeIgnoredUsage(tree.symbol)
@@ -312,14 +319,15 @@ object CheckUnused:
312319
case UnsetLocals
313320
case UnsetPrivates
314321

315-
/**
316-
* The key used to retrieve the "unused entity" analysis metadata,
317-
* from the compilation `Context`
318-
*/
322+
/** The key used to retrieve the "unused entity" analysis metadata from the compilation `Context` */
319323
private val _key = Property.StickyKey[UnusedData]
320324

325+
/** Attachment holding the name of an Ident as written by the user. */
321326
val OriginalName = Property.StickyKey[Name]
322327

328+
/** Attachment holding the name of a type class as written by the user. */
329+
val OriginalTypeClass = Property.StickyKey[tpd.Tree]
330+
323331
class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper", _key)
324332

325333
class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining", _key)
@@ -384,7 +392,7 @@ object CheckUnused:
384392
* The optional name will be used to target the right import
385393
* as the same element can be imported with different renaming
386394
*/
387-
def registerUsed(sym: Symbol, name: Option[Name], prefix: Type = NoType, includeForImport: Boolean = true, isDerived: Boolean = false)(using Context): Unit =
395+
def registerUsed(sym: Symbol, name: Option[Name], prefix: Type = NoType, includeForImport: Boolean = true)(using Context): Unit =
388396
if sym.exists && !isConstructorOfSynth(sym) && !doNotRegister(sym) then
389397
if sym.isConstructor then
390398
// constructors are "implicitly" imported with the class
@@ -399,7 +407,7 @@ object CheckUnused:
399407
if sym.exists then
400408
usedDef += sym
401409
if includeForImport1 then
402-
addUsage(Usage(sym, name, prefix, isDerived))
410+
addUsage(Usage(sym, name, prefix))
403411
addIfExists(sym)
404412
addIfExists(sym.companionModule)
405413
addIfExists(sym.companionClass)
@@ -409,7 +417,7 @@ object CheckUnused:
409417

410418
def addUsage(usage: Usage)(using Context): Unit =
411419
val usages = usedInScope.top.getOrElseUpdate(usage.symbol, ListBuffer.empty)
412-
if !usages.exists(x => x.name == usage.name && x.isDerived == usage.isDerived && x.prefix =:= usage.prefix)
420+
if !usages.exists(x => x.name == usage.name && x.prefix =:= usage.prefix)
413421
then usages += usage
414422

415423
/** Register a symbol that should be ignored */
@@ -477,18 +485,15 @@ object CheckUnused:
477485
def registerSetVar(sym: Symbol): Unit =
478486
setVars += sym
479487

480-
/**
481-
* leave the current scope and do :
482-
*
483-
* - If there are imports in this scope check for unused ones
488+
/** Leave current scope and mark any used imports; collect unused imports.
484489
*/
485490
def popScope(scopeType: ScopeType)(using Context): Unit =
486491
assert(currScopeType.pop() == scopeType)
487492
val selDatas = impInScope.pop()
488493

489494
for usedInfos <- usedInScope.pop().valuesIterator; usedInfo <- usedInfos do
490495
import usedInfo.*
491-
selDatas.find(symbol.isInImport(_, name, prefix, isDerived)) match
496+
selDatas.find(symbol.isInImport(_, name, prefix)) match
492497
case Some(sel) =>
493498
sel.markUsed()
494499
case None =>
@@ -646,35 +651,23 @@ object CheckUnused:
646651
/** Given an import selector, is the symbol imported from the given prefix, optionally with a specific name?
647652
* If isDerived, then it may be an aliased type in source but we only witness it dealiased.
648653
*/
649-
private def isInImport(selData: ImportSelectorData, altName: Option[Name], prefix: Type, isDerived: Boolean)(using Context): Boolean =
654+
private def isInImport(selData: ImportSelectorData, altName: Option[Name], prefix: Type)(using Context): Boolean =
650655
assert(sym.exists)
651656

652657
val selector = selData.selector
653658

654-
if !selector.isWildcard then
655-
if altName.exists(explicitName => selector.rename != explicitName.toTermName) then
656-
// if there is an explicit name, it must match
657-
false
658-
else if isDerived then
659-
// See i15503i.scala, grep for "package foo.test.i17156"
660-
selData.allSymbolsDealiasedForNamed.contains(sym.dealiasAsType)
661-
else (prefix.typeSymbol.isPackageObject || selData.qualTpe =:= prefix) &&
662-
selData.allSymbolsForNamed.contains(sym)
663-
else
664-
// Wildcard
665-
if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then
666-
// The qualifier does not have the target symbol as a member
667-
false
668-
else
669-
if selector.isGiven then
670-
// Further check that the symbol is a given or implicit and conforms to the bound
659+
if selector.isWildcard then
660+
selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) && { // The qualifier must have the target symbol as a member
661+
if selector.isGiven then // Further check that the symbol is a given or implicit and conforms to the bound
671662
sym.isOneOf(Given | Implicit)
672663
&& (selector.bound.isEmpty || sym.info.finalResultType <:< selector.boundTpe)
673664
&& selData.qualTpe =:= prefix
674665
else
675-
// Normal wildcard, check that the symbol is not a given (but can be implicit)
676-
!sym.is(Given)
677-
end if
666+
!sym.is(Given) // Normal wildcard, check that the symbol is not a given (but can be implicit)
667+
}
668+
else
669+
!altName.exists(_.toTermName != selector.rename) && // if there is an explicit name, it must match
670+
selData.qualTpe =:= prefix && selData.allSymbolsForNamed.contains(sym)
678671
end isInImport
679672

680673
/** Annotated with @unused */
@@ -786,12 +779,6 @@ object CheckUnused:
786779
myAllSymbols = allDenots.map(_.symbol).toSet
787780
myAllSymbols.uncheckedNN
788781

789-
private var myAllSymbolsDealiased: Set[Symbol] | Null = null
790-
791-
def allSymbolsDealiasedForNamed(using Context): Set[Symbol] =
792-
if myAllSymbolsDealiased == null then
793-
myAllSymbolsDealiased = allSymbolsForNamed.map(_.dealiasAsType)
794-
myAllSymbolsDealiased.uncheckedNN
795782
end ImportSelectorData
796783

797784
case class UnusedSymbol(pos: SrcPos, name: Name, warnType: WarnTypes)
@@ -801,9 +788,9 @@ object CheckUnused:
801788
val Empty = UnusedResult(Set.empty)
802789

803790
/** A symbol usage includes the name under which it was observed,
804-
* the prefix from which it was selected, and whether it is in a derived element.
791+
* and the prefix from which it was selected.
805792
*/
806-
class Usage(val symbol: Symbol, val name: Option[Name], val prefix: Type, val isDerived: Boolean)
793+
class Usage(val symbol: Symbol, val name: Option[Name], val prefix: Type)
807794
end UnusedData
808795
extension (sym: Symbol)
809796
/** is accessible without import in current context */
@@ -814,15 +801,20 @@ object CheckUnused:
814801
&& c.owner.thisType.baseClasses.contains(sym.owner)
815802
&& c.owner.thisType.member(sym.name).alternatives.contains(sym)
816803

817-
def dealiasAsType(using Context): Symbol =
818-
if sym.isType && sym.asType.denot.isAliasType then
819-
sym.asType.typeRef.dealias.typeSymbol
820-
else
821-
sym
822804
extension (tp: Type)
823805
def importPrefix(using Context): Type = tp match
824806
case tp: NamedType => tp.prefix
825807
case tp: ClassInfo => tp.prefix
826808
case tp: TypeProxy => tp.superType.normalizedPrefix
827809
case _ => NoType
810+
def underlyingPrefix(using Context): Type = tp match
811+
case tp: NamedType => tp.prefix
812+
case tp: ClassInfo => tp.prefix
813+
case tp: TypeProxy => tp.underlying.underlyingPrefix
814+
case _ => NoType
815+
def skipPackageObject(using Context): Type =
816+
if tp.typeSymbol.isPackageObject then tp.underlyingPrefix else tp
817+
def underlying(using Context): Type = tp match
818+
case tp: TypeProxy => tp.underlying
819+
case _ => tp
828820
end CheckUnused

compiler/src/dotty/tools/dotc/typer/Deriving.scala

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import Contexts.*, Symbols.*, Types.*, SymDenotations.*, Names.*, NameOps.*, Fla
1010
import ProtoTypes.*, ContextOps.*
1111
import util.Spans.*
1212
import util.SrcPos
13-
import collection.mutable
13+
import collection.mutable.ListBuffer
1414
import ErrorReporting.errorTree
15+
import transform.CheckUnused.OriginalTypeClass
1516

1617
/** A typer mixin that implements type class derivation functionality */
1718
trait Deriving {
@@ -25,8 +26,8 @@ trait Deriving {
2526
*/
2627
class Deriver(cls: ClassSymbol, codePos: SrcPos)(using Context) {
2728

28-
/** A buffer for synthesized symbols for type class instances */
29-
private var synthetics = new mutable.ListBuffer[Symbol]
29+
/** A buffer for synthesized symbols for type class instances, with what user asked to synthesize. */
30+
private val synthetics = ListBuffer.empty[(tpd.Tree, Symbol)]
3031

3132
/** A version of Type#underlyingClassRef that works also for higher-kinded types */
3233
private def underlyingClassRef(tp: Type): Type = tp match {
@@ -41,7 +42,7 @@ trait Deriving {
4142
* an instance with the same name does not exist already.
4243
* @param reportErrors Report an error if an instance with the same name exists already
4344
*/
44-
private def addDerivedInstance(clsName: Name, info: Type, pos: SrcPos): Unit = {
45+
private def addDerivedInstance(derived: tpd.Tree, clsName: Name, info: Type, pos: SrcPos): Unit = {
4546
val instanceName = "derived$".concat(clsName)
4647
if (ctx.denotNamed(instanceName).exists)
4748
report.error(em"duplicate type class derivation for $clsName", pos)
@@ -50,9 +51,8 @@ trait Deriving {
5051
// derived instance will have too low a priority to be selected over a freshly
5152
// derived instance at the summoning site.
5253
val flags = if info.isInstanceOf[MethodOrPoly] then GivenMethod else Given | Lazy
53-
synthetics +=
54-
newSymbol(ctx.owner, instanceName, flags, info, coord = pos.span)
55-
.entered
54+
val sym = newSymbol(ctx.owner, instanceName, flags, info, coord = pos.span).entered
55+
synthetics += derived -> sym
5656
}
5757

5858
/** Check derived type tree `derived` for the following well-formedness conditions:
@@ -77,7 +77,8 @@ trait Deriving {
7777
* that have the same name but different prefixes through selective aliasing.
7878
*/
7979
private def processDerivedInstance(derived: untpd.Tree): Unit = {
80-
val originalTypeClassType = typedAheadType(derived, AnyTypeConstructorProto).tpe
80+
val originalTypeClassTree = typedAheadType(derived, AnyTypeConstructorProto)
81+
val originalTypeClassType = originalTypeClassTree.tpe
8182
val underlyingClassType = underlyingClassRef(originalTypeClassType)
8283
val typeClassType = checkClassType(
8384
underlyingClassType.orElse(originalTypeClassType),
@@ -100,7 +101,7 @@ trait Deriving {
100101
val derivedInfo =
101102
if derivedParams.isEmpty then monoInfo
102103
else PolyType.fromParams(derivedParams, monoInfo)
103-
addDerivedInstance(originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos)
104+
addDerivedInstance(originalTypeClassTree, originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos)
104105
}
105106

106107
def deriveSingleParameter: Unit = {
@@ -312,7 +313,7 @@ trait Deriving {
312313
else tpd.ValDef(sym.asTerm, typeclassInstance(sym)(Nil))
313314
}
314315

315-
synthetics.map(syntheticDef).toList
316+
synthetics.map((t, s) => syntheticDef(s).withAttachment(OriginalTypeClass, t)).toList
316317
}
317318

318319
def finalize(stat: tpd.TypeDef): tpd.Tree = {

0 commit comments

Comments
 (0)