Skip to content

[WIP] Add methods to abstract over structure of case classes #1346

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

Closed
wants to merge 2 commits into from
Closed
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
12 changes: 12 additions & 0 deletions src/dotty/DottyPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scala.reflect.runtime.universe.TypeTag
import scala.reflect.ClassTag
import scala.Predef.???
import scala.collection.Seq
import scala.collection.immutable.Map

/** unimplemented implicit for TypeTag */
object DottyPredef {
Expand Down Expand Up @@ -42,4 +43,15 @@ object DottyPredef {
implicit def eqNumFloat : Eq[Number, Float] = Eq
implicit def eqDoubleNum: Eq[Double, Number] = Eq
implicit def eqNumDouble: Eq[Number, Double] = Eq

def _toMap(x: DottyProduct, constructorParams: List[String]): Map[String, Any] = {
val values = x.productIterator.toList
constructorParams.zip(values).toMap
}
}

trait DottyProduct extends scala.Product {
def toMap: Map[String, Any]

def fields: List[String]
}
5 changes: 3 additions & 2 deletions src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -370,10 +370,11 @@ object desugar {
if (targs.isEmpty) tycon else AppliedTypeTree(tycon, targs)
}

// Case classes and case objects get a ProductN parent
// Case classes and case objects get a ProductN parent, as well as a DottyProduct parent
val dottyProduct = Select(rootDot(nme.dotty_), "DottyProduct".toTypeName)
var parents1 = parents
if (mods.is(Case) && arity <= Definitions.MaxTupleArity)
parents1 = parents1 :+ productConstr(arity)
parents1 = parents1 :+ productConstr(arity) :+ dottyProduct

// The thicket which is the desugared version of the companion object
// synthetic object C extends parentTpt { defs }
Expand Down
13 changes: 11 additions & 2 deletions src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ class Definitions {
def DottyPredefModule(implicit ctx: Context) = DottyPredefModuleRef.symbol

def Predef_eqAny(implicit ctx: Context) = DottyPredefModule.requiredMethod(nme.eqAny)
def predef_method(name: PreName) = DottyPredefModule.requiredMethodRef(name)

lazy val DottyArraysModuleRef = ctx.requiredModuleRef("dotty.runtime.Arrays")
def DottyArraysModule(implicit ctx: Context) = DottyArraysModuleRef.symbol
Expand All @@ -264,12 +265,16 @@ class Definitions {

lazy val SeqType: TypeRef = ctx.requiredClassRef("scala.collection.Seq")
def SeqClass(implicit ctx: Context) = SeqType.symbol.asClass

lazy val Seq_applyR = SeqClass.requiredMethodRef(nme.apply)
def Seq_apply(implicit ctx: Context) = Seq_applyR.symbol
lazy val Seq_headR = SeqClass.requiredMethodRef(nme.head)
def Seq_head(implicit ctx: Context) = Seq_headR.symbol

lazy val ListModuleRef = ctx.requiredModuleRef("scala.collection.immutable.List")
def ListModule(implicit ctx: Context) = ListModuleRef.symbol
lazy val List_applyR = ListModule.requiredMethodRef(nme.apply)
def List_apply(implicit ctx: Context) = List_applyR.symbol

lazy val ArrayType: TypeRef = ctx.requiredClassRef("scala.Array")
def ArrayClass(implicit ctx: Context) = ArrayType.symbol.asClass
lazy val Array_applyR = ArrayClass.requiredMethodRef(nme.apply)
Expand Down Expand Up @@ -416,14 +421,18 @@ class Definitions {
def DynamicClass(implicit ctx: Context) = DynamicType.symbol.asClass
lazy val OptionType: TypeRef = ctx.requiredClassRef("scala.Option")
def OptionClass(implicit ctx: Context) = OptionType.symbol.asClass
lazy val ProductType: TypeRef = ctx.requiredClassRef("scala.Product")
lazy val ProductType: TypeRef = ctx.requiredClassRef("dotty.DottyProduct")
def ProductClass(implicit ctx: Context) = ProductType.symbol.asClass
lazy val Product_canEqualR = ProductClass.requiredMethodRef(nme.canEqual_)
def Product_canEqual(implicit ctx: Context) = Product_canEqualR.symbol
lazy val Product_fieldsR = ProductClass.requiredMethodRef(nme.fields)
def Product_fields(implicit ctx: Context) = Product_fieldsR.symbol
lazy val Product_productArityR = ProductClass.requiredMethodRef(nme.productArity)
def Product_productArity(implicit ctx: Context) = Product_productArityR.symbol
lazy val Product_productPrefixR = ProductClass.requiredMethodRef(nme.productPrefix)
def Product_productPrefix(implicit ctx: Context) = Product_productPrefixR.symbol
lazy val Product_toMapR = ProductClass.requiredMethodRef(nme.toMap)
def Product_toMap(implicit ctx: Context) = Product_toMapR.symbol
lazy val LanguageModuleRef = ctx.requiredModule("dotty.language")
def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass
lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl")
Expand Down
3 changes: 3 additions & 0 deletions src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ object StdNames {
val experimental: N = "experimental"
val f: N = "f"
val false_ : N = "false"
val fields: N = "fields"
val filter: N = "filter"
val finalize_ : N = "finalize"
val find_ : N = "find"
Expand Down Expand Up @@ -476,6 +477,7 @@ object StdNames {
val runtimeMirror: N = "runtimeMirror"
val sameElements: N = "sameElements"
val scala_ : N = "scala"
val dotty_ : N = "dotty"
val selectDynamic: N = "selectDynamic"
val selectOverloadedMethod: N = "selectOverloadedMethod"
val selectTerm: N = "selectTerm"
Expand All @@ -499,6 +501,7 @@ object StdNames {
val throw_ : N = "throw"
val toArray: N = "toArray"
val toList: N = "toList"
val toMap: N = "toMap"
val toObjectArray : N = "toObjectArray"
val toSeq: N = "toSeq"
val toString_ : N = "toString"
Expand Down
36 changes: 33 additions & 3 deletions src/dotty/tools/dotc/transform/SyntheticMethods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,15 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
private def initSymbols(implicit ctx: Context) =
if (myValueSymbols.isEmpty) {
myValueSymbols = List(defn.Any_hashCode, defn.Any_equals)
myCaseSymbols = myValueSymbols ++ List(defn.Any_toString, defn.Product_canEqual,
defn.Product_productArity, defn.Product_productPrefix)
myCaseSymbols = myValueSymbols ++ {
defn.Any_toString ::
defn.Product_canEqual ::
defn.Product_fields ::
defn.Product_productArity ::
defn.Product_productPrefix ::
defn.Product_toMap ::
Nil
}
}

def valueSymbols(implicit ctx: Context) = { initSymbols; myValueSymbols }
Expand Down Expand Up @@ -80,6 +87,26 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
def forwardToRuntime(vrefss: List[List[Tree]]): Tree =
ref(defn.runtimeMethodRef("_" + sym.name.toString)).appliedToArgs(This(clazz) :: vrefss.head)

def synthFields(vrefss: List[List[Tree]]): Tree = {
val paramNames = SeqLiteral(
clazz
.primaryConstructor.info
.asInstanceOf[MethodType]
.paramNames.map(x => Literal(Constant(x.show))),
TypeTree(defn.StringType)
)

val repeatedConstrParams = Typed(
paramNames,
TypeTree(paramNames.tpe.widen.translateParameterized(defn.SeqClass, defn.RepeatedParamClass))
)

ref(defn.List_apply).appliedToType(defn.StringType).appliedTo(repeatedConstrParams)
}

def synthMap(vrefss: List[List[Tree]]): Tree =
ref(defn.predef_method("_" + sym.name.toString)).appliedToArgs(This(clazz) :: synthFields(vrefss) :: Nil)

def ownName(vrefss: List[List[Tree]]): Tree =
Literal(Constant(clazz.name.stripModuleClassSuffix.decode.toString))

Expand All @@ -91,6 +118,8 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
case nme.canEqual_ => vrefss => canEqualBody(vrefss.head.head)
case nme.productArity => vrefss => Literal(Constant(accessors.length))
case nme.productPrefix => ownName
case nme.toMap => synthMap
case nme.fields => synthFields
}
ctx.log(s"adding $synthetic to $clazz at ${ctx.phase}")
DefDef(synthetic, syntheticRHS(ctx.withOwner(synthetic)))
Expand All @@ -107,7 +136,8 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
* that match {
* case x$0 @ (_: C) => this.x == this$0.x && this.y == x$0.y
* case _ => false
* }
* }
* }
*
* If C is a value class the initial `eq` test is omitted.
*/
Expand Down
5 changes: 5 additions & 0 deletions tests/pos/cc_fields.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test {
case class Car(`type`: String, year: Int)
def main(args: Array[String]): Unit =
println(Car("Mustang", 1977).fields)
}
7 changes: 7 additions & 0 deletions tests/pos/toMap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
object Test {
case class Car(`type`: String, year: Int)

def main(args: Array[String]): Unit = {
println(Car("Mustang", 1977).toMap)
}
}