diff --git a/NOTICE b/NOTICE index b3ef7149..2528ea60 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ scalaz-deriving -Copyright 2017 - 2018 Sam Halliday +Copyright 2017 - 2019 Sam Halliday This project is licensed under the terms of the LGPL 3.0 or any later version, see LICENSE. diff --git a/deriving-plugin/src/main/scala/scalaz/plugins/deriving/AnnotationPlugin.scala b/deriving-plugin/src/main/scala/scalaz/plugins/deriving/AnnotationPlugin.scala index d23bb0fc..5f4ec8d9 100644 --- a/deriving-plugin/src/main/scala/scalaz/plugins/deriving/AnnotationPlugin.scala +++ b/deriving-plugin/src/main/scala/scalaz/plugins/deriving/AnnotationPlugin.scala @@ -7,7 +7,7 @@ package scalaz.plugins.deriving import scala.Predef.ArrowAssoc import scala.deprecated -import scala.collection.immutable.Map +import scala.collection.immutable.{ Map, StringOps } import scala.collection.breakOut import scala.reflect.internal.util._ import scala.tools.nsc._ @@ -55,10 +55,50 @@ abstract class AnnotationPlugin(override val global: Global) extends Plugin { def isIde: Boolean = global.isInstanceOf[tools.nsc.interactive.Global] def isScaladoc: Boolean = global.isInstanceOf[tools.nsc.doc.ScaladocGlobal] + private case class PrettyPrinter( + level: Int, + inQuotes: Boolean, + backslashed: Boolean + ) { + val indent = List.fill(level)(" ").mkString + + def transform(char: Char): (PrettyPrinter, String) = { + val (pp, f): (PrettyPrinter, PrettyPrinter => String) = char match { + case '"' if inQuotes && !backslashed => + (copy(inQuotes = false), _ => s"$char") + case '"' if !inQuotes => (copy(inQuotes = true), _ => s"$char") + case '\\' if inQuotes && !backslashed => + (copy(backslashed = true), _ => s"$char") + + case ',' if !inQuotes => (this, p => s",\n${p.indent}") + case '(' if !inQuotes => + (copy(level = level + 1), p => s"(\n${p.indent}") + case ')' if !inQuotes => + (copy(level = level - 1), p => s"\n${p.indent})") + case _ => (this, _ => s"$char") + } + (pp, f(pp)) + } + } + + private def prettyPrint(raw: String): String = + new StringOps(raw) + .foldLeft((PrettyPrinter(0, false, false), new StringBuilder(""))) { + case ((pp, sb), char) => + val (newPP, res) = pp.transform(char) + (newPP, sb.append(res)) + } + ._2 + .toString + .replaceAll("""\(\s+\)""", "()") + + private def showTree(tree: Tree, pretty: Boolean): String = + if (pretty) prettyPrint(showRaw(tree)) else showRaw(tree) + // best way to inspect a tree, just call this - def debug(name: String, tree: Tree): Unit = + def debug(name: String, tree: Tree, pretty: Boolean = false): Unit = Predef.println( - s"====\n$name ${tree.id} ${tree.pos}:\n${showCode(tree)}\n${showRaw(tree)}" + s"====\n$name ${tree.id} ${tree.pos}:\n${showCode(tree)}\n${showTree(tree, pretty)}" ) // recovers the final part of an annotation @@ -139,6 +179,20 @@ abstract class AnnotationPlugin(override val global: Global) extends Plugin { case _ => false } + private def getConstructor(clazz: ClassDef): Option[DefDef] = + clazz.impl.collect { + case d @ DefDef(_, nme.CONSTRUCTOR, _, _, _, _) => d + }.headOption + + // A simple constructor is one with only one parameter group and no implicit parameters + private def hasSimpleConstructor(clazz: ClassDef): Boolean = + getConstructor(clazz) match { + case Some(DefDef(_, _, _, ps :: Nil, _, _)) + if !ps.exists(_.mods.hasFlag(Flag.IMPLICIT)) => + true + case _ => false + } + /** generates a zero-functionality companion for a class */ private def genCompanion(clazz: ClassDef): ModuleDef = { val mods = @@ -158,6 +212,7 @@ abstract class AnnotationPlugin(override val global: Global) extends Plugin { def sup = if (isCase && clazz.tparams.isEmpty && + hasSimpleConstructor(clazz) && addSuperFunction(clazz) && accessors.size <= 22) { AppliedTypeTree(