Skip to content

Commit 54b8b51

Browse files
Case class desugaring extend Product & NameBasedPattern (not ProductN)
Ignoring compatibility, I would say we could entirely remove the `Product` superclass. Replacing `ProductN` with `Product` require synthesizing `productArity` and `productElement` methods, which is done here directly in desugaring. To be able to complie scala.TupleN in their current definition we Unfortunately need to synthesize these new methods with override, which is less than ideal given then user defined productArity / productElement methods would be silently ignored.
1 parent f5af569 commit 54b8b51

File tree

2 files changed

+53
-32
lines changed

2 files changed

+53
-32
lines changed

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

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,8 @@ object desugar {
287287
case _ => false
288288
}
289289

290-
val isCaseClass = mods.is(Case) && !mods.is(Module)
290+
val isCaseClass = mods.is(Case) && !mods.is(Module)
291+
val isCaseObject = mods.is(Case) && mods.is(Module)
291292
val isValueClass = parents.nonEmpty && isAnyVal(parents.head)
292293
// This is not watertight, but `extends AnyVal` will be replaced by `inline` later.
293294

@@ -332,8 +333,8 @@ object desugar {
332333
lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef)
333334

334335
// Methods to add to a case class C[..](p1: T1, ..., pN: Tn)(moreParams)
335-
// def isDefined = true
336336
// def productArity = N
337+
// def productElement(i: Int): Any = i match { ... }
337338
// def _1 = this.p1
338339
// ...
339340
// def _N = this.pN
@@ -344,14 +345,35 @@ object desugar {
344345
// Note: copy default parameters need @uncheckedVariance; see
345346
// neg/t1843-variances.scala for a test case. The test would give
346347
// two errors without @uncheckedVariance, one of them spurious.
347-
val caseClassMeths =
348-
if (isCaseClass) {
349-
def syntheticProperty(name: TermName, rhs: Tree) =
350-
DefDef(name, Nil, Nil, TypeTree(), rhs).withMods(synthetic)
348+
val caseClassMeths = {
349+
def syntheticProperty(name: TermName, rhs: Tree) =
350+
DefDef(name, Nil, Nil, TypeTree(), rhs).withMods(synthetic)
351+
// The override here is less than ideal: user defined productArity / productElement
352+
// methods would be silently ignored. This is necessary to compile `scala.TupleN`.
353+
// The long term solution is to remove `ProductN` entirely from stdlib.
354+
def productArity =
355+
DefDef(nme.productArity, Nil, Nil, TypeTree(), Literal(Constant(arity)))
356+
.withMods(Modifiers(Synthetic | Override))
357+
def productElement = {
358+
val param = makeSyntheticParameter(tpt = ref(defn.IntType))
359+
// case N => _${N + 1}
360+
val cases = 0.until(arity).map { i =>
361+
CaseDef(Literal(Constant(i)), EmptyTree, Select(This(EmptyTypeIdent), nme.selectorName(i)))
362+
}
363+
val ioob = ref(defn.IndexOutOfBoundsException.typeRef)
364+
val error = Throw(New(ioob, List(List(Select(refOfDef(param), nme.toString_)))))
365+
// case _ => throw new IndexOutOfBoundsException(i.toString)
366+
val defaultCase = CaseDef(untpd.Ident(nme.WILDCARD), EmptyTree, error)
367+
val body = Match(refOfDef(param), (cases :+ defaultCase).toList)
368+
DefDef(nme.productElement, Nil, List(List(param)), TypeTree(defn.AnyType), body)
369+
.withMods(Modifiers(Synthetic | Override))
370+
}
371+
def productElemMeths = {
351372
val caseParams = constrVparamss.head.toArray
352-
val productElemMeths =
353-
for (i <- 0 until arity if nme.selectorName(i) `ne` caseParams(i).name)
354-
yield syntheticProperty(nme.selectorName(i), Select(This(EmptyTypeIdent), caseParams(i).name))
373+
for (i <- 0 until arity if nme.selectorName(i) `ne` caseParams(i).name)
374+
yield syntheticProperty(nme.selectorName(i), Select(This(EmptyTypeIdent), caseParams(i).name))
375+
}
376+
def copyMeths = {
355377
def isRepeated(tree: Tree): Boolean = tree match {
356378
case PostfixOp(_, nme.raw.STAR) => true
357379
case ByNameTypeTree(tree1) => isRepeated(tree1)
@@ -361,34 +383,32 @@ object desugar {
361383
case ValDef(_, tpt, _) => isRepeated(tpt)
362384
case _ => false
363385
})
364-
365-
val copyMeths =
366-
if (mods.is(Abstract) || hasRepeatedParam) Nil // cannot have default arguments for repeated parameters, hence copy method is not issued
367-
else {
368-
def copyDefault(vparam: ValDef) =
369-
makeAnnotated("scala.annotation.unchecked.uncheckedVariance", refOfDef(vparam))
370-
val copyFirstParams = derivedVparamss.head.map(vparam =>
371-
cpy.ValDef(vparam)(rhs = copyDefault(vparam)))
372-
val copyRestParamss = derivedVparamss.tail.nestedMap(vparam =>
373-
cpy.ValDef(vparam)(rhs = EmptyTree))
374-
DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr)
375-
.withMods(synthetic) :: Nil
376-
}
377-
copyMeths ::: productElemMeths.toList
386+
if (mods.is(Abstract) || hasRepeatedParam) Nil // Cannot have default arguments for repeated parameters, hence copy method is not issued
387+
else {
388+
def copyDefault(vparam: ValDef) =
389+
makeAnnotated("scala.annotation.unchecked.uncheckedVariance", refOfDef(vparam))
390+
val copyFirstParams = derivedVparamss.head.map(vparam =>
391+
cpy.ValDef(vparam)(rhs = copyDefault(vparam)))
392+
val copyRestParamss = derivedVparamss.tail.nestedMap(vparam =>
393+
cpy.ValDef(vparam)(rhs = EmptyTree))
394+
DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr)
395+
.withMods(synthetic) :: Nil
396+
}
378397
}
398+
399+
if (isCaseClass)
400+
productElement :: productArity :: copyMeths ::: productElemMeths.toList
401+
else if (isCaseObject)
402+
productElement :: productArity :: Nil
379403
else Nil
404+
}
380405

