Skip to content

Commit 63ac7e2

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 472b217 commit 63ac7e2

File tree

2 files changed

+53
-31
lines changed

2 files changed

+53
-31
lines changed

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

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,8 @@ object desugar {
285285
case _ => false
286286
}
287287

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

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

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

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

391412
// The thicket which is the desugared version of the companion object
392413
// 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
@@ -459,6 +459,7 @@ class Definitions {
459459

460460
lazy val JavaCloneableClass = ctx.requiredClass("java.lang.Cloneable")
461461
lazy val NullPointerExceptionClass = ctx.requiredClass("java.lang.NullPointerException")
462+
lazy val IndexOutOfBoundsException = ctx.requiredClass("java.lang.IndexOutOfBoundsException")
462463
lazy val ClassClass = ctx.requiredClass("java.lang.Class")
463464
lazy val BoxedNumberClass = ctx.requiredClass("java.lang.Number")
464465
lazy val ThrowableClass = ctx.requiredClass("java.lang.Throwable")

0 commit comments

Comments
 (0)