diff options
author | Jon Pretty <jon.pretty@propensive.com> | 2017-11-24 14:26:17 +0000 |
---|---|---|
committer | Jon Pretty <jon.pretty@propensive.com> | 2017-11-24 14:26:17 +0000 |
commit | d20a3824ce7719deb328ffed002288bedc398f77 (patch) | |
tree | 3c5cb04da66710498759439f8e8c068a0e9502ac | |
parent | 192173d811f870e6e9c705c80118e27dc63a898b (diff) | |
download | magnolia-d20a3824ce7719deb328ffed002288bedc398f77.tar.gz magnolia-d20a3824ce7719deb328ffed002288bedc398f77.tar.bz2 magnolia-d20a3824ce7719deb328ffed002288bedc398f77.zip |
Use virtualized `param` if it is available
-rw-r--r-- | build.sbt | 7 | ||||
-rw-r--r-- | core/shared/src/main/scala/interface.scala | 8 | ||||
-rw-r--r-- | core/shared/src/main/scala/magnolia.scala | 83 | ||||
-rw-r--r-- | examples/shared/src/main/scala/decode.scala | 3 | ||||
-rw-r--r-- | examples/shared/src/main/scala/default.scala | 3 | ||||
-rw-r--r-- | examples/shared/src/main/scala/eq.scala | 3 | ||||
-rw-r--r-- | examples/shared/src/main/scala/package.scala | 8 | ||||
-rw-r--r-- | examples/shared/src/main/scala/show.scala | 22 |
8 files changed, 82 insertions, 55 deletions
@@ -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) |