aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/shared/src/main/scala/interface.scala8
-rw-r--r--core/shared/src/main/scala/magnolia.scala255
2 files changed, 169 insertions, 94 deletions
diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala
index 193a6f9..5043680 100644
--- a/core/shared/src/main/scala/interface.scala
+++ b/core/shared/src/main/scala/interface.scala
@@ -102,11 +102,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
@@ -122,13 +122,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 f55dfa7..9f3bda8 100644
--- a/core/shared/src/main/scala/magnolia.scala
+++ b/core/shared/src/main/scala/magnolia.scala
@@ -84,41 +84,40 @@ 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): Unit = {
+ val typeConstructor = getTypeMember("Typeclass")
+
+ def getMethod(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")
+ val cls = c.prefix.tree.tpe.baseClasses.find(_.asType.toType.decl(term) != NoSymbol)
+ cls.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))
@@ -254,14 +253,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.exists(_.isCaseClass)
- val isCaseObject = classType.exists(_.isModuleClass)
- val isSealedTrait = classType.exists(_.isSealed)
-
- 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.exists(_.isCaseClass)
+ lazy val isCaseObject = classType.exists(_.isModuleClass)
+ lazy val isSealedTrait = classType.exists(_.isSealed)
+
+ lazy val primitives = Set(typeOf[Double],
typeOf[Float],
typeOf[Short],
typeOf[Byte],
@@ -271,20 +270,48 @@ object Magnolia {
typeOf[Boolean],
typeOf[Unit])
- 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 className = s"${genericType.typeSymbol.owner.fullName}.${genericType.typeSymbol.name.decodedName}"
val result = if (isCaseObject) {
val obj = companionRef(genericType)
- val impl = q"""
- ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType](
- $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 {
@@ -361,33 +388,52 @@ object Magnolia {
val assignments = caseParams.zip(defaults).zipWithIndex.map {
case ((CaseParam(param, repeated, typeclass, paramType, ref), defaultVal), idx) =>
- q"""$paramsVal($idx) = $magnoliaPkg.Magnolia.param[$typeConstructor, $genericType,
- $paramType](
- ${param.name.decodedName.toString}, $repeated, $ref, $defaultVal, _.${param.name}
- )"""
+ val paramMethod: Tree = getMethod("param").map { sym =>
+ q"${c.prefix}.param"
+ }.getOrElse(q"$magnoliaPkg.Magnolia.param[$typeConstructor, $genericType, $paramType]")
+
+ val paramNames = getMethod("param").map { sym =>
+ sym.paramLists.head.map(_.name.decodedName.toString)
+ }.getOrElse(List("name", "repeated", "typeclass", "default", "dereference"))
+
+ val parameters: List[Tree] = paramNames.map {
+ case "name" => q"${param.name.decodedName.toString}"
+ case "repeated" => q"$repeated"
+ 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: default, dereference, name, repeated, typeclass")
+ }
+ 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) =>
+ val arg = q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
+ if (typeclass.repeated) q"$arg: _*" else arg
+ }})
+ """
+ }
+
+ val impl = q"${c.prefix}.combine($caseClassMethod(..$parameters))"
Some(
Typeclass(
genericType,
q"""{
..$preAssignments
- val $paramsVal: $scalaPkg.Array[$magnoliaPkg.Param[$typeConstructor, $genericType]] =
+ val $paramsVal: $scalaPkg.Array[$liftedParamType] =
new $scalaPkg.Array(${assignments.length})
..$assignments
- ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType](
- $className,
- false,
- $isValueClass,
- $paramsVal,
- ($fnVal: $magnoliaPkg.Param[$typeConstructor, $genericType] => Any) =>
- new $genericType(..${caseParams.zipWithIndex.map {
- case (typeclass, idx) =>
- val arg = q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
- if (typeclass.repeated) q"$arg: _*" else arg
- }})
- ))
+ ${c.prefix}.combine($caseClassMethod(..$parameters))
}"""
)
)
@@ -422,28 +468,40 @@ object Magnolia {
}
val assignments = typeclasses.zipWithIndex.map {
- case ((typ, typeclass), idx) =>
- q"""$subtypesVal($idx) = $magnoliaPkg.Magnolia.subtype[$typeConstructor, $genericType, $typ](
- ${s"${typ.typeSymbol.owner.fullName}.${typ.typeSymbol.name.decodedName}"},
- $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"""${s"${genericType.typeSymbol.owner.fullName}.${genericType.typeSymbol.name.decodedName}"}"""
+ 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(
- $className,
- $subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]])
- ): $resultType
+ ${c.prefix}.dispatch($sealedTraitMethod(..$parameters)): $resultType
}"""
)
}
@@ -452,7 +510,7 @@ object Magnolia {
result.map {
case Typeclass(t, r) =>
Typeclass(t, q"""{
- def $assignedName: $resultType = $r
+ lazy val $assignedName: $resultType = $r
$assignedName
}""")
}
@@ -505,46 +563,63 @@ 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,
- isRepeated: Boolean,
- typeclassParam: Tc[P],
- defaultVal: => Option[P],
- deref: T => P) = new Param[Tc, T] {
- type PType = P
- def label: String = name
- def repeated: Boolean = isRepeated
- def default: Option[PType] = defaultVal
- def typeclass: Tc[PType] = typeclassParam
- def dereference(t: T): PType = deref(t)
+ repeated: Boolean,
+ typeclass: Tc[P],
+ default: => Option[P],
+ dereference: T => P) = {
+ val typeclassVal = typeclass
+ val defaultVal = default
+ val dereferenceVal = dereference
+ val repeatedVal = repeated
+
+ new Param[Tc, T] {
+ type PType = P
+ def label: String = name
+ def repeated: Boolean = repeatedVal
+ def default: Option[PType] = defaultVal
+ def typeclass: Tc[PType] = typeclassVal
+ def dereference(t: T): PType = dereferenceVal(t)
+ }
}
/** 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 caseClass[Tc[_], T](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)
+ def caseClass[Tc[_], T, ParamType](name: String,
+ isCaseObject: Boolean,
+ isValueClass: Boolean,
+ parameters: Array[ParamType],
+ constructor: (ParamType => Any) => T) =
+ 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()