Skip to content

Commit 83c5e98

Browse files
committed
Minimal support for dependent case classes
This lets us write: trait A: type B case class CC(a: A, b: a.B) Pattern matching works but isn't dependent yet: x match case CC(a, b) => val a1: A = a // Dependent pattern matching is not currently supported // val b1: a1.B = b val b1 = b // Type is CC#a.B (for my usecase this isn't a problem, I'm working on a type constraint API which lets me write things like `case class CC(a: Int, b: Int GreaterThan[a.type])`) Because case class pattern matching relies on the product selectors `_N`, making it dependent is a bit tricky, currently we generate: case class CC(a: A, b: a.B): def _1: A = a def _2: a.B = b So the type of `_2` is not obviously related to the type of `_1`, we probably need to change what we generate into: case class CC(a: A, b: a.B): @uncheckedStable def _1: a.type = a def _2: _1.B = b But this can be done in a separate PR. Fixes #8073.
1 parent 9d1f329 commit 83c5e98

File tree

4 files changed

+215
-44
lines changed

4 files changed

+215
-44
lines changed

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

+42-31
Original file line numberDiff line numberDiff line change
@@ -498,53 +498,64 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
498498
/** The class
499499
*
500500
* ```
501-
* case class C[T <: U](x: T, y: String*)
501+
* trait U:
502+
* type Elem
503+
*
504+
* case class C[T <: U](a: T, b: a.Elem, c: String*)
502505
* ```
503506
*
504507
* gets the `fromProduct` method:
505508
*
506509
* ```
507510
* def fromProduct(x$0: Product): MirroredMonoType =
508-
* new C[U](
509-
* x$0.productElement(0).asInstanceOf[U],
510-
* x$0.productElement(1).asInstanceOf[Seq[String]]: _*)
511+
* val a$1 = x$0.productElement(0).asInstanceOf[U]
512+
* val b$1 = x$0.productElement(1).asInstanceOf[a$1.Elem]
513+
* val c$1 = x$0.productElement(2).asInstanceOf[Seq[String]]
514+
* new C[U](a$1, b$1, c$1*)
511515
* ```
512516
* where
513517
* ```
514518
* type MirroredMonoType = C[?]
515519
* ```
516520
*/
517-
def fromProductBody(caseClass: Symbol, param: Tree, optInfo: Option[MirrorImpl.OfProduct])(using Context): Tree =
518-
def extractParams(tpe: Type): List[Type] =
519-
tpe.asInstanceOf[MethodType].paramInfos
520-
521-
def computeFromCaseClass: (Type, List[Type]) =
522-
val (baseRef, baseInfo) =
523-
val rawRef = caseClass.typeRef
524-
val rawInfo = caseClass.primaryConstructor.info
525-
optInfo match
526-
case Some(info) =>
527-
(rawRef.asSeenFrom(info.pre, caseClass.owner), rawInfo.asSeenFrom(info.pre, caseClass.owner))
528-
case _ =>
529-
(rawRef, rawInfo)
530-
baseInfo match
521+
def fromProductBody(caseClass: Symbol, productParam: Tree, optInfo: Option[MirrorImpl.OfProduct])(using Context): Tree =
522+
val classRef = optInfo match
523+
case Some(info) => TypeRef(info.pre, caseClass)
524+
case _ => caseClass.typeRef
525+
val (newPrefix, constrMeth) =
526+
val constr = TermRef(classRef, caseClass.primaryConstructor)
527+
(constr.info: @unchecked) match
531528
case tl: PolyType =>
532529
val tvars = constrained(tl)
533530
val targs = for tvar <- tvars yield
534531
tvar.instantiate(fromBelow = false)
535-
(baseRef.appliedTo(targs), extractParams(tl.instantiate(targs)))
536-
case methTpe =>
537-
(baseRef, extractParams(methTpe))
538-
end computeFromCaseClass
539-
540-
val (classRefApplied, paramInfos) = computeFromCaseClass
541-
val elems =
542-
for ((formal, idx) <- paramInfos.zipWithIndex) yield
543-
val elem =
544-
param.select(defn.Product_productElement).appliedTo(Literal(Constant(idx)))
545-
.ensureConforms(formal.translateFromRepeated(toArray = false))
546-
if (formal.isRepeatedParam) ctx.typer.seqToRepeated(elem) else elem
547-
New(classRefApplied, elems)
532+
(AppliedType(classRef, targs), tl.instantiate(targs).asInstanceOf[MethodType])
533+
case mt: MethodType =>
534+
(classRef, mt)
535+
536+
// Create symbols for the vals corresponding to each parameter
537+
// If there are dependent parameters, the infos won't be correct yet.
538+
val bindingSyms = constrMeth.paramRefs.map: pref =>
539+
newSymbol(ctx.owner, pref.paramName.freshened, Synthetic,
540+
pref.underlying.translateFromRepeated(toArray = false), coord = caseClass.coord)
541+
val bindingRefs = bindingSyms.map(TermRef(NoPrefix, _))
542+
// Fix the info for dependent parameters
543+
if constrMeth.isParamDependent then
544+
bindingSyms.foreach: bindingSym =>
545+
bindingSym.info = bindingSym.info.substParams(constrMeth, bindingRefs)
546+
547+
val bindingDefs = bindingSyms.zipWithIndex.map: (bindingSym, idx) =>
548+
ValDef(bindingSym,
549+
productParam.select(defn.Product_productElement).appliedTo(Literal(Constant(idx)))
550+
.ensureConforms(bindingSym.info))
551+
552+
val newArgs = bindingRefs.lazyZip(constrMeth.paramInfos).map: (bindingRef, paramInfo) =>
553+
val refTree = ref(bindingRef)
554+
if paramInfo.isRepeatedParam then ctx.typer.seqToRepeated(refTree) else refTree
555+
Block(
556+
bindingDefs,
557+
New(newPrefix, newArgs)
558+
)
548559
end fromProductBody
549560

