aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-11-10 17:13:18 +0000
committerJon Pretty <jon.pretty@propensive.com>2017-11-10 17:13:18 +0000
commitfd6a18d01218c52558508aca3424d4eb78be911c (patch)
tree75c027b34c2084eb1e13823256bf1316e05caadc
parentf5a5463fad814206c5131324e3aa8a00f6223545 (diff)
downloadmagnolia-fd6a18d01218c52558508aca3424d4eb78be911c.tar.gz
magnolia-fd6a18d01218c52558508aca3424d4eb78be911c.tar.bz2
magnolia-fd6a18d01218c52558508aca3424d4eb78be911c.zip
Include support for decomposing AnyVals, like case classes
-rw-r--r--core/src/main/scala/interface.scala1
-rw-r--r--core/src/main/scala/magnolia.scala44
-rw-r--r--examples/src/main/scala/show.scala5
-rw-r--r--tests/src/main/scala/tests.scala6
4 files changed, 38 insertions, 18 deletions
diff --git a/core/src/main/scala/interface.scala b/core/src/main/scala/interface.scala
index 30b473a..54f8ce3 100644
--- a/core/src/main/scala/interface.scala
+++ b/core/src/main/scala/interface.scala
@@ -94,6 +94,7 @@ trait Param[Typeclass[_], Type] {
abstract class CaseClass[Typeclass[_], Type] private[magnolia] (
val typeName: String,
val isObject: Boolean,
+ val isValueClass: Boolean,
parametersArray: Array[Param[Typeclass, Type]]
) {
diff --git a/core/src/main/scala/magnolia.scala b/core/src/main/scala/magnolia.scala
index 8c3d823..c29c1fa 100644
--- a/core/src/main/scala/magnolia.scala
+++ b/core/src/main/scala/magnolia.scala
@@ -177,7 +177,11 @@ object Magnolia {
val isCaseClass = classType.map(_.isCaseClass).getOrElse(false)
val isCaseObject = classType.map(_.isModuleClass).getOrElse(false)
val isSealedTrait = classType.map(_.isSealed).getOrElse(false)
- val isValueClass = genericType <:< typeOf[AnyVal]
+
+ val primitives = Set(typeOf[Double], typeOf[Float], typeOf[Short], typeOf[Byte],
+ typeOf[Int], typeOf[Long], typeOf[Char], typeOf[Boolean])
+
+ val isValueClass = genericType <:< typeOf[AnyVal] && !primitives.exists(_ =:= genericType)
val resultType = appliedType(typeConstructor, genericType)
@@ -188,13 +192,14 @@ object Magnolia {
val className = obj.name.decodedName.toString
val impl = q"""
${c.prefix}.combine($magnoliaObj.caseClass[$typeConstructor, $genericType](
- $className, true, new $arrayCls(0), _ => $obj)
+ $className, true, false, new $arrayCls(0), _ => $obj)
)
"""
Some(Typeclass(genericType, impl))
- } else if (isCaseClass) {
+ } else if (isCaseClass || isValueClass) {
val caseClassParameters = genericType.decls.collect {
- case m: MethodSymbol if m.isCaseAccessor => m.asMethod
+ case m: MethodSymbol if m.isCaseAccessor || (isValueClass && m.isParamAccessor) =>
+ m.asMethod
}
val className = genericType.typeSymbol.name.decodedName.toString
@@ -203,6 +208,7 @@ object Magnolia {
paramType: c.Type,
ref: c.TermName)
+
val caseParamsReversed: List[CaseParam] = caseClassParameters.foldLeft(List[CaseParam]()) {
case (acc, param) =>
val paramName = param.name.decodedName.toString
@@ -214,7 +220,7 @@ object Magnolia {
val caseParamOpt = predefinedRef.map { backRef =>
CaseParam(param, q"()", paramType, backRef.ref) :: acc
}
-
+
caseParamOpt.getOrElse {
val derivedImplicit =
recurse(ProductType(paramName, genericType.toString), genericType, assignedName) {
@@ -236,17 +242,19 @@ object Magnolia {
val preAssignments = caseParams.map(_.typeclass)
- val caseClassCompanion = genericType.companion
- val constructorMethod = caseClassCompanion.decl(TermName("apply")).asMethod
- val indexedConstructorParams = constructorMethod.paramLists.head.map(_.asTerm).zipWithIndex
-
- val defaults = indexedConstructorParams.map {
- case (p, idx) =>
- if (p.isParamWithDefault) {
- val method = TermName("apply$default$" + (idx + 1))
- q"_root_.scala.Some(${genericType.typeSymbol.companionSymbol.asTerm}.$method)"
- } else q"_root_.scala.None"
- }
+ val defaults = if(!isValueClass) {
+ val caseClassCompanion = genericType.companion
+ val constructorMethod = caseClassCompanion.decl(TermName("apply")).asMethod
+ val indexedConstructorParams = constructorMethod.paramLists.head.map(_.asTerm).zipWithIndex
+
+ indexedConstructorParams.map {
+ case (p, idx) =>
+ if (p.isParamWithDefault) {
+ val method = TermName("apply$default$" + (idx + 1))
+ q"_root_.scala.Some(${genericType.typeSymbol.companionSymbol.asTerm}.$method)"
+ } else q"_root_.scala.None"
+ }
+ } else List(q"_root_.scala.None")
val assignments = caseParams.zip(defaults).zipWithIndex.map {
case ((CaseParam(param, typeclass, paramType, ref), defaultVal), idx) =>
@@ -268,6 +276,7 @@ object Magnolia {
${c.prefix}.combine($magnoliaObj.caseClass[$typeConstructor, $genericType](
$className,
false,
+ $isValueClass,
$paramsVal,
($fnVal: Param[$typeConstructor, $genericType] => Any) =>
new $genericType(..${caseParams.zipWithIndex.map {
@@ -421,9 +430,10 @@ object Magnolia {
* 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, params) {
+ new CaseClass[Tc, T](name, obj, valClass, params) {
def construct[R](param: Param[Tc, T] => R): T = constructor(param)
}
}
diff --git a/examples/src/main/scala/show.scala b/examples/src/main/scala/show.scala
index e101c11..fdebb78 100644
--- a/examples/src/main/scala/show.scala
+++ b/examples/src/main/scala/show.scala
@@ -21,7 +21,10 @@ trait GenericShow[Out] {
/** 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 show(value: T) = {
+ def show(value: T) = if(ctx.isValueClass) {
+ val param = ctx.parameters.head
+ param.typeclass.show(param.dereference(value))
+ } else {
val paramStrings = ctx.parameters.map { param =>
s"${param.label}=${param.typeclass.show(param.dereference(value))}"
}
diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala
index ece1d32..242bd41 100644
--- a/tests/src/main/scala/tests.scala
+++ b/tests/src/main/scala/tests.scala
@@ -18,6 +18,8 @@ case class Company(name: String) extends Entity
case class Person(name: String, age: Int) extends Entity
case class Address(line1: String, occupant: Person)
+class Length(val value: Int) extends AnyVal
+
case class Lunchbox(fruit: Fruit, drink: String)
object Fruit {
import examples._
@@ -170,5 +172,9 @@ object Tests extends TestApp {
test("serialize a tuple") {
tupleDerivation().show((42, "Hello World"))
}.assert(_ == "Tuple2(_1=42,_2=Hello World)")
+
+ test("serialize a value class") {
+ Show.gen[Length].show(new Length(100))
+ }.assert(_ == "100")
}
}