Skip to content

Commit fcc35d2

Browse files
authored
Merge pull request #5617 from tOverney/productName
Fix #4985: Generate productElementName for case classes
2 parents 24fb64a + 8fb74ff commit fcc35d2

File tree

8 files changed

+134
-4
lines changed

8 files changed

+134
-4
lines changed

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

+20-2
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ object desugar {
457457
DefDef(name, Nil, Nil, tpt, rhs).withMods(synthetic)
458458
def productElemMeths = {
459459
val caseParams = derivedVparamss.head.toArray
460-
for (i <- 0 until arity if nme.selectorName(i) `ne` caseParams(i).name)
460+
for (i <- List.range(0, arity) if nme.selectorName(i) `ne` caseParams(i).name)
461461
yield syntheticProperty(nme.selectorName(i), caseParams(i).tpt,
462462
Select(This(EmptyTypeIdent), caseParams(i).name))
463463
}
@@ -484,8 +484,26 @@ object desugar {
484484
}
485485
}
486486

487+
// TODO When the Scala library is updated to 2.13.x add the override keyword to this generated method.
488+
// (because Product.scala was updated)
489+
def productElemNameMethod = {
490+
val methodParam = makeSyntheticParameter(tpt = scalaDot(tpnme.Int))
491+
val paramRef = Ident(methodParam.name)
492+
493+
val indexAsString = Apply(Select(javaDotLangDot(nme.String), nme.valueOf), paramRef)
494+
val throwOutOfBound = Throw(New(javaDotLangDot(tpnme.IOOBException), List(List(indexAsString))))
495+
val defaultCase = CaseDef(Ident(nme.WILDCARD), EmptyTree, throwOutOfBound)
496+
497+
val patternMatchCases = derivedVparamss.head.zipWithIndex.map { case (param, idx) =>
498+
CaseDef(Literal(Constant(idx)), EmptyTree, Literal(Constant(param.name.decode.toString)))
499+
} :+ defaultCase
500+
val body = Match(paramRef, patternMatchCases)
501+
DefDef(nme.productElementName, Nil, List(List(methodParam)), javaDotLangDot(tpnme.String), body)
502+
.withFlags(Synthetic)
503+
}
504+
487505
if (isCaseClass)
488-
copyMeths ::: enumTagMeths ::: productElemMeths.toList
506+
productElemNameMethod :: copyMeths ::: enumTagMeths ::: productElemMeths
489507
else Nil
490508
}
491509

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

