aboutsummaryrefslogtreecommitdiff
path: root/core/shared/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'core/shared/src/main')
-rw-r--r--core/shared/src/main/scala/interface.scala36
-rw-r--r--core/shared/src/main/scala/magnolia.scala70
2 files changed, 63 insertions, 43 deletions
diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala
index 193a6f9..b06f350 100644
--- a/core/shared/src/main/scala/interface.scala
+++ b/core/shared/src/main/scala/interface.scala
@@ -1,6 +1,7 @@
package magnolia
import language.higherKinds
+import scala.annotation.tailrec
/** represents a subtype of a sealed trait
*
@@ -122,13 +123,28 @@ 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
+ final def construct[Return](makeParam: Param[Typeclass, Type] => Return): Type =
+ rawConstruct(parameters map makeParam)
+
+ /** constructs a new instance of the case class type
+ *
+ * Like [[construct]] this method is implemented by Magnolia and let's you construct case class
+ * instances generically in user code, without knowing their type concretely.
+ *
+ * `rawConstruct`, however, is more low-level in that it expects you to provide a [[Seq]]
+ * containing all the field values for the case class type, in order and with the correct types.
+ *
+ * @param fieldValues contains the field values for the case class instance to be constructed,
+ * in order and with the correct types.
+ * @return a new instance of the case class
+ * @throws IllegalArgumentException if the size of `paramValues` differs from the size of [[parameters]] */
+ def rawConstruct(fieldValues: Seq[Any]): 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
+ final def parameters: Seq[Param[Typeclass, Type]] = parametersArray
}
/** represents a sealed trait and the context required to construct a new typeclass instance
@@ -157,12 +173,12 @@ final class SealedTrait[Typeclass[_], Type](val typeName: String,
* matches
* @return the result of applying the `handle` lambda to subtype of the sealed trait which
* matches the parameter `value` */
- def dispatch[Return](value: Type)(handle: Subtype[Typeclass, Type] => Return): Return =
- subtypes
- .map { sub =>
- sub.cast.andThen { v =>
- handle(sub)
- }
- }
- .reduce(_ orElse _)(value)
+ def dispatch[Return](value: Type)(handle: Subtype[Typeclass, Type] => Return): Return = {
+ @tailrec def rec(ix: Int): Return =
+ if (ix < subtypesArray.length) {
+ val sub = subtypesArray(ix)
+ if (sub.cast.isDefinedAt(value)) handle(sub) else rec(ix + 1)
+ } else throw new IllegalArgumentException(s"The given value `$value` is not a sub type of `$typeName`")
+ rec(0)
+ }
}
diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala
index 7e99192..200d38e 100644
--- a/core/shared/src/main/scala/magnolia.scala
+++ b/core/shared/src/main/scala/magnolia.scala
@@ -1,6 +1,8 @@
package magnolia
-import scala.reflect._, macros._
+import scala.util.control.NonFatal
+import scala.reflect._
+import macros._
import scala.collection.immutable.ListMap
import language.existentials
import language.higherKinds
@@ -102,7 +104,7 @@ object Magnolia {
)
}
val firstParamBlock = combineClass.asType.toType.decl(term).asTerm.asMethod.paramLists.head
- if (firstParamBlock.length != 1)
+ if (firstParamBlock.lengthCompare(1) != 0)
c.abort(c.enclosingPosition,
s"magnolia: the method `combine` should take a single parameter of type $expected")
}
@@ -114,9 +116,9 @@ object Magnolia {
def findType(key: Type): Option[TermName] =
recursionStack(c.enclosingPosition).frames.find(_.genericType == key).map(_.termName(c))
- case class Typeclass(typ: c.Type, tree: c.Tree)
+ final case class Typeclass(typ: c.Type, tree: c.Tree)
- def recurse[T](path: TypePath, key: Type, value: TermName)(fn: => T): Option[T] = {
+ def recurse[A](path: TypePath, key: Type, value: TermName)(fn: => A): Option[A] = {
val oldRecursionStack = recursionStack.get(c.enclosingPosition)
recursionStack = recursionStack.updated(
c.enclosingPosition,
@@ -126,7 +128,7 @@ object Magnolia {
)
try Some(fn)
- catch { case e: Exception => None } finally {
+ catch { case NonFatal(_) => None } finally {
val currentStack = recursionStack(c.enclosingPosition)
recursionStack = recursionStack.updated(c.enclosingPosition, currentStack.pop())
}
@@ -232,7 +234,7 @@ object Magnolia {
m.asMethod
}
- case class CaseParam(sym: c.universe.MethodSymbol,
+ final case class CaseParam(sym: c.universe.MethodSymbol,
repeated: Boolean,
typeclass: c.Tree,
paramType: c.Type,
@@ -273,7 +275,7 @@ object Magnolia {
val caseParams = caseParamsReversed.reverse
val paramsVal: TermName = TermName(c.freshName("parameters"))
- val fnVal: TermName = TermName(c.freshName("fn"))
+ val fieldValues: TermName = TermName(c.freshName("fieldValues"))
val preAssignments = caseParams.map(_.typeclass)
@@ -322,15 +324,18 @@ object Magnolia {
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
- }})
- ))
- }"""
- )
+ ($fieldValues: $scalaPkg.Seq[Any]) => {
+ if ($fieldValues.lengthCompare($paramsVal.length) != 0) {
+ val msg = "`" + $className + "` has " + $paramsVal.length + " fields, not " + $fieldValues.size
+ throw new java.lang.IllegalArgumentException(msg)
+ }
+ new $genericType(..${
+ caseParams.zipWithIndex.map { case (typeclass, idx) =>
+ val arg = q"$fieldValues($idx).asInstanceOf[${typeclass.paramType}]"
+ if (typeclass.repeated) q"$arg: _*" else arg
+ }
+ })}))
+ }""")
)
} else if (isSealedTrait) {
val genericSubtypes = classType.get.knownDirectSubclasses.to[List]
@@ -446,15 +451,14 @@ 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) =
- new Subtype[Tc, T] {
+ def subtype[Tc[_], T, S <: T](name: String, tc: => Tc[S], isType: T => Boolean, asType: T => S): Subtype[Tc, T] =
+ new Subtype[Tc, T] with PartialFunction[T, S] {
type SType = S
def label: String = name
def typeclass: Tc[SType] = tc
- def cast: PartialFunction[T, SType] = new PartialFunction[T, S] {
- def isDefinedAt(t: T) = isType(t)
- def apply(t: T): SType = asType(t)
- }
+ def cast: PartialFunction[T, SType] = this
+ def isDefinedAt(t: T) = isType(t)
+ def apply(t: T): SType = asType(t)
}
/** constructs a new [[Param]] instance
@@ -465,7 +469,7 @@ object Magnolia {
isRepeated: Boolean,
typeclassParam: Tc[P],
defaultVal: => Option[P],
- deref: T => P) = new Param[Tc, T] {
+ deref: T => P): Param[Tc, T] = new Param[Tc, T] {
type PType = P
def label: String = name
def repeated: Boolean = isRepeated
@@ -482,31 +486,31 @@ object Magnolia {
obj: Boolean,
valClass: Boolean,
params: Array[Param[Tc, T]],
- constructor: (Param[Tc, T] => Any) => T) =
+ constructor: Seq[Any] => T): CaseClass[Tc, T] =
new CaseClass[Tc, T](name, obj, valClass, params) {
- def construct[R](param: Param[Tc, T] => R): T = constructor(param)
+ def rawConstruct(fieldValues: Seq[Any]): T = constructor(fieldValues)
}
}
-private[magnolia] case class DirectlyReentrantException()
+private[magnolia] final case class DirectlyReentrantException()
extends Exception("attempt to recurse directly")
private[magnolia] object Deferred { def apply[T](method: String): T = ??? }
private[magnolia] object CompileTimeState {
- sealed class TypePath(path: String) { override def toString = path }
- case class CoproductType(typeName: String) extends TypePath(s"coproduct type $typeName")
+ sealed abstract class TypePath(path: String) { override def toString = path }
+ final case class CoproductType(typeName: String) extends TypePath(s"coproduct type $typeName")
- case class ProductType(paramName: String, typeName: String)
+ final case class ProductType(paramName: String, typeName: String)
extends TypePath(s"parameter '$paramName' of product type $typeName")
- case class ChainedImplicit(typeName: String)
+ final case class ChainedImplicit(typeName: String)
extends TypePath(s"chained implicit of type $typeName")
- case class ImplicitNotFound(genericType: String, path: List[TypePath])
+ final case class ImplicitNotFound(genericType: String, path: List[TypePath])
- case class Stack(cache: Map[whitebox.Context#Type, Option[whitebox.Context#Tree]],
+ final case class Stack(cache: Map[whitebox.Context#Type, Option[whitebox.Context#Tree]],
frames: List[Frame],
errors: List[ImplicitNotFound]) {
@@ -524,7 +528,7 @@ private[magnolia] object CompileTimeState {
def pop(): Stack = Stack(cache, frames.tail, errors)
}
- case class Frame(path: TypePath,
+ final case class Frame(path: TypePath,
genericType: whitebox.Context#Type,
term: whitebox.Context#TermName) {
def termName(c: whitebox.Context): c.TermName = term.asInstanceOf[c.TermName]