381406
def anyRef = ref(defn.AnyRefAlias.typeRef)
382-
def productConstr(n: Int) = {
383-
val tycon = scalaDot((tpnme.Product.toString + n).toTypeName)
384-
val targs = constrVparamss.head map (_.tpt)
385-
if (targs.isEmpty) tycon else AppliedTypeTree(tycon, targs)
386-
}
387407

388-
// Case classes and case objects get a ProductN parent
389-
var parents1 = parents
390-
if (mods.is(Case) && arity <= Definitions.MaxTupleArity)
391-
parents1 = parents1 :+ productConstr(arity)
408+
// Case classes and case objects get NameBasedPattern and Product parents
409+
val parents1: List[Tree] =
410+
if (mods.is(Case)) parents :+ scalaDot(nme.Product.toTypeName) :+ scalaDot(nme.NameBasedPattern.toTypeName)
411+
else parents
392412

393413
// The thicket which is the desugared version of the companion object
394414
// synthetic object C extends parentTpt { defs }

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ class Definitions {
472472

473473
lazy val JavaCloneableClass = ctx.requiredClass("java.lang.Cloneable")
474474
lazy val NullPointerExceptionClass = ctx.requiredClass("java.lang.NullPointerException")
475+
lazy val IndexOutOfBoundsException = ctx.requiredClass("java.lang.IndexOutOfBoundsException")
475476
lazy val ClassClass = ctx.requiredClass("java.lang.Class")
476477
lazy val BoxedNumberClass = ctx.requiredClass("java.lang.Number")
477478
lazy val ThrowableClass = ctx.requiredClass("java.lang.Throwable")

0 commit comments

Comments
 (0)