550561
/** For an enum T:

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

+1-13
Original file line numberDiff line numberDiff line change
@@ -1925,9 +1925,7 @@ class Namer { typer: Typer =>
19251925
if isConstructor then
19261926
// set result type tree to unit, but take the current class as result type of the symbol
19271927
typedAheadType(ddef.tpt, defn.UnitType)
1928-
val mt = wrapMethType(effectiveResultType(sym, paramSymss))
1929-
if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner)
1930-
mt
1928+
wrapMethType(effectiveResultType(sym, paramSymss))
19311929
else if sym.isAllOf(Given | Method) && Feature.enabled(modularity) then
19321930
// set every context bound evidence parameter of a given companion method
19331931
// to be tracked, provided it has a type that has an abstract type member.
@@ -1976,16 +1974,6 @@ class Namer { typer: Typer =>
19761974
ddef.trailingParamss.foreach(completeParams)
19771975
end completeTrailingParamss
19781976

1979-
/** Checks an implementation restriction on case classes. */
1980-
def checkCaseClassParamDependencies(mt: Type, cls: Symbol)(using Context): Unit =
1981-
mt.stripPoly match
1982-
case mt: MethodType if cls.is(Case) && mt.isParamDependent =>
1983-
// See issue #8073 for background
1984-
report.error(
1985-
em"""Implementation restriction: case classes cannot have dependencies between parameters""",
1986-
cls.srcPos)
1987-
case _ =>
1988-
19891977
/** Under x.modularity, we add `tracked` to context bound witnesses
19901978
* that have abstract type members
19911979
*/

tests/run/i8073.scala

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import scala.deriving.Mirror
2+
3+
trait A:
4+
type B
5+
6+
object Test:
7+
case class CC(a: A, b: a.B)
8+
9+
def test1(): Unit =
10+
val generic = summon[Mirror.Of[CC]]
11+
// No language syntax for type projection of a singleton type
12+
// summon[generic.MirroredElemTypes =:= (A, CC#a.B)]
13+
14+
val aa: A { type B = Int } = new A { type B = Int }
15+
val x: CC { val a: aa.type } = CC(aa, 1).asInstanceOf[CC { val a: aa.type }] // manual `tracked`
16+
17+
val dependent = summon[Mirror.Of[x.type]]
18+
summon[dependent.MirroredElemTypes =:= (A, x.a.B)]
19+
20+
assert(CC(aa, 1) == generic.fromProduct((aa, 1)))
21+
assert(CC(aa, 1) == dependent.fromProduct((aa, 1)))
22+
23+
x match
24+
case CC(a, b) =>
25+
val a1: A = a
26+
// Dependent pattern matching is not currently supported
27+
// val b1: a1.B = b
28+
val b1 = b // Type is CC#a.B
29+
30+
end test1
31+
32+
case class CCPoly[T <: A](a: T, b: a.B)
33+
34+
def test2(): Unit =
35+
val generic = summon[Mirror.Of[CCPoly[A]]]
36+
// No language syntax for type projection of a singleton type
37+
// summon[generic.MirroredElemTypes =:= (A, CCPoly[A]#a.B)]
38+
39+
val aa: A { type B = Int } = new A { type B = Int }
40+
val x: CCPoly[aa.type] = CCPoly(aa, 1)
41+
42+
val dependent = summon[Mirror.Of[x.type]]
43+
summon[dependent.MirroredElemTypes =:= (aa.type, x.a.B)]
44+
45+
assert(CCPoly[A](aa, 1) == generic.fromProduct((aa, 1)))
46+
assert(CCPoly[A](aa, 1) == dependent.fromProduct((aa, 1)))
47+
48+
x match
49+
case CCPoly(a, b) =>
50+
val a1: A = a
51+
// Dependent pattern matching is not currently supported
52+
// val b1: a1.B = b
53+
val b1 = b // Type is CC#a.B
54+
55+
end test2
56+
57+
enum Enum:
58+
case EC(a: A, b: a.B)
59+
60+
def test3(): Unit =
61+
val generic = summon[Mirror.Of[Enum.EC]]
62+
// No language syntax for type projection of a singleton type
63+
// summon[generic.MirroredElemTypes =:= (A, Enum.EC#a.B)]
64+
65+
val aa: A { type B = Int } = new A { type B = Int }
66+
val x: Enum.EC { val a: aa.type } = Enum.EC(aa, 1).asInstanceOf[Enum.EC { val a: aa.type }] // manual `tracked`
67+
68+
val dependent = summon[Mirror.Of[x.type]]
69+
summon[dependent.MirroredElemTypes =:= (A, x.a.B)]
70+
71+
assert(Enum.EC(aa, 1) == generic.fromProduct((aa, 1)))
72+
assert(Enum.EC(aa, 1) == dependent.fromProduct((aa, 1)))
73+
74+
x match
75+
case Enum.EC(a, b) =>
76+
val a1: A = a
77+
// Dependent pattern matching is not currently supported
78+
// val b1: a1.B = b
79+
val b1 = b // Type is Enum.EC#a.B
80+
81+
end test3
82+
83+
def main(args: Array[String]): Unit =
84+
test1()
85+
test2()
86+
test3()

