Skip to content

Fix #4985: Generate productElementName for case classes #5617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ object desugar {
DefDef(name, Nil, Nil, tpt, rhs).withMods(synthetic)
def productElemMeths = {
val caseParams = derivedVparamss.head.toArray
for (i <- 0 until arity if nme.selectorName(i) `ne` caseParams(i).name)
for (i <- List.range(0, arity) if nme.selectorName(i) `ne` caseParams(i).name)
yield syntheticProperty(nme.selectorName(i), caseParams(i).tpt,
Select(This(EmptyTypeIdent), caseParams(i).name))
}
Expand All @@ -484,8 +484,26 @@ object desugar {
}
}

// TODO When the Scala library is updated to 2.13.x add the override keyword to this generated method.
// (because Product.scala was updated)
def productElemNameMethod = {
val methodParam = makeSyntheticParameter(tpt = scalaDot(tpnme.Int))
val paramRef = Ident(methodParam.name)

val indexAsString = Apply(Select(javaDotLangDot(nme.String), nme.valueOf), paramRef)
val throwOutOfBound = Throw(New(javaDotLangDot(tpnme.IOOBException), List(List(indexAsString))))
val defaultCase = CaseDef(Ident(nme.WILDCARD), EmptyTree, throwOutOfBound)

val patternMatchCases = derivedVparamss.head.zipWithIndex.map { case (param, idx) =>
CaseDef(Literal(Constant(idx)), EmptyTree, Literal(Constant(param.name.decode.toString)))
} :+ defaultCase
val body = Match(paramRef, patternMatchCases)
DefDef(nme.productElementName, Nil, List(List(methodParam)), javaDotLangDot(tpnme.String), body)
.withFlags(Synthetic)
}

if (isCaseClass)
copyMeths ::: enumTagMeths ::: productElemMeths.toList
productElemNameMethod :: copyMeths ::: enumTagMeths ::: productElemMeths
else Nil
}

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ object Trees {
/** selector match { cases } */
case class Match[-T >: Untyped] private[ast] (selector: Tree[T], cases: List[CaseDef[T]])
extends TermTree[T] {
assert(cases.nonEmpty)
type ThisTree[-T >: Untyped] = Match[T]
def isInline = false
}
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def scalaDot(name: Name): Select = Select(rootDot(nme.scala_), name)
def scalaUnit: Select = scalaDot(tpnme.Unit)
def scalaAny: Select = scalaDot(tpnme.Any)
def javaDotLangDot(name: Name): Select = Select(Select(Ident(nme.java), nme.lang), name)

def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(implicit ctx: Context): DefDef =
DefDef(nme.CONSTRUCTOR, tparams, vparamss, TypeTree(), rhs)
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ object StdNames {
final val Serializable: N = "Serializable"
final val Singleton: N = "Singleton"
final val Throwable: N = "Throwable"
final val IOOBException: N = "IndexOutOfBoundsException"

final val ClassfileAnnotation: N = "ClassfileAnnotation"
final val ClassManifest: N = "ClassManifest"
Expand Down Expand Up @@ -483,6 +484,7 @@ object StdNames {
val prefix : N = "prefix"
val productArity: N = "productArity"
val productElement: N = "productElement"
val productElementName: N = "productElementName"
val productIterator: N = "productIterator"
val productPrefix: N = "productPrefix"
val raw_ : N = "raw"
Expand Down
3 changes: 2 additions & 1 deletion library/src/scala/tasty/reflect/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,8 @@ trait Printers
case DefDef(n, _, _, _, _) if d.symbol.owner.flags.isCase =>
n == "copy" ||
n.matches("copy\\$default\\$[1-9][0-9]*") || // default parameters for the copy method
n.matches("_[1-9][0-9]*") // Getters from Product
n.matches("_[1-9][0-9]*") || // Getters from Product
n == "productElementName"
case _ => false
})
}
Expand Down
11 changes: 11 additions & 0 deletions tests/run/productElementName.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
User(name=Susan, age=42)
ユーザー(名前=Susan, 年齢=42)
U$er(na$me=Susan, a$ge=42)
type(for=Susan, if=42)
contains spaces(first param=Susan, second param=42)
Symbols(::=Susan, ||=42)
MultipleParamLists(a=Susan, b=42)
AuxiliaryConstructor(a=Susan, b=42)
OverloadedApply(a=Susan, b=123)
PrivateMembers(a=10, b=20, c=30, d=40, e=50, f=60)
NoParams()
96 changes: 96 additions & 0 deletions tests/run/productElementName.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// These methods are not yet on Product.scala (added in 2.13.x)
trait Product2_13 extends Product {
def productElementName(n: Int): String

/** An iterator over the names of all the elements of this product.
*/
def productElementNames: Iterator[String] = new scala.collection.AbstractIterator[String] {
private[this] var c: Int = 0
private[this] val cmax = productArity

def hasNext = c < cmax

def next() = {
val result = productElementName(c); c += 1; result
}
}
}