+1
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ object Trees {
520520
/** selector match { cases } */
521521
case class Match[-T >: Untyped] private[ast] (selector: Tree[T], cases: List[CaseDef[T]])
522522
extends TermTree[T] {
523+
assert(cases.nonEmpty)
523524
type ThisTree[-T >: Untyped] = Match[T]
524525
def isInline = false
525526
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
356356
def scalaDot(name: Name): Select = Select(rootDot(nme.scala_), name)
357357
def scalaUnit: Select = scalaDot(tpnme.Unit)
358358
def scalaAny: Select = scalaDot(tpnme.Any)
359+
def javaDotLangDot(name: Name): Select = Select(Select(Ident(nme.java), nme.lang), name)
359360

360361
def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(implicit ctx: Context): DefDef =
361362
DefDef(nme.CONSTRUCTOR, tparams, vparamss, TypeTree(), rhs)

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

+2
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ object StdNames {
203203
final val Serializable: N = "Serializable"
204204
final val Singleton: N = "Singleton"
205205
final val Throwable: N = "Throwable"
206+
final val IOOBException: N = "IndexOutOfBoundsException"
206207

207208
final val ClassfileAnnotation: N = "ClassfileAnnotation"
208209
final val ClassManifest: N = "ClassManifest"
@@ -483,6 +484,7 @@ object StdNames {
483484
val prefix : N = "prefix"
484485
val productArity: N = "productArity"
485486
val productElement: N = "productElement"
487+
val productElementName: N = "productElementName"
486488
val productIterator: N = "productIterator"
487489
val productPrefix: N = "productPrefix"
488490
val raw_ : N = "raw"

library/src/scala/tasty/reflect/Printers.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,8 @@ trait Printers
564564
case DefDef(n, _, _, _, _) if d.symbol.owner.flags.isCase =>
565565
n == "copy" ||
566566
n.matches("copy\\$default\\$[1-9][0-9]*") || // default parameters for the copy method
567-
n.matches("_[1-9][0-9]*") // Getters from Product
567+
n.matches("_[1-9][0-9]*") || // Getters from Product
568+
n == "productElementName"
568569
case _ => false
569570
})
570571
}

tests/run/productElementName.check

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
User(name=Susan, age=42)
2+
ユーザー(名前=Susan, 年齢=42)
3+
U$er(na$me=Susan, a$ge=42)
4+
type(for=Susan, if=42)
5+
contains spaces(first param=Susan, second param=42)
6+
Symbols(::=Susan, ||=42)
7+
MultipleParamLists(a=Susan, b=42)
8+
AuxiliaryConstructor(a=Susan, b=42)
9+
OverloadedApply(a=Susan, b=123)
10+
PrivateMembers(a=10, b=20, c=30, d=40, e=50, f=60)
11+
NoParams()

tests/run/productElementName.scala

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// These methods are not yet on Product.scala (added in 2.13.x)
2+
trait Product2_13 extends Product {
3+
def productElementName(n: Int): String
4+
5+
/** An iterator over the names of all the elements of this product.
6+
*/
7+
def productElementNames: Iterator[String] = new scala.collection.AbstractIterator[String] {
8+
private[this] var c: Int = 0
9+
private[this] val cmax = productArity
10+
11+
def hasNext = c < cmax
12+
13+
def next() = {
14+
val result = productElementName(c); c += 1; result
15+
}
16+
}
17+
}
18+
19+
case class User(name: String, age: Int) extends Product2_13
20+
21+
case class ーザー(名前: String, 年齢: Int) extends Product2_13
22+
23+
case class U$er(na$me: String, a$ge: Int) extends Product2_13
24+
25+
case class `type`(`for`: String, `if`: Int) extends Product2_13
26+
27+
case class `contains spaces`(`first param`: String, `second param`: Int) extends Product2_13
28+
29+
case class Symbols(:: : String, || : Int) extends Product2_13
30+
31+
case class MultipleParamLists(a: String, b: Int)(c: Boolean) extends Product2_13
32+
33+
case class AuxiliaryConstructor(a: String, b: Int) extends Product2_13 {
34+
def this(x: String) = {
35+
this(x, 123)
36+
}
37+
}
38+
39+
case class OverloadedApply(a: String, b: Int) extends Product2_13
40+
object OverloadedApply {
41+
def apply(x: String): OverloadedApply =
42+
new OverloadedApply(x, 123)
43+
}
44+
45+
case class NoParams() extends Product2_13
46+
47+
//case class DefinesProductElementName(a: String, b: Int) extends Product2_13 {
48+
// override def productElementName(n: Int): String = "foo"
49+
//}
50+
51+
//trait A {
52+
// override def productElementName(n: Int): String = "overriden"
53+
//}
54+
//case class InheritsProductElementName(a: String, b: Int) extends A
55+
//
56+
//trait B extends Product2_13 {
57+
// override def productElementName(n: Int): String = "overriden"
58+
//}
59+
//case class InheritsProductElementName_Override(a: String, b: Int) extends B
60+
//
61+
//trait C { self: Product =>
62+
// override def productElementName(n: Int): String = "overriden"
63+
//}
64+
//case class InheritsProductElementName_Override_SelfType(a: String, b: Int) extends C
65+
66+
case class PrivateMembers(a: Int, private val b: Int, c: Int, private val d: Int, e: Int, private val f: Int) extends Product2_13
67+
68+
object Test extends App {
69+
def pretty(p: Product2_13): String =
70+
p.productElementNames.zip(p.productIterator)
71+
.map { case (name, value) => s"$name=$value" }
72+
.mkString(p.productPrefix + "(", ", ", ")")
73+
74+
println(pretty(User("Susan", 42)))
75+
println(pretty(ユーザー("Susan", 42)))
76+
println(pretty(U$er("Susan", 42)))
77+
println(pretty(`type`("Susan", 42)))
78+
println(pretty(`contains spaces`("Susan", 42)))
79+
println(pretty(Symbols("Susan", 42)))
80+
println(pretty(MultipleParamLists("Susan", 42)(true)))
81+
println(pretty(AuxiliaryConstructor("Susan", 42)))
82+
println(pretty(OverloadedApply("Susan")))
83+
// println(pretty(DefinesProductElementName("Susan", 42)))
84+
85+
// // uses the synthetic, not the one defined in the trait
86+
// println(pretty(InheritsProductElementName("Susan", 42)))
87+
//
88+
// // uses the override defined in the trait
89+
// println(pretty(InheritsProductElementName_Override("Susan", 42)))
90+
//
91+
// // uses the synthetic, not the one defined in the trait
92+
// println(pretty(InheritsProductElementName_Override_SelfType("Susan", 42)))
93+
94+
println(pretty(PrivateMembers(10, 20, 30, 40, 50, 60)))
95+
println(pretty(NoParams()))
96+
}

tests/run/tasty-extractors-2.check

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Type.SymRef(IsClassSymbol(<scala.Unit>), Type.ThisType(Type.SymRef(IsPackageSymb
4949
Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil)), None, List(DefDef("a", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(0))))))), Term.Literal(Constant.Unit())))
5050
Type.SymRef(IsClassSymbol(<scala.Unit>), Type.ThisType(Type.SymRef(IsPackageSymbol(<scala>), NoPrefix())))
5151

