Skip to content

Commit 4aa59eb

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 7189f46 commit 4aa59eb

File tree

6 files changed

+216
-53
lines changed

6 files changed

+216
-53
lines changed

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

+42-31
Original file line numberDiff line numberDiff line change
@@ -504,53 +504,64 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
504504
/** The class
505505
*
506506
* ```
507-
* case class C[T <: U](x: T, y: String*)
507+
* trait U:
508+
* type Elem
509+
*
510+
* case class C[T <: U](a: T, b: a.Elem, c: String*)
508511
* ```
509512
*
510513
* gets the `fromProduct` method:
511514
*
512515
* ```
513516
* def fromProduct(x$0: Product): MirroredMonoType =
514-
* new C[U](
515-
* x$0.productElement(0).asInstanceOf[U],
516-
* x$0.productElement(1).asInstanceOf[Seq[String]]: _*)
517+
* val a$1 = x$0.productElement(0).asInstanceOf[U]
518+
* val b$1 = x$0.productElement(1).asInstanceOf[a$1.Elem]
519+
* val c$1 = x$0.productElement(2).asInstanceOf[Seq[String]]
520+
* new C[U](a$1, b$1, c$1*)
517521
* ```
518522
* where
519523
* ```
520524
* type MirroredMonoType = C[?]
521525
* ```
522526
*/
523-
def fromProductBody(caseClass: Symbol, param: Tree, optInfo: Option[MirrorImpl.OfProduct])(using Context): Tree =
524-
def extractParams(tpe: Type): List[Type] =
525-
tpe.asInstanceOf[MethodType].paramInfos
526-
527-
def computeFromCaseClass: (Type, List[Type]) =
528-
val (baseRef, baseInfo) =
529-
val rawRef = caseClass.typeRef
530-
val rawInfo = caseClass.primaryConstructor.info
531-
optInfo match
532-
case Some(info) =>
533-
(rawRef.asSeenFrom(info.pre, caseClass.owner), rawInfo.asSeenFrom(info.pre, caseClass.owner))
534-
case _ =>
535-
(rawRef, rawInfo)
536-
baseInfo match
527+
def fromProductBody(caseClass: Symbol, productParam: Tree, optInfo: Option[MirrorImpl.OfProduct])(using Context): Tree =
528+
val classRef = optInfo match
529+
case Some(info) => TypeRef(info.pre, caseClass)
530+
case _ => caseClass.typeRef
531+
val (newPrefix, constrMeth) =
532+
val constr = TermRef(classRef, caseClass.primaryConstructor)
533+
(constr.info: @unchecked) match
537534
case tl: PolyType =>
538535
val tvars = constrained(tl)
539536
val targs = for tvar <- tvars yield
540537
tvar.instantiate(fromBelow = false)
541-
(baseRef.appliedTo(targs), extractParams(tl.instantiate(targs)))
542-
case methTpe =>
543-
(baseRef, extractParams(methTpe))
544-
end computeFromCaseClass
545-
546-
val (classRefApplied, paramInfos) = computeFromCaseClass
547-
val elems =
548-
for ((formal, idx) <- paramInfos.zipWithIndex) yield
549-
val elem =
550-
param.select(defn.Product_productElement).appliedTo(Literal(Constant(idx)))
551-
.ensureConforms(formal.translateFromRepeated(toArray = false))
552-
if (formal.isRepeatedParam) ctx.typer.seqToRepeated(elem) else elem
553-
New(classRefApplied, elems)
538+
(AppliedType(classRef, targs), tl.instantiate(targs).asInstanceOf[MethodType])
539+
case mt: MethodType =>
540+
(classRef, mt)
541+
542+
// Create symbols for the vals corresponding to each parameter
543+
// If there are dependent parameters, the infos won't be correct yet.
544+
val bindingSyms = constrMeth.paramRefs.map: pref =>
545+
newSymbol(ctx.owner, pref.paramName.freshened, Synthetic,
546+
pref.underlying.translateFromRepeated(toArray = false), coord = ctx.owner.span.focus)
547+
val bindingRefs = bindingSyms.map(TermRef(NoPrefix, _))
548+
// Fix the infos for dependent parameters
549+
if constrMeth.isParamDependent then
550+
bindingSyms.foreach: bindingSym =>
551+
bindingSym.info = bindingSym.info.substParams(constrMeth, bindingRefs)
552+
553+
val bindingDefs = bindingSyms.zipWithIndex.map: (bindingSym, idx) =>
554+
ValDef(bindingSym,
555+
productParam.select(defn.Product_productElement).appliedTo(Literal(Constant(idx)))
556+
.ensureConforms(bindingSym.info))
557+
558+
val newArgs = bindingRefs.lazyZip(constrMeth.paramInfos).map: (bindingRef, paramInfo) =>
559+
val refTree = ref(bindingRef)
560+
if paramInfo.isRepeatedParam then ctx.typer.seqToRepeated(refTree) else refTree
561+
Block(
562+
bindingDefs,
563+
New(newPrefix, newArgs)
564+
)
554565
end fromProductBody
555566