case class User(name: String, age: Int) extends Product2_13

case class ユーザー(名前: String, 年齢: Int) extends Product2_13

case class U$er(na$me: String, a$ge: Int) extends Product2_13

case class `type`(`for`: String, `if`: Int) extends Product2_13

case class `contains spaces`(`first param`: String, `second param`: Int) extends Product2_13

case class Symbols(:: : String, || : Int) extends Product2_13

case class MultipleParamLists(a: String, b: Int)(c: Boolean) extends Product2_13

case class AuxiliaryConstructor(a: String, b: Int) extends Product2_13 {
def this(x: String) = {
this(x, 123)
}
}

case class OverloadedApply(a: String, b: Int) extends Product2_13
object OverloadedApply {
def apply(x: String): OverloadedApply =
new OverloadedApply(x, 123)
}

case class NoParams() extends Product2_13

//case class DefinesProductElementName(a: String, b: Int) extends Product2_13 {
// override def productElementName(n: Int): String = "foo"
//}

//trait A {
// override def productElementName(n: Int): String = "overriden"
//}
//case class InheritsProductElementName(a: String, b: Int) extends A
//
//trait B extends Product2_13 {
// override def productElementName(n: Int): String = "overriden"
//}
//case class InheritsProductElementName_Override(a: String, b: Int) extends B
//
//trait C { self: Product =>
// override def productElementName(n: Int): String = "overriden"
//}
//case class InheritsProductElementName_Override_SelfType(a: String, b: Int) extends C

case class PrivateMembers(a: Int, private val b: Int, c: Int, private val d: Int, e: Int, private val f: Int) extends Product2_13

object Test extends App {
def pretty(p: Product2_13): String =
p.productElementNames.zip(p.productIterator)
.map { case (name, value) => s"$name=$value" }
.mkString(p.productPrefix + "(", ", ", ")")

println(pretty(User("Susan", 42)))
println(pretty(ユーザー("Susan", 42)))
println(pretty(U$er("Susan", 42)))
println(pretty(`type`("Susan", 42)))
println(pretty(`contains spaces`("Susan", 42)))
println(pretty(Symbols("Susan", 42)))
println(pretty(MultipleParamLists("Susan", 42)(true)))
println(pretty(AuxiliaryConstructor("Susan", 42)))
println(pretty(OverloadedApply("Susan")))
// println(pretty(DefinesProductElementName("Susan", 42)))

// // uses the synthetic, not the one defined in the trait
// println(pretty(InheritsProductElementName("Susan", 42)))
//
// // uses the override defined in the trait
// println(pretty(InheritsProductElementName_Override("Susan", 42)))
//
// // uses the synthetic, not the one defined in the trait
// println(pretty(InheritsProductElementName_Override_SelfType("Susan", 42)))

println(pretty(PrivateMembers(10, 20, 30, 40, 50, 60)))
println(pretty(NoParams()))
}
2 changes: 1 addition & 1 deletion tests/run/tasty-extractors-2.check
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Type.SymRef(IsClassSymbol(<scala.Unit>), Type.ThisType(Type.SymRef(IsPackageSymb
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())))
Type.SymRef(IsClassSymbol(<scala.Unit>), Type.ThisType(Type.SymRef(IsPackageSymbol(<scala>), NoPrefix())))

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())))
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())))
Type.SymRef(IsClassSymbol(<scala.Unit>), Type.ThisType(Type.SymRef(IsPackageSymbol(<scala>), NoPrefix())))

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())))
Expand Down