From 4d1e10dff683150b687c413b80e38ae0aba632c5 Mon Sep 17 00:00:00 2001 From: Jon Pretty Date: Mon, 12 Jun 2017 13:11:57 +0200 Subject: Improvements to error reporting --- build.sbt | 2 +- core/src/main/scala/magnolia.scala | 56 ++++++++++++++++++------- examples/src/main/scala/example.scala | 10 +++++ tests/shared/src/main/scala/magnolia/main.scala | 3 ++ 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/build.sbt b/build.sbt index 79f28a2..e943e73 100644 --- a/build.sbt +++ b/build.sbt @@ -48,7 +48,7 @@ lazy val buildSettings = Seq( organization := "com.propensive", scalaVersion := "2.12.2", name := "magnolia", - version := "2.0.0", + version := "0.1.0", scalacOptions ++= Seq("-deprecation", "-feature", "-Ywarn-value-discard", "-Ywarn-dead-code", "-Ywarn-nullary-unit", "-Ywarn-numeric-widen", "-Ywarn-inaccessible", "-Ywarn-adapted-args"), crossScalaVersions := Seq("2.10.6", "2.11.11", "2.12.2"), scmInfo := Some(ScmInfo(url("https://github.com/propensive/magnolia"), diff --git a/core/src/main/scala/magnolia.scala b/core/src/main/scala/magnolia.scala index 7342c67..08210f8 100644 --- a/core/src/main/scala/magnolia.scala +++ b/core/src/main/scala/magnolia.scala @@ -56,7 +56,7 @@ class Macros(val c: whitebox.Context) { scala.util.Try { val genericTypeName: String = genericType.typeSymbol.name.encodedName.toString.toLowerCase val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass")) - recurse(RecursiveCall(genericType.toString), genericType, assignedName) { + recurse(ChainedImplicit(genericType.toString), genericType, assignedName) { val inferredImplicit = c.inferImplicitValue(searchType, false, false) q"""{ def $assignedName: $searchType = $inferredImplicit @@ -96,8 +96,13 @@ class Macros(val c: whitebox.Context) { case m: MethodSymbol if m.isCaseAccessor => m.asMethod }.map { param => val paramName = param.name.encodedName.toString - val derivedImplicit = recurse(ProductType(paramName, genericType.toString), genericType, assignedName) { - getImplicit(Some(paramName), param.returnType, typeConstructor, assignedName, derivationImplicit) + + val derivedImplicit = recurse(ProductType(paramName, genericType.toString), genericType, + assignedName) { + + getImplicit(Some(paramName), param.returnType, typeConstructor, assignedName, + derivationImplicit) + }.getOrElse { c.abort(c.enclosingPosition, s"failed to get implicit for type $genericType") } @@ -137,9 +142,17 @@ class Macros(val c: whitebox.Context) { val reduction = components.reduce { (left, right) => q"$impl.combine($left, $right)" } q"$impl.call($reduction, sourceParameter)" case Right(impl) => - val parts = subtypes.zip(components) - parts.tail.foldLeft(q"$impl.call(${parts.head._2}, sourceParameter.asInstanceOf[${parts.head._1}])") { case (aggregated, (componentType, derivedImplicit)) => - q"if(sourceParameter.isInstanceOf[$componentType]) $impl.call($derivedImplicit, sourceParameter.asInstanceOf[$componentType]) else $aggregated" + val parts = subtypes.tail.zip(components.tail) + + val base = q""" + $impl.call(${components.head}, sourceParameter.asInstanceOf[${subtypes.head}]) + """ + + parts.foldLeft(base) { case (aggregated, (componentType, derivedImplicit)) => + q""" + if(sourceParameter.isInstanceOf[$componentType]) + $impl.call($derivedImplicit, sourceParameter.asInstanceOf[$componentType]) + else $aggregated""" } } } @@ -163,8 +176,8 @@ class Macros(val c: whitebox.Context) { def magnolia[T: WeakTypeTag, Typeclass: WeakTypeTag]: Tree = try { val genericType: Type = weakTypeOf[T] - val currentStack: List[Frame] = recursionStack.get(c.enclosingPosition).map(_.frames).getOrElse(List()) - val directlyReentrant = Some(genericType) == currentStack.headOption.map(_.genericType) + val currentStack: Stack = recursionStack.get(c.enclosingPosition).getOrElse(Stack(List(), List())) + val directlyReentrant = Some(genericType) == currentStack.frames.headOption.map(_.genericType) val typeConstructor: Type = weakTypeOf[Typeclass].typeConstructor val coDerivationTypeclass = weakTypeOf[CovariantDerivation[_]].typeConstructor @@ -180,8 +193,17 @@ class Macros(val c: whitebox.Context) { } if(directlyReentrant) throw DirectlyReentrantException() - - val result: Option[Tree] = if(!recursionStack.isEmpty) { + + currentStack.errors.foreach { error => + if(!emittedErrors.contains(error)) { + emittedErrors += error + val trace = error.path.mkString("\n in ", "\n in ", "\n \n") + val msg = s"could not derive ${typeConstructor} instance for type ${error.genericType}" + c.info(c.enclosingPosition, msg+trace, true) + } + } + + val result: Option[Tree] = if(!currentStack.frames.isEmpty) { findType(genericType) match { case None => directInferImplicit(genericType, typeConstructor, derivationImplicit) @@ -195,10 +217,10 @@ class Macros(val c: whitebox.Context) { directInferImplicit(genericType, typeConstructor, derivationImplicit) } - if(currentStack.isEmpty) recursionStack = Map() + if(currentStack.frames.isEmpty) recursionStack = Map() result.map { tree => - if(currentStack.isEmpty) { + if(currentStack.frames.isEmpty) { val res = c.untypecheck(removeLazy.transform(tree)) res } else tree @@ -228,8 +250,8 @@ private[magnolia] object CompileTimeState { override def toString = s"parameter '$paramName' of product type $typeName" } - case class RecursiveCall(typeName: String) extends TypePath { - override def toString = s"recursive implicit of type $typeName" + case class ChainedImplicit(typeName: String) extends TypePath { + override def toString = s"chained implicit of type $typeName" } case class ImplicitNotFound(genericType: String, path: List[TypePath]) @@ -250,14 +272,18 @@ private[magnolia] object CompileTimeState { private[magnolia] var recursionStack: Map[api.Position, Stack] = Map() + + private[magnolia] var emittedErrors: Set[ImplicitNotFound] = Set() } trait CovariantDerivation[Typeclass[_]] { type Value def dereference(value: Value, param: String): Value def call[T](typeclass: Typeclass[T], value: Value): T - def combine[Supertype, Right <: Supertype](left: Typeclass[_ <: Supertype], right: Typeclass[Right]): Typeclass[Supertype] def construct[T](body: Value => T): Typeclass[T] + + def combine[Supertype, Right <: Supertype](left: Typeclass[_ <: Supertype], + right: Typeclass[Right]): Typeclass[Supertype] } trait ContravariantDerivation[Typeclass[_]] { diff --git a/examples/src/main/scala/example.scala b/examples/src/main/scala/example.scala index 5dd1f47..afd202e 100644 --- a/examples/src/main/scala/example.scala +++ b/examples/src/main/scala/example.scala @@ -89,12 +89,22 @@ object `package` { implicit class Showable[T: Show](t: T) { def show: String = implicitly[Show[T]].show(t) } + implicit val showString: Show[String] = identity + implicit val showBool: Show[Boolean] = _.toString + implicit def showList[T: Show]: Show[List[T]] = xs => xs.map { x => s"list:${implicitly[Show[T]].show(x)}" }.mkString(";") + implicit def showSet[T: Show]: Show[Set[T]] = s => "set" } sealed trait Tree case class Branch(left: Tree, right: Tree) extends Tree case class Leaf(value: Int) extends Tree +sealed trait Entity +case class Person(name: String, address: Address) extends Entity +case class Organization(name: String, contacts: Set[Person]) extends Entity +case class Address(lines: List[String], country: Country) +case class Country(name: String, code: String, salesTax: Boolean) + trait Show[T] { def show(t: T): String } object Show extends Show_1 { implicit val showInt: Show[Int] = _.toString diff --git a/tests/shared/src/main/scala/magnolia/main.scala b/tests/shared/src/main/scala/magnolia/main.scala index 362a0fe..e826bca 100644 --- a/tests/shared/src/main/scala/magnolia/main.scala +++ b/tests/shared/src/main/scala/magnolia/main.scala @@ -5,6 +5,9 @@ import examples._ object Main { def main(args: Array[String]): Unit = { println(Branch(Branch(Leaf(1), Leaf(2)), Leaf(3)).show) + + println(List[Entity](Person("Jon Pretty", Address(List("Home"), Country("UK", "GBR", false)))).show) + } } -- cgit v1.2.3