aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-11-28 22:51:39 +0000
committerJon Pretty <jon.pretty@propensive.com>2017-11-28 22:51:39 +0000
commitbf5fbc0373ffdff5bd7b269e2c0709fd92819641 (patch)
tree5c0e2d26887378af95ad868997dddb6cfdc50e3a
parentd20a3824ce7719deb328ffed002288bedc398f77 (diff)
downloadmagnolia-bf5fbc0373ffdff5bd7b269e2c0709fd92819641.tar.gz
magnolia-bf5fbc0373ffdff5bd7b269e2c0709fd92819641.tar.bz2
magnolia-bf5fbc0373ffdff5bd7b269e2c0709fd92819641.zip
Support for virtualization of parameters and subtypes
-rw-r--r--core/shared/src/main/scala/magnolia.scala199
-rw-r--r--examples/shared/src/main/scala/eq.scala1
-rw-r--r--examples/shared/src/main/scala/show.scala25
-rw-r--r--tests/src/main/scala/tests.scala4
4 files changed, 156 insertions, 73 deletions
diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala
index 0e5728c..bcb695f 100644
--- a/core/shared/src/main/scala/magnolia.scala
+++ b/core/shared/src/main/scala/magnolia.scala
@@ -98,15 +98,11 @@ object Magnolia {
}
val typeConstructor = getTypeMember("Typeclass")
- val pTypeConstructor = getTypeMember("ParamType")
def getMethod[T](termName: String): Option[MethodSymbol] = {
val term = TermName(termName)
- val combineClass = c.prefix.tree.tpe.baseClasses
- .find { cls =>
- cls.asType.toType.decl(term) != NoSymbol
- }
- combineClass.map { c =>
+ val cls = c.prefix.tree.tpe.baseClasses.find(_.asType.toType.decl(term) != NoSymbol)
+ cls.map { c =>
c.asType.toType.decl(term).asTerm.asMethod
}
}
@@ -204,14 +200,14 @@ object Magnolia {
def directInferImplicit(genericType: c.Type, typeConstructor: Type): Option[Typeclass] = {
val genericTypeName: String = genericType.typeSymbol.name.decodedName.toString.toLowerCase
- val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass"))
- val typeSymbol = genericType.typeSymbol
- val classType = if (typeSymbol.isClass) Some(typeSymbol.asClass) else None
- val isCaseClass = classType.map(_.isCaseClass).getOrElse(false)
- val isCaseObject = classType.map(_.isModuleClass).getOrElse(false)
- val isSealedTrait = classType.map(_.isSealed).getOrElse(false)
-
- val primitives = Set(typeOf[Double],
+ lazy val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass"))
+ lazy val typeSymbol = genericType.typeSymbol
+ lazy val classType = if (typeSymbol.isClass) Some(typeSymbol.asClass) else None
+ lazy val isCaseClass = classType.map(_.isCaseClass).getOrElse(false)
+ lazy val isCaseObject = classType.map(_.isModuleClass).getOrElse(false)
+ lazy val isSealedTrait = classType.map(_.isSealed).getOrElse(false)
+
+ lazy val primitives = Set(typeOf[Double],
typeOf[Float],
typeOf[Short],
typeOf[Byte],
@@ -220,24 +216,47 @@ object Magnolia {
typeOf[Char],
typeOf[Boolean])
- val isValueClass = genericType <:< typeOf[AnyVal] && !primitives.exists(_ =:= genericType)
+ lazy val isValueClass = genericType <:< typeOf[AnyVal] && !primitives.exists(_ =:= genericType)
- val resultType = appliedType(typeConstructor, genericType)
+ lazy val resultType = appliedType(typeConstructor, genericType)
+ lazy val liftedParamType: Tree = getMethod("param").map { sym =>
+ tq"${c.prefix}.ParamType[$genericType, _]"
+ }.getOrElse(tq"$magnoliaPkg.Param[$typeConstructor, $genericType]")
+
+ lazy val liftedSubtypeType: Tree = getMethod("subtype").map { sym =>
+ tq"${c.prefix}.SubtypeType[$genericType, _]"
+ }.getOrElse(tq"$magnoliaPkg.Subtype[$typeConstructor, $genericType]")
+
+ lazy val caseClassMethod = getMethod("caseClass").map { sym =>
+ q"${c.prefix}.caseClass[$genericType, $liftedParamType]"
+ }.getOrElse(q"$magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType, $liftedParamType]")
+
+ lazy val caseClassParamNames = getMethod("caseClass").map { sym =>
+ sym.paramLists.head.map(_.name.decodedName.toString)
+ }.getOrElse(List("name", "isCaseObject", "isValueClass", "parameters", "constructor"))
+
+ lazy val sealedTraitMethod = getMethod("sealedTrait").map { sym =>
+ q"${c.prefix}.sealedTrait[$genericType, $liftedParamType]"
+ }.getOrElse(q"$magnoliaPkg.Magnolia.sealedTrait[$typeConstructor, $genericType, $liftedParamType]")
+
+ lazy val sealedTraitParamNames = getMethod("sealedTrait").map { sym =>
+ sym.paramLists.head.map(_.name.decodedName.toString)
+ }.getOrElse(List("name", "subtypes"))
+
val result = if (isCaseObject) {
- // FIXME: look for an alternative which isn't deprecated on Scala 2.12+
val obj = companionRef(genericType)
val className = genericType.typeSymbol.name.decodedName.toString
- val paramType: Tree = getMethod("param").map { sym =>
- tq"${c.prefix}.ParamType[$genericType, _]"
- }.getOrElse(tq"$magnoliaPkg.Param[$typeConstructor, $genericType]")
-
- val impl = q"""
- ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType, $paramType](
- $className, true, false, new $scalaPkg.Array(0), _ => $obj)
- )
- """
+ val parameters = caseClassParamNames.map {
+ case "name" => q"$className"
+ case "isCaseObject" => q"true"
+ case "isValueClass" => q"false"
+ case "parameters" => q"new $scalaPkg.Array(0)"
+ case "constructor" => q"(_ => $obj)"
+ }
+
+ val impl = q"${c.prefix}.combine($caseClassMethod(..$parameters))"
Some(Typeclass(genericType, impl))
} else if (isCaseClass || isValueClass) {
val caseClassParameters = genericType.decls.collect {
@@ -302,31 +321,49 @@ object Magnolia {
val assignments = caseParams.zip(defaults).zipWithIndex.map {
case ((CaseParam(param, typeclass, paramType, ref), defaultVal), idx) =>
val paramMethod: Tree = getMethod("param").map { sym =>
- q"${c.prefix}.param[$genericType, $paramType]"
+ q"${c.prefix}.param"
}.getOrElse(q"$magnoliaPkg.Magnolia.param[$typeConstructor, $genericType, $paramType]")
- q"""$paramsVal($idx) = $paramMethod(${param.name.decodedName.toString}, $ref, $defaultVal, _.${param.name})"""
+
+ val paramNames = getMethod("param").map { sym =>
+ sym.paramLists.head.map(_.name.decodedName.toString)
+ }.getOrElse(List("name", "typeclass", "default", "dereference"))
+
+ val parameters: List[Tree] = paramNames.map {
+ case "name" => q"${param.name.decodedName.toString}"
+ case "typeclass" => q"$ref"
+ case "default" => q"$defaultVal"
+ case "dereference" => q"_.${param.name}"
+ case other =>
+ c.abort(c.enclosingPosition, s"magnolia: method 'param' has an unexpected parameter with name '$other'; permitted parameter names: name, typeclass, default, dereference")
+ }
+ q"""$paramsVal($idx) = $paramMethod(..$parameters)"""
}
+ val parameters = caseClassParamNames.map {
+ case "name" => q"$className"
+ case "isCaseObject" => q"false"
+ case "isValueClass" => q"$isValueClass"
+ case "parameters" => q"$paramsVal"
+ case "constructor" => q"""
+ ($fnVal: $liftedParamType => Any) =>
+ new $genericType(..${caseParams.zipWithIndex.map {
+ case (typeclass, idx) =>
+ q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
+ }})
+ """
+ }
+
+ val impl = q"${c.prefix}.combine($caseClassMethod(..$parameters))"
Some(
Typeclass(
genericType,
q"""{
..$preAssignments
- val $paramsVal: $scalaPkg.Array[${c.prefix}.ParamType[$genericType, _]] =
+ val $paramsVal: $scalaPkg.Array[$liftedParamType] =
new $scalaPkg.Array(${assignments.length})
..$assignments
- ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType, ${c.prefix}.ParamType[$genericType, _]](
- $className,
- false,
- $isValueClass,
- $paramsVal,
- ($fnVal: ${c.prefix}.ParamType[$genericType, _] => Any) =>
- new $genericType(..${caseParams.zipWithIndex.map {
- case (typeclass, idx) =>
- q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
- }})
- ))
+ ${c.prefix}.combine($caseClassMethod(..$parameters))
}"""
)
)
@@ -358,28 +395,40 @@ object Magnolia {
}
val assignments = typeclasses.zipWithIndex.map {
- case ((typ, typeclass), idx) =>
- q"""$subtypesVal($idx) = $magnoliaPkg.Magnolia.subtype[$typeConstructor, $genericType, $typ](
- ${typ.typeSymbol.fullName.toString},
- $typeclass,
- (t: $genericType) => t.isInstanceOf[$typ],
- (t: $genericType) => t.asInstanceOf[$typ]
- )"""
+ case ((subtype, typeclass), idx) =>
+ val subtypeMethod: Tree = getMethod("subtype").map { sym =>
+ q"${c.prefix}.subtype"
+ }.getOrElse(q"$magnoliaPkg.Magnolia.subtype[$typeConstructor, $genericType, $subtype]")
+
+ val subtypeParamNames = getMethod("subtype").map { sym =>
+ sym.paramLists.head.map(_.name.decodedName.toString)
+ }.getOrElse(List("name", "typeclass", "isType", "asType"))
+
+ val parameters = subtypeParamNames.map {
+ case "name" => q"${subtype.typeSymbol.fullName.toString}"
+ case "typeclass" => q"$typeclass"
+ case "isType" => q"(t: $genericType) => t.isInstanceOf[$subtype]"
+ case "asType" => q"(t: $genericType) => t.asInstanceOf[$subtype]"
+ }
+
+ q"""$subtypesVal($idx) = $subtypeMethod(..$parameters)"""
+ }
+
+ val parameters = sealedTraitParamNames.map {
+ case "name" => q"$genericTypeName"
+ case "subtypes" => q"$subtypesVal: $scalaPkg.Array[$liftedSubtypeType]"
}
Some {
Typeclass(
genericType,
q"""{
- val $subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]] =
+ val $subtypesVal: $scalaPkg.Array[$liftedSubtypeType] =
new $scalaPkg.Array(${assignments.size})
..$assignments
- ${c.prefix}.dispatch(new $magnoliaPkg.SealedTrait(
- $genericTypeName,
- $subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]])
- ): $resultType
+ ${c.prefix}.dispatch($sealedTraitMethod(..$parameters)): $resultType
}"""
)
}
@@ -388,7 +437,7 @@ object Magnolia {
result.map {
case Typeclass(t, r) =>
Typeclass(t, q"""{
- def $assignedName: $resultType = $r
+ lazy val $assignedName: $resultType = $r
$assignedName
}""")
}
@@ -441,30 +490,38 @@ object Magnolia {
*
* This method is intended to be called only from code generated by the Magnolia macro, and
* should not be called directly from users' code. */
- def subtype[Tc[_], T, S <: T](name: String, tc: => Tc[S], isType: T => Boolean, asType: T => S) =
+ def subtype[Tc[_], T, S <: T](name: String, typeclass: => Tc[S], isType: T => Boolean, asType: T => S) = {
+ lazy val typeclassVal = typeclass
new Subtype[Tc, T] {
type SType = S
def label: String = name
- def typeclass: Tc[SType] = tc
+ def typeclass: Tc[SType] = typeclassVal
def cast: PartialFunction[T, SType] = new PartialFunction[T, S] {
def isDefinedAt(t: T) = isType(t)
def apply(t: T): SType = asType(t)
}
}
+ }
/** constructs a new [[Param]] instance
*
* This method is intended to be called only from code generated by the Magnolia macro, and
* should not be called directly from users' code. */
def param[Tc[_], T, P](name: String,
- typeclassParam: Tc[P],
- defaultVal: => Option[P],
- deref: T => P) = new Param[Tc, T] {
- type PType = P
- def label: String = name
- def default: Option[PType] = defaultVal
- def typeclass: Tc[PType] = typeclassParam
- def dereference(t: T): PType = deref(t)
+ typeclass: Tc[P],
+ default: => Option[P],
+ dereference: T => P) = {
+ val typeclassVal = typeclass
+ val defaultVal = default
+ val dereferenceVal = dereference
+
+ new Param[Tc, T] {
+ type PType = P
+ def label: String = name
+ def default: Option[PType] = defaultVal
+ def typeclass: Tc[PType] = typeclassVal
+ def dereference(t: T): PType = dereferenceVal(t)
+ }
}
/** constructs a new [[CaseClass]] instance
@@ -472,13 +529,21 @@ object Magnolia {
* This method is intended to be called only from code generated by the Magnolia macro, and
* should not be called directly from users' code. */
def caseClass[Tc[_], T, ParamType](name: String,
- obj: Boolean,
- valClass: Boolean,
- params: Array[ParamType],
+ isCaseObject: Boolean,
+ isValueClass: Boolean,
+ parameters: Array[ParamType],
constructor: (ParamType => Any) => T) =
- new CaseClass[Tc, T, ParamType](name, obj, valClass, params) {
+ new CaseClass[Tc, T, ParamType](name, isCaseObject, isValueClass, parameters) {
def construct[R](param: ParamType => R): T = constructor(param)
}
+
+ /** constructs a new [[CaseClass]] instance
+ *
+ * This method is intended to be called only from code generated by the Magnolia macro, and
+ * should not be called directly from users' code. */
+ def sealedTrait[Tc[_], T, SubType](name: String,
+ subtypes: Array[Subtype[Tc, T]]): SealedTrait[Tc, T] =
+ new SealedTrait[Tc, T](name, subtypes)
}
private[magnolia] case class DirectlyReentrantException()
diff --git a/examples/shared/src/main/scala/eq.scala b/examples/shared/src/main/scala/eq.scala
index 6f00458..2b3d551 100644
--- a/examples/shared/src/main/scala/eq.scala
+++ b/examples/shared/src/main/scala/eq.scala
@@ -11,7 +11,6 @@ object Eq {
/** type constructor for the equality typeclass */
type Typeclass[T] = Eq[T]
- type ParamType[T, P] = Param[Eq, T] { type PType = P }
/** defines equality for this case class in terms of equality for all its parameters */
def combine[T](ctx: CaseClass[Eq, T, Param[Eq, T]]): Eq[T] = new Eq[T] {
diff --git a/examples/shared/src/main/scala/show.scala b/examples/shared/src/main/scala/show.scala
index a2b3c6b..c9ce09f 100644
--- a/examples/shared/src/main/scala/show.scala
+++ b/examples/shared/src/main/scala/show.scala
@@ -28,15 +28,32 @@ trait GenericShow[Out] {
* The first parameter is fixed as `String`, and the second parameter varies generically. */
type Typeclass[T] = Show[Out, T]
type ParamType[T, P] = ShowParam[Out, T, P]
+ type SubtypeType[T, P] = Subtype[Typeclass, T]
+
+ case class Derivation[T, P](name: String, isValueClass: Boolean, parameters: Seq[P])
def join(typeName: String, strings: Seq[String]): Out
- def param[T, P](name: String, typeclassParam: Show[Out, P], default: Option[P], deref: T => P)(implicit auto: Dflt[P]) =
- ShowParam[Out, T, P](name, typeclassParam, deref)
+ def param[T, P](name: String, typeclass: Show[Out, P], dereference: T => P)(implicit auto: Dflt[P]) =
+ ShowParam[Out, T, P](name, typeclass, dereference)
+
+ def caseClass[T, P](name: String, parameters: Array[P], isValueClass: Boolean): Derivation[T, P] =
+ Derivation(name, isValueClass, parameters)
+
+ /*def subtype[T, S <: T](name: String, typeclassParam: => Show[Out, S], isType: T => Boolean, asType: T => S): Subtype[Typeclass, T] =
+ new Subtype[Typeclass, T] {
+ type SType = S
+ def label = name
+ def typeclass = typeclassParam
+ def cast = new PartialFunction[T, S] {
+ def isDefinedAt(t: T) = isType(t)
+ def apply(t: T): SType = asType(t)
+ }
+ }*/
/** creates a new [[Show]] instance by labelling and joining (with `mkString`) the result of
* showing each parameter, and prefixing it with the class name */
- def combine[T](ctx: CaseClass[Typeclass, T, ParamType[T, _]]): Show[Out, T] = new Show[Out, T] {
+ def combine[T](ctx: Derivation[T, ShowParam[Out, T, _]]): Show[Out, T] = new Show[Out, T] {
def show(value: T) =
if (ctx.isValueClass) {
val param = ctx.parameters.head
@@ -46,7 +63,7 @@ trait GenericShow[Out] {
s"${param.label}=${param.show.show(param.dereference(value))}"
}
- join(ctx.typeName.split("\\.").last, paramStrings)
+ join(ctx.name.split("\\.").last, paramStrings)
}
}
diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala
index e453502..110e29d 100644
--- a/tests/src/main/scala/tests.scala
+++ b/tests/src/main/scala/tests.scala
@@ -39,7 +39,7 @@ case class `%%`(`/`: Int, `#`: String)
object Tests extends TestApp {
- def tests() = for (i <- 1 to 1000) {
+ def tests() = for(i <- 1 to 1000) {
import examples._
test("construct a Show product instance") {
@@ -176,5 +176,7 @@ object Tests extends TestApp {
test("serialize a value class") {
Show.gen[Length].show(new Length(100))
}.assert(_ == "100")
+
+ ()
}
}