diff options
Diffstat (limited to 'core/shared/src')
-rw-r--r-- | core/shared/src/main/scala/interface.scala | 36 | ||||
-rw-r--r-- | core/shared/src/main/scala/magnolia.scala | 70 |
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] |