Skip to content

Commit 16cc49b

Browse files
committed
Fix #2663: More refined handling of enum case apply results
i2663 demonstrates that we do not always have a complete result type for the `apply` method of an enum case. In that case our only recourse is to add a generic widening method that upcasts the enum case class to the enum base class.
1 parent b58799e commit 16cc49b

File tree

3 files changed

+71
-14
lines changed

3 files changed

+71
-14
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+11-14
Original file line numberDiff line numberDiff line change
@@ -456,19 +456,14 @@ object desugar {
456456
// For all other classes, the parent is AnyRef.
457457
val companions =
458458
if (isCaseClass) {
459-
def extractType(t: Tree): Tree = t match {
460-
case Apply(t1, _) => extractType(t1)
461-
case TypeApply(t1, ts) => AppliedTypeTree(extractType(t1), ts)
462-
case Select(t1, nme.CONSTRUCTOR) => extractType(t1)
463-
case New(t1) => t1
464-
case t1 => t1
465-
}
466459
// The return type of the `apply` method
467-
val applyResultTpt =
468-
if (isEnumCase)
469-
if (parents.isEmpty) enumClassTypeRef
470-
else parents.map(extractType).reduceLeft(AndTypeTree)
471-
else TypeTree()
460+
val (applyResultTpt, widenDefs) =
461+
if (!isEnumCase)
462+
(TypeTree(), Nil)
463+
else if (parents.isEmpty || derivedTparams.isEmpty || enumClass.typeParams.isEmpty)
464+
(enumClassTypeRef, Nil)
465+
else
466+
enumApplyResult(cdef, parents, derivedTparams, appliedRef(enumClassRef, derivedTparams))
472467

473468
val parent =
474469
if (constrTparams.nonEmpty ||
@@ -479,11 +474,13 @@ object desugar {
479474
// todo: also use anyRef if constructor has a dependent method type (or rule that out)!
480475
(constrVparamss :\ (if (isEnumCase) applyResultTpt else classTypeRef)) (
481476
(vparams, restpe) => Function(vparams map (_.tpt), restpe))
477+
def widenedCreatorExpr =
478+
(creatorExpr /: widenDefs)((rhs, meth) => Apply(Ident(meth.name), rhs :: Nil))
482479
val applyMeths =
483480
if (mods is Abstract) Nil
484481
else
485-
DefDef(nme.apply, derivedTparams, derivedVparamss, applyResultTpt, creatorExpr)
486-
.withFlags(Synthetic | (constr1.mods.flags & DefaultParameterized)) :: Nil
482+
DefDef(nme.apply, derivedTparams, derivedVparamss, applyResultTpt, widenedCreatorExpr)
483+
.withFlags(Synthetic | (constr1.mods.flags & DefaultParameterized)) :: widenDefs
487484
val unapplyMeth = {
488485
val unapplyParam = makeSyntheticParameter(tpt = classTypeRef)
489486
val unapplyRHS = if (arity == 0) Literal(Constant(true)) else Ident(unapplyParam.name)

compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala

+46
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,52 @@ object DesugarEnums {
120120
TypeTree(), creator)
121121
}
122122

123+
/** The return type of an enum case apply method and any widening methods in which
124+
* the apply's right hand side will be wrapped. For parents of the form
125+
*
126+
* extends E(args) with T1(args1) with ... TN(argsN)
127+
*
128+
* and type parameters `tparams` the generated widen method is
129+
*
130+
* def C$to$E[tparams](x$1: E[tparams] with T1 with ... TN) = x$1
131+
*
132+
* @param cdef The case definition
133+
* @param parents The declared parents of the enum case
134+
* @param tparams The type parameters of the enum case
135+
* @param appliedEnumRef The enum class applied to `tparams`.
136+
*/
137+
def enumApplyResult(
138+
cdef: TypeDef,
139+
parents: List[Tree],
140+
tparams: List[TypeDef],
141+
appliedEnumRef: Tree)(implicit ctx: Context): (Tree, List[DefDef]) = {
142+
143+
def extractType(t: Tree): Tree = t match {
144+
case Apply(t1, _) => extractType(t1)
145+
case TypeApply(t1, ts) => AppliedTypeTree(extractType(t1), ts)
146+
case Select(t1, nme.CONSTRUCTOR) => extractType(t1)
147+
case New(t1) => t1
148+
case t1 => t1
149+
}
150+
151+
val parentTypes = parents.map(extractType)
152+
parentTypes.head match {
153+
case parent: RefTree if parent.name == enumClass.name =>
154+
// need a widen method to compute correct type parameters for enum base class
155+
val widenParamType = (appliedEnumRef /: parentTypes.tail)(AndTypeTree)
156+
val widenParam = makeSyntheticParameter(tpt = widenParamType)
157+
val widenDef = DefDef(
158+
name = s"${cdef.name}$$to$$${enumClass.name}".toTermName,
159+
tparams = tparams,
160+
vparamss = (widenParam :: Nil) :: Nil,
161+
tpt = TypeTree(),
162+
rhs = Ident(widenParam.name))
163+
(TypeTree(), widenDef :: Nil)
164+
case _ =>
165+
(parentTypes.reduceLeft(AndTypeTree), Nil)
166+
}
167+
}
168+
123169
/** A pair consisting of
124170
* - the next enum tag
125171
* - scaffolding containing the necessary definitions for singleton enum cases

tests/pos/i2663.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
trait Tr
2+
enum Foo[T](x: T) {
3+
case Bar[T](y: T) extends Foo(y)
4+
case Bas[T](y: Int) extends Foo(y)
5+
case Bam[T](y: String) extends Foo(y) with Tr
6+
}
7+
object Test {
8+
import Foo._
9+
val bar: Foo[Boolean] = Bar(true)
10+
val bas: Foo[Int] = Bas(1)
11+
val bam: Foo[String] & Tr = Bam("")
12+
}
13+
14+

0 commit comments

Comments
 (0)