Skip to content

Commit be1f3a0

Browse files
committed
Handle derived typeclasses with non-trivial prefixes
1 parent 970c389 commit be1f3a0

File tree

4 files changed

+89
-13
lines changed

4 files changed

+89
-13
lines changed

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

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@ import Inferencing._
1616
import transform.TypeUtils._
1717
import transform.SymUtils._
1818

19-
20-
// TODOs:
21-
// - handle case where there's no companion object
22-
// - check that derived instances are stable
23-
// - reference derived instances with correct prefix instead of just the symbol
24-
2519
/** A typer mixin that implements typeclass derivation functionality */
2620
trait Deriving { this: Typer =>
2721

@@ -131,14 +125,23 @@ trait Deriving { this: Typer =>
131125
flags: FlagSet = EmptyFlags)(implicit ctx: Context): TermSymbol =
132126
newSymbol(name, info, pos, flags | Method).asTerm
133127

128+
/** A version of Type#underlyingClassRef that works also for higher-kinded types */
129+
private def underlyingClassRef(tp: Type): Type = tp match {
130+
case tp: TypeRef if tp.symbol.isClass => tp
131+
case tp: TypeRef if tp.symbol.isAbstractType => NoType
132+
case tp: TermRef => NoType
133+
case tp: TypeProxy => underlyingClassRef(tp.underlying)
134+
case _ => NoType
135+
}
136+
134137
/** Enter type class instance with given name and info in current scope, provided
135138
* an instance with the same name does not exist already.
136139
* @param reportErrors Report an error if an instance with the same name exists already
137140
*/
138141
private def addDerivedInstance(clsName: Name, info: Type, pos: Position, reportErrors: Boolean) = {
139142
val instanceName = s"derived$$$clsName".toTermName
140143
if (ctx.denotNamed(instanceName).exists) {
141-
if (reportErrors) ctx.error(i"duplicate typeclass derivation for $clsName")
144+
if (reportErrors) ctx.error(i"duplicate typeclass derivation for $clsName", pos)
142145
}
143146
else add(newMethod(instanceName, info, pos, Implicit))
144147
}
@@ -162,8 +165,9 @@ trait Deriving { this: Typer =>
162165
* that have the same name but different prefixes through selective aliasing.
163166
*/
164167
private def processDerivedInstance(derived: untpd.Tree): Unit = {
165-
val uncheckedType = typedAheadType(derived, AnyTypeConstructorProto).tpe.dealias
166-
val derivedType = checkClassType(uncheckedType, derived.pos, traitReq = false, stablePrefixReq = true)
168+
val originalType = typedAheadType(derived, AnyTypeConstructorProto).tpe
169+
val underlyingType = underlyingClassRef(originalType)
170+
val derivedType = checkClassType(underlyingType, derived.pos, traitReq = false, stablePrefixReq = true)
167171
val nparams = derivedType.classSymbol.typeParams.length
168172
if (nparams == 1) {
169173
val typeClass = derivedType.classSymbol
@@ -174,7 +178,7 @@ trait Deriving { this: Typer =>
174178
val instanceInfo =
175179
if (cls.typeParams.isEmpty) ExprType(resultType)
176180
else PolyType.fromParams(cls.typeParams, ImplicitMethodType(evidenceParamInfos, resultType))
177-
addDerivedInstance(derivedType.typeSymbol.name, instanceInfo, derived.pos, reportErrors = true)
181+
addDerivedInstance(originalType.typeSymbol.name, instanceInfo, derived.pos, reportErrors = true)
178182
}
179183
else
180184
ctx.error(
@@ -377,14 +381,20 @@ trait Deriving { this: Typer =>
377381
def instantiated(info: Type): Type = info match {
378382
case info: PolyType => instantiated(info.instantiate(tparamRefs))
379383
case info: MethodType => info.instantiate(params.map(_.termRef))
380-
case info => info
384+
case info => info.widenExpr
385+
}
386+
def classAndCompanionRef(tp: Type): (ClassSymbol, TermRef) = tp match {
387+
case tp @ TypeRef(prefix, _) if tp.symbol.isClass =>
388+
(tp.symbol.asClass, prefix.select(tp.symbol.companionModule).asInstanceOf[TermRef])
389+
case tp: TypeProxy =>
390+
classAndCompanionRef(tp.underlying)
381391
}
382392
val resultType = instantiated(sym.info)
383-
val typeCls = resultType.classSymbol
393+
val (typeCls, companionRef) = classAndCompanionRef(resultType)
384394
if (typeCls == defn.ShapedClass)
385395
shapedRHS(resultType)
386396
else {
387-
val module = untpd.ref(typeCls.companionModule.termRef).withPos(sym.pos)
397+
val module = untpd.ref(companionRef).withPos(sym.pos)
388398
val rhs = untpd.Select(module, nme.derived)
389399
typed(rhs, resultType)
390400
}

tests/neg/deriving-duplicate.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
object Test extends App {
2+
3+
class A {
4+
class TC1[T] {
5+
}
6+
object TC1 {
7+
def derived[T]: TC1[T] = new TC1[T]
8+
}
9+
}
10+
class A2 {
11+
class TC1[T] {
12+
}
13+
object TC1 {
14+
def derived[T]: TC1[T] = new TC1[T]
15+
}
16+
}
17+
val a = new A
18+
val a2 = new A2
19+
20+
case class D() derives a.TC1, a2.TC1 // error: duplicate typeclass derivation
21+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
a.TC1
2+
b.TC2
3+
a.TC1
4+
a2.TC1
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
object Test extends App {
2+
3+
class A {
4+
class TC1[T] {
5+
def print() = println("a.TC1")
6+
}
7+
object TC1 {
8+
def derived[T]: TC1[T] = new TC1[T]
9+
}
10+
}
11+
class A2 {
12+
class TC1[T] {
13+
def print() = println("a2.TC1")
14+
}
15+
object TC1 {
16+
def derived[T]: TC1[T] = new TC1[T]
17+
}
18+
}
19+
class B {
20+
class TC2[T] {
21+
def print() = println("b.TC2")
22+
}
23+
object TC2 {
24+
def derived[T]: TC2[T] = new TC2[T]
25+
}
26+
}
27+
val a = new A
28+
val a2 = new A2
29+
val b = new B
30+
31+
case class C() derives a.TC1, b.TC2
32+
33+
implicitly[a.TC1[C]].print()
34+
implicitly[b.TC2[C]].print()
35+
36+
type TC1a[T] = a2.TC1[T]
37+
case class D() derives a.TC1, TC1a
38+
39+
implicitly[a.TC1[D]].print()
40+
implicitly[a2.TC1[D]].print()
41+
}

0 commit comments

Comments
 (0)