Skip to content

Commit b1f5432

Browse files
authored
Merge pull request #2314 from dotty-staging/no-product-n
Remove ProductN parent on case classes
2 parents 0b44aed + c7de6a4 commit b1f5432

File tree

5 files changed

+131
-73
lines changed

5 files changed

+131
-73
lines changed

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

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ object desugar {
2727

2828
/** Names of methods that are added unconditionally to case classes */
2929
def isDesugaredCaseClassMethodName(name: Name)(implicit ctx: Context): Boolean =
30-
name == nme.copy ||
31-
name == nme.productArity ||
32-
name.isSelectorName
30+
name == nme.copy || name.isSelectorName
3331

3432
// ----- DerivedTypeTrees -----------------------------------
3533

@@ -291,7 +289,8 @@ object desugar {
291289
case _ => false
292290
}
293291

294-
val isCaseClass = mods.is(Case) && !mods.is(Module)
292+
val isCaseClass = mods.is(Case) && !mods.is(Module)
293+
val isCaseObject = mods.is(Case) && mods.is(Module)
295294
val isEnum = mods.hasMod[Mod.Enum]
296295
val isEnumCase = isLegalEnumCase(cdef)
297296
val isValueClass = parents.nonEmpty && isAnyVal(parents.head)
@@ -360,31 +359,12 @@ object desugar {
360359
// pN: TN = pN: @uncheckedVariance)(moreParams) =
361360
// new C[...](p1, ..., pN)(moreParams)
362361
//
363-
// Above arity 22 we also synthesize:
364-
// def productArity = N
365-
// def productElement(i: Int): Any = i match { ... }
366-
//
367362
// Note: copy default parameters need @uncheckedVariance; see
368363
// neg/t1843-variances.scala for a test case. The test would give
369364
// two errors without @uncheckedVariance, one of them spurious.
370365
val caseClassMeths = {
371366
def syntheticProperty(name: TermName, rhs: Tree) =
372367
DefDef(name, Nil, Nil, TypeTree(), rhs).withMods(synthetic)
373-
def productArity = syntheticProperty(nme.productArity, Literal(Constant(arity)))
374-
def productElement = {
375-
val param = makeSyntheticParameter(tpt = ref(defn.IntType))
376-
// case N => _${N + 1}
377-
val cases = 0.until(arity).map { i =>
378-
CaseDef(Literal(Constant(i)), EmptyTree, Select(This(EmptyTypeIdent), nme.selectorName(i)))
379-
}
380-
val ioob = ref(defn.IndexOutOfBoundsException.typeRef)
381-
val error = Throw(New(ioob, List(List(Select(refOfDef(param), nme.toString_)))))
382-
// case _ => throw new IndexOutOfBoundsException(i.toString)
383-
val defaultCase = CaseDef(untpd.Ident(nme.WILDCARD), EmptyTree, error)
384-
val body = Match(refOfDef(param), (cases :+ defaultCase).toList)
385-
DefDef(nme.productElement, Nil, List(List(param)), TypeTree(defn.AnyType), body)
386-
.withMods(synthetic)
387-
}
388368
def productElemMeths = {
389369
val caseParams = constrVparamss.head.toArray
390370
for (i <- 0 until arity if nme.selectorName(i) `ne` caseParams(i).name)
@@ -414,33 +394,19 @@ object desugar {
414394
}
415395
}
416396

417-
// Above MaxTupleArity we extend Product instead of ProductN, in this
418-
// case we need to synthesise productElement & productArity.
419-
def largeProductMeths =
420-
if (arity > Definitions.MaxTupleArity) productElement :: productArity :: Nil
421-
else Nil
422-
423397
if (isCaseClass)
424-
largeProductMeths ::: copyMeths ::: enumTagMeths ::: productElemMeths.toList
398+
copyMeths ::: enumTagMeths ::: productElemMeths.toList
425399
else Nil
426400
}
427401

428402
def anyRef = ref(defn.AnyRefAlias.typeRef)
429-
def productConstr(n: Int) = {
430-
val tycon = scalaDot((str.Product + n).toTypeName)
431-
val targs = constrVparamss.head map (_.tpt)
432-
if (targs.isEmpty) tycon else AppliedTypeTree(tycon, targs)
433-
}
434-
def product =
435-
if (arity > Definitions.MaxTupleArity) scalaDot(str.Product.toTypeName)
436-
else productConstr(arity)
437403

438-
// Case classes and case objects get Product/ProductN parents
404+
// Case classes and case objects get Product parents
439405
var parents1 = parents
440406
if (isEnumCase && parents.isEmpty)
441407
parents1 = enumClassTypeRef :: Nil
442-
if (mods.is(Case))
443-
parents1 = parents1 :+ product // TODO: This also adds Product0 to case objects. Do we want that?
408+
if (isCaseClass | isCaseObject)
409+
parents1 = parents1 :+ scalaDot(str.Product.toTypeName)
444410
if (isEnum)
445411
parents1 = parents1 :+ ref(defn.EnumType)
446412

@@ -499,7 +465,6 @@ object desugar {
499465
companionDefs(anyRef, Nil)
500466
else Nil
501467

502-
503468
// For an implicit class C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, .., pMN: TMN), the method
504469
// synthetic implicit C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, ..., pMN: TMN): C[Ts] =
505470
// new C[Ts](p11, ..., p1N) ... (pM1, ..., pMN) =

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,8 @@ class Definitions {
524524
def Product_canEqual(implicit ctx: Context) = Product_canEqualR.symbol
525525
lazy val Product_productArityR = ProductClass.requiredMethodRef(nme.productArity)
526526
def Product_productArity(implicit ctx: Context) = Product_productArityR.symbol
527+
lazy val Product_productElementR = ProductClass.requiredMethodRef(nme.productElement)
528+
def Product_productElement(implicit ctx: Context) = Product_productElementR.symbol
527529
lazy val Product_productPrefixR = ProductClass.requiredMethodRef(nme.productPrefix)
528530
def Product_productPrefix(implicit ctx: Context) = Product_productPrefixR.symbol
529531
lazy val LanguageModuleRef = ctx.requiredModule("scala.language")
@@ -701,7 +703,6 @@ class Definitions {
701703
def FunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => ImplementedFunctionType.map(_.symbol.asClass))
702704

703705
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2)
704-
lazy val ProductNType = mkArityArray("scala.Product", MaxTupleArity, 0)
705706

706707
def FunctionClass(n: Int, isImplicit: Boolean = false)(implicit ctx: Context) =
707708
if (isImplicit) ctx.requiredClass("scala.ImplicitFunction" + n.toString)
@@ -716,7 +717,6 @@ class Definitions {
716717
else FunctionClass(n, isImplicit).typeRef
717718

718719
private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet
719-
private lazy val ProductTypes: Set[TypeRef] = ProductNType.toSet
720720

721721
/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
722722
def scalaClassName(cls: Symbol)(implicit ctx: Context): TypeName =

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

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,22 @@ import scala.language.postfixOps
1717

1818
/** Synthetic method implementations for case classes, case objects,
1919
* and value classes.
20+
*
2021
* Selectively added to case classes/objects, unless a non-default
2122
* implementation already exists:
2223
* def equals(other: Any): Boolean
2324
* def hashCode(): Int
2425
* def canEqual(other: Any): Boolean
2526
* def toString(): String
27+
* def productElement(i: Int): Any
2628
* def productArity: Int
2729
* def productPrefix: String
30+
*
2831
* Special handling:
2932
* protected def readResolve(): AnyRef
3033
*
3134
* Selectively added to value classes, unless a non-default
3235
* implementation already exists:
33-
*
3436
* def equals(other: Any): Boolean
3537
* def hashCode(): Int
3638
*/
@@ -44,14 +46,13 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
4446
if (myValueSymbols.isEmpty) {
4547
myValueSymbols = List(defn.Any_hashCode, defn.Any_equals)
4648
myCaseSymbols = myValueSymbols ++ List(defn.Any_toString, defn.Product_canEqual,
47-
defn.Product_productArity, defn.Product_productPrefix)
49+
defn.Product_productArity, defn.Product_productPrefix, defn.Product_productElement)
4850
}
4951

5052
def valueSymbols(implicit ctx: Context) = { initSymbols; myValueSymbols }
5153
def caseSymbols(implicit ctx: Context) = { initSymbols; myCaseSymbols }
5254

53-
/** The synthetic methods of the case or value class `clazz`.
54-
*/
55+
/** The synthetic methods of the case or value class `clazz`. */
5556
def syntheticMethods(clazz: ClassSymbol)(implicit ctx: Context): List[Tree] = {
5657
val clazzType = clazz.typeRef
5758
lazy val accessors =
@@ -91,25 +92,68 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
9192
case nme.canEqual_ => vrefss => canEqualBody(vrefss.head.head)
9293
case nme.productArity => vrefss => Literal(Constant(accessors.length))
9394
case nme.productPrefix => ownName
95+
case nme.productElement => vrefss => productElementBody(accessors.length, vrefss.head.head)
9496
}
9597
ctx.log(s"adding $synthetic to $clazz at ${ctx.phase}")
9698
DefDef(synthetic, syntheticRHS(ctx.withOwner(synthetic)))
9799
}
98100

99101
/** The class
100102
*
101-
* case class C(x: T, y: U)
103+
* ```
104+
* case class C(x: T, y: T)
105+
* ```
106+
*
107+
* gets the `productElement` method:
108+
*
109+
* ```
110+
* def productElement(index: Int): Any = index match {
111+
* case 0 => this._1
112+
* case 1 => this._2
113+
* case _ => throw new IndexOutOfBoundsException(index.toString)
114+
* }
115+
* ```
116+
*/
117+
def productElementBody(arity: Int, index: Tree)(implicit ctx: Context): Tree = {
118+
val ioob = defn.IndexOutOfBoundsException.typeRef
119+
// Second constructor of ioob that takes a String argument
120+
def filterStringConstructor(s: Symbol): Boolean = s.info match {
121+
case m: MethodType if s.isConstructor => m.paramInfos == List(defn.StringType)
122+
case _ => false
123+
}
124+
val constructor = ioob.typeSymbol.info.decls.find(filterStringConstructor _).asTerm
125+
val stringIndex = Apply(Select(index, nme.toString_), Nil)
126+
val error = Throw(New(ioob, constructor, List(stringIndex)))
127+
128+
// case _ => throw new IndexOutOfBoundsException(i.toString)
129+
val defaultCase = CaseDef(Underscore(defn.IntType), EmptyTree, error)
130+
131+
// case N => _${N + 1}
132+
val cases = 0.until(arity).map { i =>
133+
CaseDef(Literal(Constant(i)), EmptyTree, Select(This(clazz), nme.selectorName(i)))
134+
}
135+
136+
Match(index, (cases :+ defaultCase).toList)
137+
}
138+
139+
/** The class
140+
*
141+
* ```
142+
* case class C(x: T, y: U)
143+
* ```
102144
*
103-
* gets the equals method:
145+
* gets the `equals` method:
104146
*
105-
* def equals(that: Any): Boolean =
106-
* (this eq that) || {
107-
* that match {
108-
* case x$0 @ (_: C) => this.x == this$0.x && this.y == x$0.y
109-
* case _ => false
110-
* }
147+
* ```
148+
* def equals(that: Any): Boolean =
149+
* (this eq that) || {
150+
* that match {
151+
* case x$0 @ (_: C) => this.x == this$0.x && this.y == x$0.y
152+
* case _ => false
153+
* }
154+
* ```
111155
*
112-
* If C is a value class the initial `eq` test is omitted.
156+
* If `C` is a value class the initial `eq` test is omitted.
113157
*/
114158
def equalsBody(that: Tree)(implicit ctx: Context): Tree = {
115159
val thatAsClazz = ctx.newSymbol(ctx.owner, nme.x_0, Synthetic, clazzType, coord = ctx.owner.pos) // x$0
@@ -131,11 +175,15 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
131175

132176
/** The class
133177
*
178+
* ```
134179
* class C(x: T) extends AnyVal
180+
* ```
135181
*
136-
* gets the hashCode method:
182+
* gets the `hashCode` method:
137183
*
138-
* def hashCode: Int = x.hashCode()
184+
* ```
185+
* def hashCode: Int = x.hashCode()
186+
* ```
139187
*/
140188
def valueHashCodeBody(implicit ctx: Context): Tree = {
141189
assert(accessors.length == 1)
@@ -144,17 +192,21 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
144192

145193
/** The class
146194
*
147-
* package p
148-
* case class C(x: T, y: T)
195+
* ```
196+
* package p
197+
* case class C(x: T, y: T)
198+
* ```
149199
*
150-
* gets the hashCode method:
200+
* gets the `hashCode` method:
151201
*
152-
* def hashCode: Int = {
153-
* <synthetic> var acc: Int = "p.C".hashCode // constant folded
154-
* acc = Statics.mix(acc, x);
155-
* acc = Statics.mix(acc, Statics.this.anyHash(y));
156-
* Statics.finalizeHash(acc, 2)
157-
* }
202+
* ```
203+
* def hashCode: Int = {
204+
* <synthetic> var acc: Int = "p.C".hashCode // constant folded
205+
* acc = Statics.mix(acc, x);
206+
* acc = Statics.mix(acc, Statics.this.anyHash(y));
207+
* Statics.finalizeHash(acc, 2)
208+
* }
209+
* ```
158210
*/
159211
def caseHashCodeBody(implicit ctx: Context): Tree = {
160212
val acc = ctx.newSymbol(ctx.owner, "acc".toTermName, Mutable | Synthetic, defn.IntType, coord = ctx.owner.pos)
@@ -165,7 +217,7 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
165217
Block(accDef :: mixes, finish)
166218
}
167219

168-
/** The hashCode implementation for given symbol `sym`. */
220+
/** The `hashCode` implementation for given symbol `sym`. */
169221
def hashImpl(sym: Symbol)(implicit ctx: Context): Tree =
170222
defn.scalaClassName(sym.info.finalResultType) match {
171223
case tpnme.Unit | tpnme.Null => Literal(Constant(0))
@@ -180,11 +232,15 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
180232

181233
/** The class
182234
*
183-
* case class C(...)
235+
* ```
236+
* case class C(...)
237+
* ```
184238
*
185-
* gets the canEqual method
239+
* gets the `canEqual` method
186240
*
187-
* def canEqual(that: Any) = that.isInstanceOf[C]
241+
* ```
242+
* def canEqual(that: Any) = that.isInstanceOf[C]
243+
* ```
188244
*/
189245
def canEqualBody(that: Tree): Tree = that.isInstance(clazzType)
190246

tests/neg/t1843-variances.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
object Crash {
88
trait UpdateType[A]
9-
case class StateUpdate[+A](updateType : UpdateType[A], value : A) // error
9+
case class StateUpdate[+A](updateType : UpdateType[A], value : A) // error // error
1010
case object IntegerUpdateType extends UpdateType[Integer]
1111

1212
//However this method will cause a crash

tests/run/i2314.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
case class A(i: Int, s: String)
2+
3+
case class B(i: Int, s: String) {
4+
// No override, these methods will be added by SyntheticMethods only if
5+
// there are not user defined.
6+
def productArity = -1
7+
def productElement(i: Int): Any = None
8+
}
9+
10+
object Test {
11+
def main(args: Array[String]): Unit = {
12+
val a = A(1, "s")
13+
assert(a.productArity == 2)
14+
assert(a.productElement(0) == 1)
15+
assert(a.productElement(1) == "s")
16+
17+
try {
18+
a.productElement(-1)
19+
???
20+
} catch {
21+
case e: IndexOutOfBoundsException =>
22+
assert(e.getMessage == "-1")
23+
}
24+
try {
25+
a.productElement(2)
26+
???
27+
} catch {
28+
case e: IndexOutOfBoundsException =>
29+
assert(e.getMessage == "2")
30+
}
31+
32+
val b = B(1, "s")
33+
assert(b.productArity == -1)
34+
assert(b.productElement(0) == None)
35+
assert(b.productElement(1) == None)
36+
}
37+
}

0 commit comments

Comments
 (0)