556567
/** For an enum T:

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

+1-13
Original file line numberDiff line numberDiff line change
@@ -1957,9 +1957,7 @@ class Namer { typer: Typer =>
19571957
if isConstructor then
19581958
// set result type tree to unit, but take the current class as result type of the symbol
19591959
typedAheadType(ddef.tpt, defn.UnitType)
1960-
val mt = wrapMethType(effectiveResultType(sym, paramSymss))
1961-
if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner)
1962-
mt
1960+
wrapMethType(effectiveResultType(sym, paramSymss))
19631961
else
19641962
val paramFn = if Feature.enabled(Feature.modularity) && sym.isAllOf(Given | Method) then wrapRefinedMethType else wrapMethType
19651963
valOrDefDefSig(ddef, sym, paramSymss, paramFn)
@@ -2001,16 +1999,6 @@ class Namer { typer: Typer =>
20011999
ddef.trailingParamss.foreach(completeParams)
20022000
end completeTrailingParamss
20032001

2004-
/** Checks an implementation restriction on case classes. */
2005-
def checkCaseClassParamDependencies(mt: Type, cls: Symbol)(using Context): Unit =
2006-
mt.stripPoly match
2007-
case mt: MethodType if cls.is(Case) && mt.isParamDependent =>
2008-
// See issue #8073 for background
2009-
report.error(
2010-
em"""Implementation restriction: case classes cannot have dependencies between parameters""",
2011-
cls.srcPos)
2012-
case _ =>
2013-
20142002
private def setParamTrackedWithAccessors(psym: Symbol, ownerTpe: Type)(using Context): Unit =
20152003
for acc <- ownerTpe.decls.lookupAll(psym.name) if acc.is(ParamAccessor) do
20162004
acc.resetFlag(PrivateLocal)

tests/neg/i8069.scala

-8
This file was deleted.

tests/run-macros/tasty-extractors-2.check

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit")
4949
Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("<init>", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil)), None, List(DefDef("a", Nil, Inferred(), Some(Literal(IntConstant(0))))))), Literal(UnitConstant())))
5050
TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit")
5151

52-
Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("<init>", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), None, List(DefDef("hashCode", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_hashCode"), List(This(Some("Foo")))))), DefDef("equals", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(Apply(Select(This(Some("Foo")), "eq"), List(TypeApply(Select(Ident("x$0"), "$asInstanceOf$"), List(Inferred())))), "||"), List(Match(Ident("x$0"), List(CaseDef(Bind("x$0", Typed(Wildcard(), Inferred())), None, Apply(Select(Literal(BooleanConstant(true)), "&&"), List(Apply(Select(Ident("x$0"), "canEqual"), List(This(Some("Foo"))))))), CaseDef(Wildcard(), None, Literal(BooleanConstant(false))))))))), DefDef("toString", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_toString"), List(This(Some("Foo")))))), DefDef("canEqual", List(TermParamClause(List(ValDef("that", Inferred(), None)))), Inferred(), Some(TypeApply(Select(Ident("that"), "isInstanceOf"), List(Inferred())))), DefDef("productArity", Nil, Inferred(), Some(Literal(IntConstant(0)))), DefDef("productPrefix", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), DefDef("productElement", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), "<init>"), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), "<init>"), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("copy", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), "<init>"), Nil))), ClassDef("Foo$", DefDef("<init>", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil), Inferred()), Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), Nil))), DefDef("unapply", List(TermParamClause(List(ValDef("x$1", Inferred(), None)))), Singleton(Literal(BooleanConstant(true))), Some(Literal(BooleanConstant(true)))), DefDef("toString", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), TypeDef("MirroredMonoType", TypeBoundsTree(Inferred(), Inferred())), DefDef("fromProduct", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), Nil)))))), Literal(UnitConstant())))
52+
Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("<init>", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), None, List(DefDef("hashCode", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_hashCode"), List(This(Some("Foo")))))), DefDef("equals", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(Apply(Select(This(Some("Foo")), "eq"), List(TypeApply(Select(Ident("x$0"), "$asInstanceOf$"), List(Inferred())))), "||"), List(Match(Ident("x$0"), List(CaseDef(Bind("x$0", Typed(Wildcard(), Inferred())), None, Apply(Select(Literal(BooleanConstant(true)), "&&"), List(Apply(Select(Ident("x$0"), "canEqual"), List(This(Some("Foo"))))))), CaseDef(Wildcard(), None, Literal(BooleanConstant(false))))))))), DefDef("toString", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_toString"), List(This(Some("Foo")))))), DefDef("canEqual", List(TermParamClause(List(ValDef("that", Inferred(), None)))), Inferred(), Some(TypeApply(Select(Ident("that"), "isInstanceOf"), List(Inferred())))), DefDef("productArity", Nil, Inferred(), Some(Literal(IntConstant(0)))), DefDef("productPrefix", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), DefDef("productElement", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), "<init>"), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), "<init>"), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("copy", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), "<init>"), Nil))), ClassDef("Foo$", DefDef("<init>", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil), Inferred()), Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), Nil))), DefDef("unapply", List(TermParamClause(List(ValDef("x$1", Inferred(), None)))), Singleton(Literal(BooleanConstant(true))), Some(Literal(BooleanConstant(true)))), DefDef("toString", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), TypeDef("MirroredMonoType", TypeBoundsTree(Inferred(), Inferred())), DefDef("fromProduct", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Block(Nil, Apply(Select(New(Inferred()), "<init>"), Nil))))))), Literal(UnitConstant())))
5353
TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit")
5454

5555
Inlined(None, Nil, Block(List(ClassDef("Foo1", DefDef("<init>", List(TermParamClause(List(ValDef("a", TypeIdent("Int"), None)))), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil)), None, List(ValDef("a", Inferred(), None)))), Literal(UnitConstant())))

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)