aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.sbt13
-rw-r--r--core/shared/src/main/scala/interface.scala8
-rw-r--r--core/shared/src/main/scala/magnolia.scala84
-rw-r--r--examples/shared/src/main/scala/decode.scala2
-rw-r--r--project/plugins.sbt12
-rw-r--r--tests/src/main/scala/tests.scala13
6 files changed, 56 insertions, 76 deletions
diff --git a/build.sbt b/build.sbt
index 8a2a8c3..0239960 100644
--- a/build.sbt
+++ b/build.sbt
@@ -127,13 +127,12 @@ lazy val publishSettings = Seq(
)
)
-lazy val unmanagedSettings = unmanagedBase := (scalaVersion.value
- .split("\\.")
- .map(_.toInt)
- .to[List] match {
- case List(2, 12, _) => baseDirectory.value / "lib" / "2.12"
- case List(2, 11, _) => baseDirectory.value / "lib" / "2.11"
-})
+lazy val unmanagedSettings = unmanagedBase :=
+ baseDirectory.value / "lib" /
+ (CrossVersion.partialVersion(scalaVersion.value) match {
+ case Some((2, 11)) => "2.11"
+ case _ => "2.12"
+ })
lazy val scalaMacroDependencies: Seq[Setting[_]] = Seq(
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,
diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala
index 6eea9f1..af4fb51 100644
--- a/core/shared/src/main/scala/interface.scala
+++ b/core/shared/src/main/scala/interface.scala
@@ -21,7 +21,7 @@ import scala.annotation.tailrec
*
* @tparam Typeclass type constructor for the typeclass being derived
* @tparam Type generic type of this parameter */
-trait Subtype[Typeclass[_], Type] {
+trait Subtype[Typeclass[_], Type] extends Serializable {
/** the type of subtype */
type SType <: Type
@@ -47,7 +47,7 @@ trait Subtype[Typeclass[_], Type] {
*
* @tparam Typeclass type constructor for the typeclass being derived
* @tparam Type generic type of this parameter */
-trait Param[Typeclass[_], Type] {
+trait Param[Typeclass[_], Type] extends Serializable {
/** the type of the parameter being represented
*
@@ -136,7 +136,7 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] (
val isValueClass: Boolean,
parametersArray: Array[Param[Typeclass, Type]],
annotationsArray: Array[Any]
-) {
+) extends Serializable {
override def toString: String = s"CaseClass(${typeName.full}, ${parameters.mkString(",")})"
/** constructs a new instance of the case class type
@@ -196,7 +196,7 @@ final class SealedTrait[Typeclass[_], Type](
val typeName: TypeName,
subtypesArray: Array[Subtype[Typeclass, Type]],
annotationsArray: Array[Any]
-) {
+) extends Serializable {
override def toString: String = s"SealedTrait($typeName, Array[${subtypes.mkString(",")}])"
diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala
index b61b7d7..1ec6649 100644
--- a/core/shared/src/main/scala/magnolia.scala
+++ b/core/shared/src/main/scala/magnolia.scala
@@ -14,6 +14,8 @@
*/
package magnolia
+import scala.annotation.compileTimeOnly
+import scala.collection.breakOut
import scala.collection.mutable
import scala.language.existentials
import scala.language.higherKinds
@@ -95,42 +97,34 @@ object Magnolia {
val prefixObject = prefixType.typeSymbol
val prefixName = prefixObject.name.decodedName
+ def error(msg: String) = c.abort(c.enclosingPosition, msg)
+
val typeDefs = prefixType.baseClasses.flatMap { cls =>
cls.asType.toType.decls.filter(_.isType).find(_.name.toString == "Typeclass").map { tpe =>
tpe.asType.toType.asSeenFrom(prefixType, cls)
}
}
- val typeConstructor = typeDefs.headOption.fold {
- c.abort(
- c.enclosingPosition,
- s"magnolia: the derivation $prefixObject does not define the Typeclass type constructor"
- )
- }(_.typeConstructor)
+ val typeConstructor = typeDefs.headOption.fold(
+ error(s"magnolia: the derivation $prefixObject does not define the Typeclass type constructor")
+ )(_.typeConstructor)
def checkMethod(termName: String, category: String, expected: String): Unit = {
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 $prefixObject to derive typeclasses for $category"
- )
- }
+ .find(cls => cls.asType.toType.decl(term) != NoSymbol)
+ .getOrElse(error(s"magnolia: the method `$termName` must be defined on the derivation $prefixObject to derive typeclasses for $category"))
+
val firstParamBlock = combineClass.asType.toType.decl(term).asTerm.asMethod.paramLists.head
if (firstParamBlock.lengthCompare(1) != 0)
- c.abort(c.enclosingPosition,
- s"magnolia: the method `combine` should take a single parameter of type $expected")
+ error(s"magnolia: the method `combine` should take a single parameter of type $expected")
}
// FIXME: Only run these methods if they're used, particularly `dispatch`
checkMethod("combine", "case classes", "CaseClass[Typeclass, _]")
checkMethod("dispatch", "sealed traits", "SealedTrait[Typeclass, _]")
- val removeDeferred = new Transformer {
+ val expandDeferred = new Transformer {
override def transform(tree: Tree) = tree match {
case q"$magnolia.Deferred.apply[$_](${Literal(Constant(method: String))})"
if magnolia.symbol == magnoliaPkg =>
@@ -155,12 +149,11 @@ object Magnolia {
.filterNot(_.isEmpty)
.orElse(directInferImplicit(genericType, typeConstructor))
.getOrElse {
- val missingType = stack.top.fold(searchType)(_.searchType.asInstanceOf[Type])
+ val missingType = stack.top.fold(searchType)(_.searchType)
val typeClassName = s"${missingType.typeSymbol.name.decodedName}.Typeclass"
val genericType = missingType.typeArgs.head
val trace = stack.trace.mkString(" in ", "\n in ", "\n")
- c.abort(c.enclosingPosition,
- s"magnolia: could not find $typeClassName for type $genericType\n$trace")
+ error(s"magnolia: could not find $typeClassName for type $genericType\n$trace")
}
}
}
@@ -256,7 +249,7 @@ object Magnolia {
}
val ref = TermName(c.freshName("paramTypeclass"))
- val assigned = q"""lazy val $ref = $derivedImplicit"""
+ val assigned = q"""val $ref = $derivedImplicit"""
CaseParam(param, repeated, assigned, paramType, ref) :: acc
} { backRef =>
CaseParam(param, repeated, q"()", paramType, backRef.ref) :: acc
@@ -295,7 +288,7 @@ object Magnolia {
}
val assignments = caseParams.zip(defaults).zip(annotations).zipWithIndex.map {
- case (((CaseParam(param, repeated, typeclass, paramType, ref), defaultVal), annList), idx) =>
+ case (((CaseParam(param, repeated, _, paramType, ref), defaultVal), annList), idx) =>
q"""$paramsVal($idx) = $magnoliaPkg.Magnolia.param[$typeConstructor, $genericType,
$paramType](
${param.name.decodedName.toString},
@@ -333,7 +326,7 @@ object Magnolia {
}})}))
}""")
} else if (isSealedTrait) {
- val genericSubtypes = classType.get.knownDirectSubclasses.to[List]
+ val genericSubtypes = classType.get.knownDirectSubclasses.toList
val subtypes = genericSubtypes.map { sub =>
val subType = sub.asType.toType // FIXME: Broken for path dependent types
val typeParams = sub.asType.typeParams
@@ -349,7 +342,7 @@ object Magnolia {
s"magnolia: could not find any direct subtypes of $typeSymbol",
force = true)
- c.abort(c.enclosingPosition, "")
+ error("")
}
val subtypesVal: TermName = TermName(c.freshName("subtypes"))
@@ -401,12 +394,8 @@ object Magnolia {
val result = stack
.find(searchType)
- .map { enclosingRef =>
- q"$magnoliaPkg.Deferred[$searchType](${enclosingRef.toString})"
- }
- .orElse {
- directInferImplicit(genericType, typeConstructor)
- }
+ .map(enclosingRef => q"$magnoliaPkg.Deferred[$searchType](${enclosingRef.toString})")
+ .orElse(directInferImplicit(genericType, typeConstructor))
for (tree <- result) if (debug.isDefined && genericType.toString.contains(debug.get)) {
c.echo(c.enclosingPosition, s"Magnolia macro expansion for $genericType")
@@ -415,11 +404,10 @@ object Magnolia {
val dereferencedResult =
if (stack.nonEmpty) result
- else for (tree <- result) yield c.untypecheck(removeDeferred.transform(tree))
+ else for (tree <- result) yield c.untypecheck(expandDeferred.transform(tree))
dereferencedResult.getOrElse {
- c.abort(c.enclosingPosition,
- s"magnolia: could not infer $prefixName.Typeclass for type $genericType")
+ error(s"magnolia: could not infer $prefixName.Typeclass for type $genericType")
}
}
@@ -479,7 +467,8 @@ object Magnolia {
private[magnolia] final case class DirectlyReentrantException()
extends Exception("attempt to recurse directly")
-private[magnolia] object Deferred { def apply[T](method: String): T = ??? }
+@compileTimeOnly("magnolia.Deferred is used for derivation of recursive typeclasses")
+object Deferred { def apply[T](method: String): T = ??? }
private[magnolia] object CompileTimeState {
@@ -492,7 +481,7 @@ private[magnolia] object CompileTimeState {
final case class ChainedImplicit(typeClassName: String, typeName: String)
extends TypePath(s"chained implicit $typeClassName for type $typeName")
- final class Stack[C <: whitebox.Context] {
+ final class Stack[C <: whitebox.Context with Singleton] {
private var frames = List.empty[Frame]
private val cache = mutable.Map.empty[C#Type, C#Tree]
@@ -511,25 +500,18 @@ private[magnolia] object CompileTimeState {
case Frame(_, tpe, term) if tpe =:= searchType => term
}
- def recurse[T <: C#Tree](frame: Frame, searchType: C#Type)(fn: => T): T = {
+ def recurse[T <: C#Tree](frame: Frame, searchType: C#Type)(fn: => C#Tree): C#Tree = {
push(frame)
val result = cache.getOrElseUpdate(searchType, fn)
pop()
- result.asInstanceOf[T]
+ result
}
def trace: List[TypePath] =
- frames
- .drop(1)
- .foldLeft[(C#Type, List[TypePath])]((null, Nil)) {
- case ((_, Nil), frame) =>
- (frame.searchType, frame.path :: Nil)
- case (continue @ (tpe, acc), frame) =>
- if (tpe =:= frame.searchType) continue
- else (frame.searchType, frame.path :: acc)
- }
- ._2
- .reverse
+ (frames.drop(1), frames).zipped.collect {
+ case (Frame(path, tp1, _), Frame(_, tp2, _))
+ if !(tp1 =:= tp2) => path
+ } (breakOut)
override def toString: String =
frames.mkString("magnolia stack:\n", "\n", "\n")
@@ -538,7 +520,9 @@ private[magnolia] object CompileTimeState {
}
object Stack {
- private val global = new Stack[whitebox.Context]
+ // Cheating to satisfy Singleton bound (which improves type inference).
+ private val dummyContext: whitebox.Context = null
+ private val global = new Stack[dummyContext.type]
private val workSet = mutable.Set.empty[whitebox.Context#Symbol]
def withContext(c: whitebox.Context)(fn: Stack[c.type] => c.Tree): c.Tree = {
diff --git a/examples/shared/src/main/scala/decode.scala b/examples/shared/src/main/scala/decode.scala
index 5838905..16ccbcd 100644
--- a/examples/shared/src/main/scala/decode.scala
+++ b/examples/shared/src/main/scala/decode.scala
@@ -78,7 +78,7 @@ object Decoder {
}
def keyValue(str: String): (String, String) = {
- val List(label, value) = str.split("=", 2).to[List]
+ val List(label, value) = str.split("=", 2).toList
(label, value)
}
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 4db006e..5e8b926 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,8 +1,8 @@
-addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
-addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.1")
+addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1")
+addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
-addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.22")
-addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "0.3.1")
-addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.3.1")
-addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.6") \ No newline at end of file
+addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.23")
+addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.4.0")
+addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "0.4.0")
+addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.7")
diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala
index fc3a012..c3b90cc 100644
--- a/tests/src/main/scala/tests.scala
+++ b/tests/src/main/scala/tests.scala
@@ -43,10 +43,10 @@ class Length(val value: Int) extends AnyVal
case class FruitBasket(fruits: Fruit*)
case class Lunchbox(fruit: Fruit, drink: String)
+case class Fruit(name: String)
object Fruit {
implicit val showFruit: Show[String, Fruit] = (f: Fruit) => f.name
}
-case class Fruit(name: String)
case class Item(name: String, quantity: Int = 1, price: Int)
@@ -116,19 +116,16 @@ object Tests extends TestApp {
}.assert(_ == "Address(line1=Home,occupant=nobody)")
test("even low-priority implicit beats Magnolia for nested case") {
- import Show.gen
implicitly[Show[String, Lunchbox]].show(Lunchbox(Fruit("apple"), "lemonade"))
}.assert(_ == "Lunchbox(fruit=apple,drink=lemonade)")
- test("low-priority implicit does not beat Magnolia when not nested") {
- import Show.gen
+ test("low-priority implicit beats Magnolia when not nested") {
implicitly[Show[String, Fruit]].show(Fruit("apple"))
- }.assert(_ == "Fruit(name=apple)")
+ }.assert(_ == "apple")
- test("low-priority implicit does not beat Magnolia when chained") {
- import Show.gen
+ test("low-priority implicit beats Magnolia when chained") {
implicitly[Show[String, FruitBasket]].show(FruitBasket(Fruit("apple"), Fruit("banana")))
- }.assert(_ == "FruitBasket(fruits=[Fruit(name=apple),Fruit(name=banana)])")
+ }.assert(_ == "FruitBasket(fruits=[apple,banana])")
test("typeclass implicit scope has lower priority than ADT implicit scope") {
implicitly[Show[String, Fruit]].show(Fruit("apple"))