aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-11-24 14:26:17 +0000
committerJon Pretty <jon.pretty@propensive.com>2017-11-24 14:26:17 +0000
commitd20a3824ce7719deb328ffed002288bedc398f77 (patch)
tree3c5cb04da66710498759439f8e8c068a0e9502ac
parent192173d811f870e6e9c705c80118e27dc63a898b (diff)
downloadmagnolia-d20a3824ce7719deb328ffed002288bedc398f77.tar.gz
magnolia-d20a3824ce7719deb328ffed002288bedc398f77.tar.bz2
magnolia-d20a3824ce7719deb328ffed002288bedc398f77.zip
Use virtualized `param` if it is available
-rw-r--r--build.sbt7
-rw-r--r--core/shared/src/main/scala/interface.scala8
-rw-r--r--core/shared/src/main/scala/magnolia.scala83
-rw-r--r--examples/shared/src/main/scala/decode.scala3
-rw-r--r--examples/shared/src/main/scala/default.scala3
-rw-r--r--examples/shared/src/main/scala/eq.scala3
-rw-r--r--examples/shared/src/main/scala/package.scala8
-rw-r--r--examples/shared/src/main/scala/show.scala22
8 files changed, 82 insertions, 55 deletions
diff --git a/build.sbt b/build.sbt
index 178ac83..f127515 100644
--- a/build.sbt
+++ b/build.sbt
@@ -25,13 +25,6 @@ lazy val tests = project
.settings(buildSettings: _*)
.settings(unmanagedSettings)
.settings(moduleName := "magnolia-tests")
- .settings(
- addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full),
- libraryDependencies ++= Seq(
- "com.fommil" %% "deriving-macro" % "0.9.0",
- "com.fommil" %% "scalaz-deriving" % "0.9.0"
- )
- )
.dependsOn(examplesJVM)
lazy val benchmarks = project
diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala
index 54f8ce3..25dd1fd 100644
--- a/core/shared/src/main/scala/interface.scala
+++ b/core/shared/src/main/scala/interface.scala
@@ -91,11 +91,11 @@ trait Param[Typeclass[_], Type] {
* @param parametersArray an array of [[Param]] values for this case class
* @tparam Typeclass type constructor for the typeclass being derived
* @tparam Type generic type of this parameter */
-abstract class CaseClass[Typeclass[_], Type] private[magnolia] (
+abstract class CaseClass[Typeclass[_], Type, ParamType] private[magnolia] (
val typeName: String,
val isObject: Boolean,
val isValueClass: Boolean,
- parametersArray: Array[Param[Typeclass, Type]]
+ parametersArray: Array[ParamType]
) {
/** constructs a new instance of the case class type
@@ -111,13 +111,13 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] (
* @param makeParam lambda for converting a generic [[Param]] into the value to be used for
* this parameter in the construction of a new instance of the case class
* @return a new instance of the case class */
- def construct[Return](makeParam: Param[Typeclass, Type] => Return): Type
+ def construct[Return](makeParam: ParamType => Return): Type
/** a sequence of [[Param]] objects representing all of the parameters in the case class
*
* For efficiency, this sequence is implemented by an `Array`, but upcast to a
* [[scala.collection.Seq]] to hide the mutable collection API. */
- def parameters: Seq[Param[Typeclass, Type]] = parametersArray
+ def parameters: Seq[ParamType] = parametersArray
}
/** represents a sealed trait and the context required to construct a new typeclass instance
diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala
index 6400002..0e5728c 100644
--- a/core/shared/src/main/scala/magnolia.scala
+++ b/core/shared/src/main/scala/magnolia.scala
@@ -81,41 +81,44 @@ object Magnolia {
else q"${tpe.typeSymbol.name.toTermName}"
}
- val typeDefs = prefixType.baseClasses.flatMap { cls =>
- cls.asType.toType.decls.filter(_.isType).find(_.name.toString == "Typeclass").map { tpe =>
- tpe.asType.toType.asSeenFrom(prefixType, cls)
+
+ def getTypeMember(name: String) = {
+ val typeDefs = prefixType.baseClasses.flatMap { cls =>
+ cls.asType.toType.decls.filter(_.isType).find(_.name.toString == name).map { tpe =>
+ tpe.asType.toType.asSeenFrom(prefixType, cls)
+ }
}
- }
- val typeConstructorOpt =
- typeDefs.headOption.map(_.typeConstructor)
+ val typeConstructorOpt = typeDefs.headOption.map(_.typeConstructor)
- val typeConstructor = typeConstructorOpt.getOrElse {
- c.abort(c.enclosingPosition,
- "magnolia: the derivation object does not define the Typeclass type constructor")
+ typeConstructorOpt.getOrElse {
+ c.abort(c.enclosingPosition,
+ s"magnolia: the derivation object does not define the $name type constructor")
+ }
}
- def checkMethod(termName: String, category: String, expected: String) = {
+ 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
}
- .getOrElse {
- c.abort(
- c.enclosingPosition,
- s"magnolia: the method `$termName` must be defined on the derivation object to derive typeclasses for $category"
- )
- }
- val firstParamBlock = combineClass.asType.toType.decl(term).asTerm.asMethod.paramLists.head
- if (firstParamBlock.length != 1)
- c.abort(c.enclosingPosition,
- s"magnolia: the method `combine` should take a single parameter of type $expected")
+ combineClass.map { c =>
+ c.asType.toType.decl(term).asTerm.asMethod
+ }
}
// FIXME: Only run these methods if they're used, particularly `dispatch`
- checkMethod("combine", "case classes", "CaseClass[Typeclass, _]")
- checkMethod("dispatch", "sealed traits", "SealedTrait[Typeclass, _]")
+ getMethod("combine").getOrElse(c.abort(c.enclosingPosition,
+ s"magnolia: the method `dispatch` should take a single parameter of type CaseClass[Typeclass, _]")
+ )
+
+ getMethod("dispatch").getOrElse(c.abort(c.enclosingPosition,
+ s"magnolia: the method `combine` should take a single parameter of type SealedTrait[Typeclass, _]")
+ )
def findType(key: Type): Option[TermName] =
recursionStack(c.enclosingPosition).frames.find(_.genericType == key).map(_.termName(c))
@@ -226,8 +229,12 @@ object Magnolia {
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](
+ ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType, $paramType](
$className, true, false, new $scalaPkg.Array(0), _ => $obj)
)
"""
@@ -294,10 +301,10 @@ object Magnolia {
val assignments = caseParams.zip(defaults).zipWithIndex.map {
case ((CaseParam(param, typeclass, paramType, ref), defaultVal), idx) =>
- q"""$paramsVal($idx) = $magnoliaPkg.Magnolia.param[$typeConstructor, $genericType,
- $paramType](
- ${param.name.decodedName.toString}, $ref, $defaultVal, _.${param.name}
- )"""
+ val paramMethod: Tree = getMethod("param").map { sym =>
+ q"${c.prefix}.param[$genericType, $paramType]"
+ }.getOrElse(q"$magnoliaPkg.Magnolia.param[$typeConstructor, $genericType, $paramType]")
+ q"""$paramsVal($idx) = $paramMethod(${param.name.decodedName.toString}, $ref, $defaultVal, _.${param.name})"""
}
Some(
@@ -305,20 +312,20 @@ object Magnolia {
genericType,
q"""{
..$preAssignments
- val $paramsVal: $scalaPkg.Array[$magnoliaPkg.Param[$typeConstructor, $genericType]] =
+ val $paramsVal: $scalaPkg.Array[${c.prefix}.ParamType[$genericType, _]] =
new $scalaPkg.Array(${assignments.length})
..$assignments
- ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType](
+ ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType, ${c.prefix}.ParamType[$genericType, _]](
$className,
false,
$isValueClass,
$paramsVal,
- ($fnVal: $magnoliaPkg.Param[$typeConstructor, $genericType] => Any) =>
+ ($fnVal: ${c.prefix}.ParamType[$genericType, _] => Any) =>
new $genericType(..${caseParams.zipWithIndex.map {
- case (typeclass, idx) =>
- q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
- }})
+ case (typeclass, idx) =>
+ q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
+ }})
))
}"""
)
@@ -464,13 +471,13 @@ 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](name: String,
+ def caseClass[Tc[_], T, ParamType](name: String,
obj: Boolean,
valClass: Boolean,
- params: Array[Param[Tc, T]],
- constructor: (Param[Tc, T] => Any) => T) =
- new CaseClass[Tc, T](name, obj, valClass, params) {
- def construct[R](param: Param[Tc, T] => R): T = constructor(param)
+ params: Array[ParamType],
+ constructor: (ParamType => Any) => T) =
+ new CaseClass[Tc, T, ParamType](name, obj, valClass, params) {
+ def construct[R](param: ParamType => R): T = constructor(param)
}
}
diff --git a/examples/shared/src/main/scala/decode.scala b/examples/shared/src/main/scala/decode.scala
index 5b083bd..14ab671 100644
--- a/examples/shared/src/main/scala/decode.scala
+++ b/examples/shared/src/main/scala/decode.scala
@@ -22,9 +22,10 @@ object Decoder {
/** type constructor for new instances of the typeclass */
type Typeclass[T] = Decoder[T]
+ type ParamType[T, P] = Param[Decoder, T] { type PType = P }
/** defines how new [[Decoder]]s for case classes should be constructed */
- def combine[T](ctx: CaseClass[Decoder, T]): Decoder[T] = new Decoder[T] {
+ def combine[T](ctx: CaseClass[Decoder, T, Param[Decoder, T]]): Decoder[T] = new Decoder[T] {
def decode(value: String) = {
val (name, values) = parse(value)
ctx.construct { param =>
diff --git a/examples/shared/src/main/scala/default.scala b/examples/shared/src/main/scala/default.scala
index 4c1b634..aa9dedc 100644
--- a/examples/shared/src/main/scala/default.scala
+++ b/examples/shared/src/main/scala/default.scala
@@ -10,10 +10,11 @@ trait Default[T] { def default: T }
object Default {
type Typeclass[T] = Default[T]
+ type ParamType[T, P] = Param[Default, T] { type PType = P }
/** constructs a default for each parameter, using the constructor default (if provided),
* otherwise using a typeclass-provided default */
- def combine[T](ctx: CaseClass[Default, T]): Default[T] = new Default[T] {
+ def combine[T](ctx: CaseClass[Default, T, Param[Default, T]]): Default[T] = new Default[T] {
def default = ctx.construct { param =>
param.default.getOrElse(param.typeclass.default)
}
diff --git a/examples/shared/src/main/scala/eq.scala b/examples/shared/src/main/scala/eq.scala
index 8ee42a4..6f00458 100644
--- a/examples/shared/src/main/scala/eq.scala
+++ b/examples/shared/src/main/scala/eq.scala
@@ -11,9 +11,10 @@ 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]): Eq[T] = new Eq[T] {
+ def combine[T](ctx: CaseClass[Eq, T, Param[Eq, T]]): Eq[T] = new Eq[T] {
def equal(value1: T, value2: T) = ctx.parameters.forall { param =>
param.typeclass.equal(param.dereference(value1), param.dereference(value2))
}
diff --git a/examples/shared/src/main/scala/package.scala b/examples/shared/src/main/scala/package.scala
new file mode 100644
index 0000000..c196dbc
--- /dev/null
+++ b/examples/shared/src/main/scala/package.scala
@@ -0,0 +1,8 @@
+package magnolia
+
+package object examples {
+ implicit class Extensions[T](t: T) {
+ def show(implicit show: Show[String, T]): String = show.show(t)
+ def ===[S >: T: Eq](that: S): Boolean = implicitly[Eq[S]].equal(t, that)
+ }
+}
diff --git a/examples/shared/src/main/scala/show.scala b/examples/shared/src/main/scala/show.scala
index 50b34ee..a2b3c6b 100644
--- a/examples/shared/src/main/scala/show.scala
+++ b/examples/shared/src/main/scala/show.scala
@@ -9,25 +9,41 @@ import scala.language.experimental.macros
* be something other than a string. */
trait Show[Out, T] { def show(value: T): Out }
+case class ShowParam[Out, T, P](label: String, typeclass: Show[Out, P], deref: T => P) {
+ type PType = P
+ def show: Show[Out, PType] = typeclass
+ def dereference(t: T): PType = deref(t)
+}
+
+object Dflt {
+ implicit def any[T]: Dflt[T] = new Dflt[T] { def default: T = null.asInstanceOf[T] }
+}
+
+trait Dflt[T] { def default: T }
+
trait GenericShow[Out] {
/** the type constructor for new [[Show]] instances
*
* 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]
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)
+
/** 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]): Show[Out, T] = new Show[Out, T] {
+ def combine[T](ctx: CaseClass[Typeclass, T, ParamType[T, _]]): Show[Out, T] = new Show[Out, T] {
def show(value: T) =
if (ctx.isValueClass) {
val param = ctx.parameters.head
- param.typeclass.show(param.dereference(value))
+ param.show.show(param.dereference(value))
} else {
val paramStrings = ctx.parameters.map { param =>
- s"${param.label}=${param.typeclass.show(param.dereference(value))}"
+ s"${param.label}=${param.show.show(param.dereference(value))}"
}
join(ctx.typeName.split("\\.").last, paramStrings)