Skip to content

Commit d78ba4a

Browse files
committed
Derive typeclasses with number of type parameters != 1
1 parent 058decb commit d78ba4a

File tree

3 files changed

+94
-14
lines changed

3 files changed

+94
-14
lines changed

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,33 +161,67 @@ trait Deriving { this: Typer =>
161161
*
162162
* implicit def derived$D(implicit ev_1: D[T_1], ..., ev_n: D[T_n]): D[C[Ts]] = D.derived
163163
*
164-
* See test run/typeclass-derivation2 for examples that spell out what would be generated.
165-
* Note that the name of the derived method containd the name in the derives clause, not
164+
* See the body of this method for who to generalize this to typeclasses with more
165+
* or less than one type parameter.
166+
*
167+
* See test run/typeclass-derivation2 and run/derive-multi
168+
* for examples that spell out what would be generated.
169+
*
170+
* Note that the name of the derived method contains the name in the derives clause, not
166171
* the underlying class name. This allows one to disambiguate derivations of type classes
167172
* that have the same name but different prefixes through selective aliasing.
168173
*/
169174
private def processDerivedInstance(derived: untpd.Tree): Unit = {
170175
val originalType = typedAheadType(derived, AnyTypeConstructorProto).tpe
171176
val underlyingType = underlyingClassRef(originalType)
172177
val derivedType = checkClassType(underlyingType, derived.sourcePos, traitReq = false, stablePrefixReq = true)
173-
val nparams = derivedType.classSymbol.typeParams.length
178+
val typeClass = derivedType.classSymbol
179+
val nparams = typeClass.typeParams.length
174180
if (derivedType.isRef(defn.GenericClass))
175181
derivesGeneric = true
176-
else if (nparams == 1) {
177-
val typeClass = derivedType.classSymbol
178-
val firstKindedParams = cls.typeParams.filterNot(_.info.isLambdaSub)
182+
else {
183+
// A matrix of all parameter combinations of current class parameters
184+
// and derived typeclass parameters.
185+
// Rows: parameters of current class
186+
// Columns: parameters of typeclass
187+
188+
// Running example: typeclass: class TC[X, Y, Z], deriving class: class A[T, U]
189+
// clsParamss =
190+
// T_X T_Y T_Z
191+
// U_X U_Y U_Z
192+
val clsParamss: List[List[TypeSymbol]] = cls.typeParams.map { tparam =>
193+
if (nparams == 0) Nil
194+
else if (nparams == 1) tparam :: Nil
195+
else typeClass.typeParams.map(tcparam =>
196+
tparam.copy(name = s"${tparam.name}_${tcparam.name}".toTypeName)
197+
.asInstanceOf[TypeSymbol])
198+
}
199+
val firstKindedParamss = clsParamss.filter {
200+
case param :: _ => !param.info.isLambdaSub
201+
case nil => false
202+
}
203+
204+
// The types of the required evidence parameters. In the running example:
205+
// TC[T_X, T_Y, T_Z], TC[U_X, U_Y, U_Z]
179206
val evidenceParamInfos =
180-
for (param <- firstKindedParams) yield derivedType.appliedTo(param.typeRef)
181-
val resultType = derivedType.appliedTo(cls.appliedRef)
207+
for (row <- firstKindedParamss)
208+
yield derivedType.appliedTo(row.map(_.typeRef))
209+
210+
// The class instances in the result type. Running example:
211+
// A[T_X, U_X], A[T_Y, U_Y], A[T_Z, U_Z]
212+
val resultInstances =
213+
for (n <- List.range(0, nparams))
214+
yield cls.typeRef.appliedTo(clsParamss.map(row => row(n).typeRef))
215+
216+
// TC[A[T_X, U_X], A[T_Y, U_Y], A[T_Z, U_Z]]
217+
val resultType = derivedType.appliedTo(resultInstances)
218+
219+
val clsParams: List[TypeSymbol] = clsParamss.flatten
182220
val instanceInfo =
183-
if (cls.typeParams.isEmpty) ExprType(resultType)
184-
else PolyType.fromParams(cls.typeParams, ImplicitMethodType(evidenceParamInfos, resultType))
221+
if (clsParams.isEmpty) ExprType(resultType)
222+
else PolyType.fromParams(clsParams, ImplicitMethodType(evidenceParamInfos, resultType))
185223
addDerivedInstance(originalType.typeSymbol.name, instanceInfo, derived.sourcePos, reportErrors = true)
186224
}
187-
else
188-
ctx.error(
189-
i"derived class $derivedType should have one type paramater but has $nparams",
190-
derived.sourcePos)
191225
}
192226

193227
/** Add value corresponding to `val genericClass = new GenericClass(...)`

tests/run/derive-multi.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
derived: A
2+
derived: B[One, Two]
3+
derived: B
4+
derived: B
5+
derived: B

tests/run/derive-multi.scala

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
class A
2+
object A {
3+
def derived: A = {
4+
println("derived: A")
5+
new A
6+
}
7+
}
8+
9+
class B[X, Y]
10+
object B {
11+
def derived[X, Y]: B[X, Y] = {
12+
println("derived: B")
13+
new B[X, Y]
14+
}
15+
}
16+
17+
case class One() derives A, B
18+
case class Two() derives A, B
19+
20+
implied for B[One, Two] {
21+
println("derived: B[One, Two]")
22+
}
23+
24+
enum Lst[T] derives A, B {
25+
case Cons(x: T, xs: Lst[T])
26+
case Nil()
27+
}
28+
29+
case class Triple[S, T, U] derives A, B
30+
31+
object Test1 {
32+
import Lst._
33+
implicitly[A]
34+
}
35+
36+
object Test extends App {
37+
Test1
38+
implicitly[B[Lst[Lst[One]], Lst[Lst[Two]]]]
39+
implicitly[B[Triple[One, One, One],
40+
Triple[Two, Two, Two]]]
41+
}

0 commit comments

Comments
 (0)