tests/run/i8073b.scala

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import scala.deriving.Mirror
2+
3+
trait A:
4+
type B
5+
6+
// Test local mirrors
7+
@main def Test =
8+
case class CC(a: A, b: a.B)
9+
10+
def test1(): Unit =
11+
val generic = summon[Mirror.Of[CC]]
12+
// No language syntax for type projection of a singleton type
13+
// summon[generic.MirroredElemTypes =:= (A, CC#a.B)]
14+
15+
val aa: A { type B = Int } = new A { type B = Int }
16+
val x: CC { val a: aa.type } = CC(aa, 1).asInstanceOf[CC { val a: aa.type }] // manual `tracked`
17+
18+
val dependent = summon[Mirror.Of[x.type]]
19+
summon[dependent.MirroredElemTypes =:= (A, x.a.B)]
20+
21+
assert(CC(aa, 1) == generic.fromProduct((aa, 1)))
22+
assert(CC(aa, 1) == dependent.fromProduct((aa, 1)))
23+
24+
x match
25+
case CC(a, b) =>
26+
val a1: A = a
27+
// Dependent pattern matching is not currently supported
28+
// val b1: a1.B = b
29+
val b1 = b // Type is CC#a.B
30+
31+
end test1
32+
33+
case class CCPoly[T <: A](a: T, b: a.B)
34+
35+
def test2(): Unit =
36+
val generic = summon[Mirror.Of[CCPoly[A]]]
37+
// No language syntax for type projection of a singleton type
38+
// summon[generic.MirroredElemTypes =:= (A, CCPoly[A]#a.B)]
39+
40+
val aa: A { type B = Int } = new A { type B = Int }
41+
val x: CCPoly[aa.type] = CCPoly(aa, 1)
42+
43+
val dependent = summon[Mirror.Of[x.type]]
44+
summon[dependent.MirroredElemTypes =:= (aa.type, x.a.B)]
45+
46+
assert(CCPoly[A](aa, 1) == generic.fromProduct((aa, 1)))
47+
assert(CCPoly[A](aa, 1) == dependent.fromProduct((aa, 1)))
48+
49+
x match
50+
case CCPoly(a, b) =>
51+
val a1: A = a
52+
// Dependent pattern matching is not currently supported
53+
// val b1: a1.B = b
54+
val b1 = b // Type is CC#a.B
55+
56+
end test2
57+
58+
enum Enum:
59+
case EC(a: A, b: a.B)
60+
61+
def test3(): Unit =
62+
val generic = summon[Mirror.Of[Enum.EC]]
63+
// No language syntax for type projection of a singleton type
64+
// summon[generic.MirroredElemTypes =:= (A, Enum.EC#a.B)]
65+
66+
val aa: A { type B = Int } = new A { type B = Int }
67+
val x: Enum.EC { val a: aa.type } = Enum.EC(aa, 1).asInstanceOf[Enum.EC { val a: aa.type }] // manual `tracked`
68+
69+
val dependent = summon[Mirror.Of[x.type]]
70+
summon[dependent.MirroredElemTypes =:= (A, x.a.B)]
71+
72+
assert(Enum.EC(aa, 1) == generic.fromProduct((aa, 1)))
73+
assert(Enum.EC(aa, 1) == dependent.fromProduct((aa, 1)))
74+
75+
x match
76+
case Enum.EC(a, b) =>
77+
val a1: A = a
78+
// Dependent pattern matching is not currently supported
79+
// val b1: a1.B = b
80+
val b1 = b // Type is Enum.EC#a.B
81+
82+
end test3
83+
84+
test1()
85+
test2()
86+
test3()

0 commit comments

Comments
 (0)