52-
Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Product")), None, List(DefDef("copy", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil))), DefDef("hashCode", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Literal(Constant.Int(394005536)))), DefDef("equals", Nil, List(List(ValDef("x$0", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.Apply(Term.Select(Term.This(Some(Id("Foo"))), "eq"), List(Term.TypeApply(Term.Select(Term.Ident("x$0"), "asInstanceOf"), List(TypeTree.Inferred())))), "||"), List(Term.Match(Term.Ident("x$0"), List(CaseDef(Pattern.Bind("x$0", Pattern.TypeTest(TypeTree.Inferred())), None, Term.Literal(Constant.Boolean(true))), CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Literal(Constant.Boolean(false))))))))), DefDef("toString", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Ident("_toString"), List(Term.This(Some(Id("Foo"))))))), DefDef("canEqual", Nil, List(List(ValDef("that", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.TypeApply(Term.Select(Term.Ident("that"), "isInstanceOf"), List(TypeTree.Inferred())))), DefDef("productArity", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(0)))), DefDef("productPrefix", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.String("Foo")))), DefDef("productElement", Nil, List(List(ValDef("n", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Match(Term.Ident("n"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), List(Term.Apply(Term.Select(Term.Ident("n"), "toString"), Nil)))))))))))), ValDef("Foo", TypeTree.Ident("Foo$"), Some(Term.Apply(Term.Select(Term.New(TypeTree.Ident("Foo$")), "<init>"), Nil))), ClassDef("Foo$", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil), TypeTree.Applied(TypeTree.Inferred(), List(TypeTree.Inferred()))), Some(ValDef("_", TypeTree.Singleton(Term.Ident("Foo")), None)), List(DefDef("apply", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil))), DefDef("unapply", Nil, List(List(ValDef("x$1", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Literal(Constant.Boolean(true))))))), Term.Literal(Constant.Unit())))
52+
Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Product")), None, List(DefDef("productElementName", Nil, List(List(ValDef("x$1", TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Int"), None))), TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "String"), Some(Term.Match(Term.Ident("x$1"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "IndexOutOfBoundsException")), "<init>"), List(Term.Apply(Term.Select(Term.Select(Term.Select(Term.Ident("java"), "lang"), "String"), "valueOf"), List(Term.Ident("x$1")))))))))))), DefDef("copy", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil))), DefDef("hashCode", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Literal(Constant.Int(394005536)))), DefDef("equals", Nil, List(List(ValDef("x$0", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.Apply(Term.Select(Term.This(Some(Id("Foo"))), "eq"), List(Term.TypeApply(Term.Select(Term.Ident("x$0"), "asInstanceOf"), List(TypeTree.Inferred())))), "||"), List(Term.Match(Term.Ident("x$0"), List(CaseDef(Pattern.Bind("x$0", Pattern.TypeTest(TypeTree.Inferred())), None, Term.Literal(Constant.Boolean(true))), CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Literal(Constant.Boolean(false))))))))), DefDef("toString", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Ident("_toString"), List(Term.This(Some(Id("Foo"))))))), DefDef("canEqual", Nil, List(List(ValDef("that", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.TypeApply(Term.Select(Term.Ident("that"), "isInstanceOf"), List(TypeTree.Inferred())))), DefDef("productArity", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(0)))), DefDef("productPrefix", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.String("Foo")))), DefDef("productElement", Nil, List(List(ValDef("n", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Match(Term.Ident("n"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), List(Term.Apply(Term.Select(Term.Ident("n"), "toString"), Nil)))))))))))), ValDef("Foo", TypeTree.Ident("Foo$"), Some(Term.Apply(Term.Select(Term.New(TypeTree.Ident("Foo$")), "<init>"), Nil))), ClassDef("Foo$", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil), TypeTree.Applied(TypeTree.Inferred(), List(TypeTree.Inferred()))), Some(ValDef("_", TypeTree.Singleton(Term.Ident("Foo")), None)), List(DefDef("apply", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil))), DefDef("unapply", Nil, List(List(ValDef("x$1", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Literal(Constant.Boolean(true))))))), Term.Literal(Constant.Unit())))
5353
Type.SymRef(IsClassSymbol(<scala.Unit>), Type.ThisType(Type.SymRef(IsPackageSymbol(<scala>), NoPrefix())))
5454

5555
Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo1", DefDef("<init>", Nil, List(List(ValDef("a", TypeTree.Ident("Int"), None))), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil)), None, List(ValDef("a", TypeTree.Inferred(), None)))), Term.Literal(Constant.Unit())))

0 commit comments

Comments
 (0)