From 4d957fd5a35f1d7eed8643b22132b65696c80c8a Mon Sep 17 00:00:00 2001 From: Jon Pretty Date: Fri, 3 Nov 2017 14:15:07 +0100 Subject: Test fixes and other miscellaneous changes --- build.sbt | 16 ++++++++- core/src/main/scala/magnolia.scala | 56 +++++++++++++++++++------------ examples/src/main/scala/typeclasses.scala | 29 ++++++++++++++++ tests/src/main/scala/adt.scala | 7 ++++ tests/src/main/scala/tests.scala | 10 +++--- 5 files changed, 90 insertions(+), 28 deletions(-) create mode 100644 tests/src/main/scala/adt.scala diff --git a/build.sbt b/build.sbt index 9fc7552..6ae8750 100644 --- a/build.sbt +++ b/build.sbt @@ -45,9 +45,23 @@ lazy val testsJVM = tests.jvm lazy val testsJS = tests.js lazy val testsNative = tests.native +lazy val benchmarks = crossProject(JSPlatform, JVMPlatform, NativePlatform) + .crossType(CrossType.Pure) + .in(file("benchmarks")) + .settings(buildSettings: _*) + .settings(noPublishSettings: _*) + .settings(moduleName := "magnolia-benchmarks") + .settings(quasiQuotesDependencies) + .nativeSettings(nativeSettings) + .dependsOn(examples) + +lazy val benchmarksJVM = benchmarks.jvm +lazy val benchmarksJS = benchmarks.js +lazy val benchmarksNative = benchmarks.native + lazy val buildSettings = Seq( organization := "com.propensive", - scalaVersion := "2.12.2", + scalaVersion := "2.12.4", name := "magnolia", version := "0.2.0", scalacOptions ++= Seq("-deprecation", "-feature", "-Ywarn-value-discard", "-Ywarn-dead-code", "-Ywarn-nullary-unit", "-Ywarn-numeric-widen", "-Ywarn-inaccessible", "-Ywarn-adapted-args"), diff --git a/core/src/main/scala/magnolia.scala b/core/src/main/scala/magnolia.scala index cabe88e..4d3d3d3 100644 --- a/core/src/main/scala/magnolia.scala +++ b/core/src/main/scala/magnolia.scala @@ -45,7 +45,7 @@ object Magnolia { recursionStack = recursionStack.updated( c.enclosingPosition, recursionStack.get(c.enclosingPosition).map(_.push(path, key, value)).getOrElse( - Stack(List(Frame(path, key, value)), Nil)) + Stack(Map(), List(Frame(path, key, value)), Nil)) ) try Some(fn) catch { case e: Exception => None } finally { @@ -73,17 +73,21 @@ object Magnolia { val methodAsString = methodName.encodedName.toString q"_root_.magnolia.Deferred.apply[$searchType]($methodAsString)" }.orElse { - scala.util.Try { - val genericTypeName: String = genericType.typeSymbol.name.encodedName.toString.toLowerCase - val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass")) - recurse(ChainedImplicit(genericType.toString), genericType, assignedName) { - val inferredImplicit = c.inferImplicitValue(searchType, false, false) - q"""{ - def $assignedName: $searchType = $inferredImplicit - $assignedName - }""" - }.get - }.toOption.orElse(directInferImplicit(genericType, typeConstructor)) + val (inferredImplicit, newStack) = recursionStack(c.enclosingPosition).lookup(c)(searchType) { + scala.util.Try { + val genericTypeName: String = genericType.typeSymbol.name.encodedName.toString.toLowerCase + val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass")) + recurse(ChainedImplicit(genericType.toString), genericType, assignedName) { + val inferredImplicit = c.inferImplicitValue(searchType, false, false) + q"""{ + def $assignedName: $searchType = $inferredImplicit + $assignedName + }""" + }.get + }.toOption.orElse(directInferImplicit(genericType, typeConstructor)) + } + recursionStack = recursionStack.updated(c.enclosingPosition, newStack) + inferredImplicit }.getOrElse { val currentStack: Stack = recursionStack(c.enclosingPosition) @@ -111,8 +115,6 @@ object Magnolia { val resultType = appliedType(typeConstructor, genericType) - println(s"Deriving $genericType") - // FIXME: Handle AnyVals if(isCaseObject) { val termSym = genericType.typeSymbol.companionSymbol @@ -162,6 +164,7 @@ object Magnolia { def label: _root_.java.lang.String = $label def dereference(param: ${genericType}): ${paramType} = param.${TermName(label)} }""" + } val constructor = q"""new $genericType(..${callables.zip(implicits).map { case (call, imp) => @@ -236,7 +239,7 @@ object Magnolia { val genericType: Type = weakTypeOf[T] val currentStack: Stack = - recursionStack.get(c.enclosingPosition).getOrElse(Stack(List(), List())) + recursionStack.get(c.enclosingPosition).getOrElse(Stack(Map(), List(), List())) val directlyReentrant = Some(genericType) == currentStack.frames.headOption.map(_.genericType) @@ -268,10 +271,11 @@ object Magnolia { if(currentStack.frames.isEmpty) recursionStack = ListMap() result.map { tree => - val out = if(currentStack.frames.isEmpty) c.untypecheck(removeDeferred.transform(tree)) - else tree - println(out) - out + if(currentStack.frames.isEmpty) { + val out = c.untypecheck(removeDeferred.transform(tree)) + //println(out) + out + } else tree }.getOrElse { c.abort(c.enclosingPosition, s"magnolia: could not infer typeclass for type $genericType") } @@ -297,13 +301,21 @@ private[magnolia] object CompileTimeState { case class ImplicitNotFound(genericType: String, path: List[TypePath]) - case class Stack(frames: List[Frame], errors: List[ImplicitNotFound]) { + case class Stack(cache: Map[whitebox.Context#Type, Option[whitebox.Context#Tree]], frames: List[Frame], errors: List[ImplicitNotFound]) { + def lookup(c: whitebox.Context)(t: c.Type)(orElse: => Option[c.Tree]): (Option[c.Tree], Stack) = + if(cache.contains(t)) { + (cache(t).asInstanceOf[Option[c.Tree]], this) + } else { + val value = orElse + (value, copy(cache.updated(t, value))) + } + def push(path: TypePath, key: whitebox.Context#Type, value: whitebox.Context#TermName): Stack = - Stack(Frame(path, key, value) :: frames, errors) + Stack(cache, Frame(path, key, value) :: frames, errors) - def pop(): Stack = Stack(frames.tail, errors) + def pop(): Stack = Stack(cache, frames.tail, errors) } case class Frame(path: TypePath, genericType: whitebox.Context#Type, diff --git a/examples/src/main/scala/typeclasses.scala b/examples/src/main/scala/typeclasses.scala index 440c5bc..b4af6d0 100644 --- a/examples/src/main/scala/typeclasses.scala +++ b/examples/src/main/scala/typeclasses.scala @@ -90,3 +90,32 @@ sealed trait Entity case class Company(name: String) extends Entity case class Person(name: String, age: Int) extends Entity case class Address(line1: String, occupant: Person) + + + +sealed trait Alphabet + +case class Greek(άλφα: Letter, βήτα: Letter, γάμα: Letter, δέλτα: Letter, έψιλον: Letter, ζήτα: Letter, ήτα: Letter, θήτα: Letter) extends Alphabet +case class Cyrillic(б: Letter, в: Letter, г: Letter, д: Letter, ж: Letter, з: Letter) extends Alphabet +case class Latin(a: Letter, b: Letter, c: Letter, d: Letter, e: Letter, f: Letter, g: Letter, h: Letter, i: Letter, j: Letter, k: Letter, l: Letter, m: Letter) extends Alphabet + +case class Letter(name: String, phonetic: String) +//case class Country(name: String, language: Language, leader: Person) +case class Language(name: String, code: String, alphabet: Alphabet) +//case class Person(name: String, dateOfBirth: Date) +case class Date(year: Int, month: Month, day: Int) + +sealed trait Month +case object Jan extends Month +case object Feb extends Month +case object Mar extends Month +case object Apr extends Month +case object May extends Month +case object Jun extends Month +case object Jul extends Month +case object Aug extends Month +case object Sep extends Month +case object Oct extends Month +case object Nov extends Month +case object Dec extends Month + diff --git a/tests/src/main/scala/adt.scala b/tests/src/main/scala/adt.scala new file mode 100644 index 0000000..75316aa --- /dev/null +++ b/tests/src/main/scala/adt.scala @@ -0,0 +1,7 @@ +package adt + +import magnolia._, examples._ + +object Gen { + Eq.generic[Alphabet] +} diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index aea6cc3..64d6594 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -28,7 +28,7 @@ object Tests extends TestApp { test("serialize a Branch") { import magnolia.examples._ implicitly[Show[String, Branch[String]]].show(Branch(Leaf("LHS"), Leaf("RHS"))) - }.assert(_ == "Branch(left=Leaf(value=LHS),right=Leaf(value=RHS))") + }.assert(_ == "Branch[String](left=Leaf[String](value=LHS),right=Leaf[String](value=RHS))") test("test equality false") { import examples._ @@ -52,9 +52,9 @@ object Tests extends TestApp { test("construction of Show instance for Leaf") { scalac""" import magnolia.examples._ - implicitly[Show[String, Leaf[String]]] + implicitly[Show[String, Leaf[java.lang.String]]] """ - }.assert(_ == (Returns(fqt"magnolia.examples.Show[String,magnolia.examples.Leaf[java.lang.String]]"): Compilation)) + }.assert(_ == (Returns(fqt"magnolia.examples.Show[String,magnolia.examples.Leaf[String]]"): Compilation)) test("construction of Show instance for Tree") { scalac""" @@ -65,11 +65,11 @@ object Tests extends TestApp { test("serialize a Leaf") { implicitly[Show[String, Leaf[String]]].show(Leaf("testing")) - }.assert(_ == "Leaf(value=testing)") + }.assert(_ == "Leaf[String](value=testing)") test("serialize a Branch as a Tree") { implicitly[Show[String, Tree[String]]].show(Branch(Leaf("LHS"), Leaf("RHS"))) - }.assert(_ == "Branch(left=Leaf(value=LHS),right=Leaf(value=RHS))") + }.assert(_ == "Branch[String](left=Leaf[String](value=LHS),right=Leaf[String](value=RHS))") test("show error stack") { scalac""" -- cgit v1.2.3