From 1205d14e99f46d75ac3a88732a4d6c03e641dad0 Mon Sep 17 00:00:00 2001 From: David Dudson Date: Thu, 25 Jan 2018 16:02:44 +1300 Subject: [PATCH 1/3] Add typeclass extractors. Horrible but necessary --- .../scala/meta/gen/TypeclassExtractors.scala | 106 ++++++++++++++++++ .../meta/gen/TypeclassExtractorTest.scala | 49 ++++++++ 2 files changed, 155 insertions(+) create mode 100644 scalagen/src/main/scala/scala/meta/gen/TypeclassExtractors.scala create mode 100644 scalagen/src/test/scala/scala/meta/gen/TypeclassExtractorTest.scala diff --git a/scalagen/src/main/scala/scala/meta/gen/TypeclassExtractors.scala b/scalagen/src/main/scala/scala/meta/gen/TypeclassExtractors.scala new file mode 100644 index 0000000..9c92396 --- /dev/null +++ b/scalagen/src/main/scala/scala/meta/gen/TypeclassExtractors.scala @@ -0,0 +1,106 @@ +package scala.meta.gen + +import scala.meta._ +import scala.meta.gen._ +import scala.meta.contrib._ + +/** This is a big hack of a class. + * We have to retrieve instances at runtime based in input type + */ +// TODO move to scalameta/contrib +object TypeclassExtractors { + + def retrieveModExtractInstance[A](a: A): Option[Extract[A, Mod]] = { + val res = + a match { + case _: Defn.Class => Option(implicitly[Extract[Defn.Class, Mod]]) + case _: Defn.Type => Option(implicitly[Extract[Defn.Type, Mod]]) + case _: Defn.Trait => Option(implicitly[Extract[Defn.Trait, Mod]]) + case _: Defn.Object => Option(implicitly[Extract[Defn.Object, Mod]]) + case _: Defn.Def => Option(implicitly[Extract[Defn.Def, Mod]]) + case _: Defn.Val => Option(implicitly[Extract[Defn.Val, Mod]]) + case _: Defn.Var => Option(implicitly[Extract[Defn.Var, Mod]]) + case _: Decl.Val => Option(implicitly[Extract[Decl.Val, Mod]]) + case _: Decl.Var => Option(implicitly[Extract[Decl.Var, Mod]]) + case _: Decl.Def => Option(implicitly[Extract[Decl.Def, Mod]]) + case _: Decl.Type => Option(implicitly[Extract[Decl.Type, Mod]]) + case _ => None + } + + res.asInstanceOf[Option[Extract[A, Mod]]] + } + + def retrieveModReplaceInstance[A](a: A): Option[Replace[A, Mod]] = { + val res = + a match { + case _: Defn.Class => Option(implicitly[Replace[Defn.Class, Mod]]) + case _: Defn.Type => Option(implicitly[Replace[Defn.Type, Mod]]) + case _: Defn.Trait => Option(implicitly[Replace[Defn.Trait, Mod]]) + case _: Defn.Object => Option(implicitly[Replace[Defn.Object, Mod]]) + case _: Defn.Def => Option(implicitly[Replace[Defn.Def, Mod]]) + case _: Defn.Val => Option(implicitly[Replace[Defn.Val, Mod]]) + case _: Defn.Var => Option(implicitly[Replace[Defn.Var, Mod]]) + case _: Decl.Val => Option(implicitly[Replace[Decl.Val, Mod]]) + case _: Decl.Var => Option(implicitly[Replace[Decl.Var, Mod]]) + case _: Decl.Def => Option(implicitly[Replace[Decl.Def, Mod]]) + case _: Decl.Type => Option(implicitly[Replace[Decl.Type, Mod]]) + case _ => None + } + + res.asInstanceOf[Option[Replace[A, Mod]]] + } + + def retrieveAnnotExtractInstance[A](a: A): Option[Extract[A, Mod.Annot]] = { + val res = + a match { + case _: Defn.Class => Option(implicitly[Extract[Defn.Class, Mod.Annot]]) + case _: Defn.Type => Option(implicitly[Extract[Defn.Type, Mod.Annot]]) + case _: Defn.Trait => Option(implicitly[Extract[Defn.Trait, Mod.Annot]]) + case _: Defn.Object => Option(implicitly[Extract[Defn.Object, Mod.Annot]]) + case _: Defn.Def => Option(implicitly[Extract[Defn.Def, Mod.Annot]]) + case _: Defn.Val => Option(implicitly[Extract[Defn.Val, Mod.Annot]]) + case _: Defn.Var => Option(implicitly[Extract[Defn.Var, Mod.Annot]]) + case _: Decl.Val => Option(implicitly[Extract[Decl.Val, Mod.Annot]]) + case _: Decl.Var => Option(implicitly[Extract[Decl.Var, Mod.Annot]]) + case _: Decl.Def => Option(implicitly[Extract[Decl.Def, Mod.Annot]]) + case _: Decl.Type => Option(implicitly[Extract[Decl.Type, Mod.Annot]]) + case _ => None + } + + res.asInstanceOf[Option[Extract[A, Mod.Annot]]] + } + + def retrieveStatReplaceInstance[A](a: A): Option[Replace[A, Stat]] = { + val res = + a match { + case _: Defn.Class => Option(implicitly[Replace[Defn.Class, Stat]]) + case _: Defn.Trait => Option(implicitly[Replace[Defn.Trait, Stat]]) + case _: Defn.Object => Option(implicitly[Replace[Defn.Object, Stat]]) + case _: Defn.Def => Option(implicitly[Replace[Defn.Def, Stat]]) + case _: Defn.Val => Option(implicitly[Replace[Defn.Val, Stat]]) + case _: Defn.Var => Option(implicitly[Replace[Defn.Var, Stat]]) + case _: Source => Option(implicitly[Replace[Source, Stat]]) + case _: Pkg => Option(implicitly[Replace[Pkg, Stat]]) + case _ => None + } + + res.asInstanceOf[Option[Replace[A, Stat]]] + + } + + def retrieveStatExtractInstance[A](a: A): Option[Extract[A, Stat]] = { + val res = + a match { + case _: Defn.Class => Option(implicitly[Extract[Defn.Class, Stat]]) + case _: Defn.Trait => Option(implicitly[Extract[Defn.Trait, Stat]]) + case _: Defn.Object => Option(implicitly[Extract[Defn.Object, Stat]]) + case _: Defn.Def => Option(implicitly[Extract[Defn.Def, Stat]]) + case _: Defn.Val => Option(implicitly[Extract[Defn.Val, Stat]]) + case _: Defn.Var => Option(implicitly[Extract[Defn.Var, Stat]]) + case _: Source => Option(implicitly[Extract[Source, Stat]]) + case _: Pkg => Option(implicitly[Extract[Pkg, Stat]]) + case _ => None + } + res.asInstanceOf[Option[Extract[A, Stat]]] + } +} diff --git a/scalagen/src/test/scala/scala/meta/gen/TypeclassExtractorTest.scala b/scalagen/src/test/scala/scala/meta/gen/TypeclassExtractorTest.scala new file mode 100644 index 0000000..a30aed3 --- /dev/null +++ b/scalagen/src/test/scala/scala/meta/gen/TypeclassExtractorTest.scala @@ -0,0 +1,49 @@ +package scala.meta.gen + +import org.scalatest.FunSuite +import scala.meta._ +import scala.meta.gen._ +import scala.meta.contrib._ +import scala.meta.gen.TypeclassExtractors._ + +class TypeclassExtractorTest extends FunSuite { + + testModInstanceRetrieval(q"class Foo") + testModInstanceRetrieval(q"object Foo") + testModInstanceRetrieval(q"trait Foo") + testModInstanceRetrieval(q"def foo") + testModInstanceRetrieval(q"type Foo") + testModInstanceRetrieval(q"val foo: Int") + testModInstanceRetrieval(q"var foo: Int") + testModInstanceRetrieval(q"def foo = 1") + testModInstanceRetrieval(q"type Foo = Int") + testModInstanceRetrieval(q"val foo = 1") + testModInstanceRetrieval(q"var foo = 1") + + testStatInstanceRetrieval(q"class Foo") + testStatInstanceRetrieval(q"object Foo") + testStatInstanceRetrieval(q"trait Foo") + testStatInstanceRetrieval(q"def foo = 1") + testStatInstanceRetrieval(q"val foo = 1") + testStatInstanceRetrieval(q"var foo = 1") + testStatInstanceRetrieval(source"object foo") + testStatInstanceRetrieval(source"package foo; object foo".children.head) + + private def testModInstanceRetrieval(t: Tree) = { + test(s"Retrieve mod extract instance: ${t.syntax}") { + assert(retrieveModExtractInstance(t).isDefined) + } + test(s"Retrieve mod replace instance: ${t.syntax}") { + assert(retrieveModReplaceInstance(t).isDefined) + } + } + + private def testStatInstanceRetrieval(t: Tree) = { + test(s"Retrieve stat extract instance: ${t.syntax}") { + assert(retrieveStatExtractInstance(t).isDefined) + } + test(s"Retrieve stat replace instance: ${t.syntax}") { + assert(retrieveStatReplaceInstance(t).isDefined) + } + } +} From 2969948aa8219d53895932af2d5d7b116edf2564 Mon Sep 17 00:00:00 2001 From: David Dudson Date: Tue, 23 Jan 2018 13:23:30 +1300 Subject: [PATCH 2/3] Improve OwnerTree generation and handling --- build.sbt | 9 +- .../scalameta/scalagen/GeneratorTree.scala | 152 +++++++++++++ .../org/scalameta/scalagen/OwnerTree.scala | 63 ------ .../org/scalameta/scalagen/Runner2.scala | 14 ++ .../scala/meta/gen/implicits/StatOwner.scala | 6 +- .../scalagen/GeneratorTreeTests.scala | 208 ++++++++++++++++++ .../scalagen/StatOwnerTreeTests.scala | 81 ------- 7 files changed, 384 insertions(+), 149 deletions(-) create mode 100644 scalagen/src/main/scala/org/scalameta/scalagen/GeneratorTree.scala delete mode 100644 scalagen/src/main/scala/org/scalameta/scalagen/OwnerTree.scala create mode 100644 scalagen/src/main/scala/org/scalameta/scalagen/Runner2.scala create mode 100644 scalagen/src/test/scala/org/scalameta/scalagen/GeneratorTreeTests.scala delete mode 100644 scalagen/src/test/scala/org/scalameta/scalagen/StatOwnerTreeTests.scala diff --git a/build.sbt b/build.sbt index 1cbe5e2..a28fbb6 100644 --- a/build.sbt +++ b/build.sbt @@ -17,6 +17,8 @@ lazy val sharedSettings: Def.SettingsDefinition = Def.settings( "org.scalameta" %% "scalameta" % "2.1.3" :: "org.scalameta" %% "contrib" % "2.1.3" :: "org.typelevel" %% "cats-core" % "1.0.1" :: + "org.typelevel" %% "cats-free" % "1.0.1" :: + "com.github.julien-truffaut" %% "monocle-core" % "1.5.0-cats" :: "org.scalactic" %% "scalactic" % "3.0.4" :: "org.scalactic" %% "scalactic" % "3.0.4" :: "org.scalatest" %% "scalatest" % "3.0.4" % "test" :: Nil, @@ -29,8 +31,11 @@ lazy val sharedSettings: Def.SettingsDefinition = Def.settings( lazy val scalagen = project .in(file("scalagen")) - .settings(sharedSettings) - .settings(name := "scalagen") + .settings( + sharedSettings, + name := "scalagen", + addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.4") + ) // JVM sbt plugin lazy val sbtScalagen = diff --git a/scalagen/src/main/scala/org/scalameta/scalagen/GeneratorTree.scala b/scalagen/src/main/scala/org/scalameta/scalagen/GeneratorTree.scala new file mode 100644 index 0000000..0b45db1 --- /dev/null +++ b/scalagen/src/main/scala/org/scalameta/scalagen/GeneratorTree.scala @@ -0,0 +1,152 @@ +package org.scalameta.scalagen + +import cats._ +import cats.free._ +import cats.implicits._ +import monocle._ + +import scala.meta.{XtensionShow => _, _} +import scala.meta.gen.TypeclassExtractors.retrieveAnnotExtractInstance +import scala.meta.gen._ + +object GeneratorTree { + + /** + * Lazily build a Cofree from this tree. + * + * Cofree objects are only created as the tree is traversed. + */ + def apply(t: Tree): GeneratorTree = + Cofree(t, Eval.later(t.children.map(apply))) + + /** + * Produce a product prefix based tree representation. + * + * Includes all nodes of the tree. + * + * Defn.Class + * - Defn.Var + * - Defn.Def + */ + implicit def treeShowInstance: Show[GeneratorTree] = + Show.show[GeneratorTree](genTraversalString(regularTraversal, _)) + + /** + * Will print all nodes visited by the given traversal + */ + def genTraversalString(t: Traversal[GeneratorTreeF[Tree], Tree], ot: GeneratorTree): String = { + val childString = + ot.tailForced + .map(genTraversalString(t, _)) + .filterNot(_.isEmpty) + .flatMap(_.lines.toList) + .mkString("\n ") + + val res = t.headOption(ot) match { + case None if childString.isEmpty => + "" + case None => + error(childString) + childString + case Some(tree) if childString.isEmpty => + s" - ${treePrefixAndName(tree)}" + case Some(tree) => + s""" - ${treePrefixAndName(tree)} + | $childString""".stripMargin + } + + res + } + + /** + * Primarily used for debug + * + * For example + * "Defn.Class: Foo" + * + * TODO: Make an extract/replace instance for names + */ + private def treePrefixAndName(tree: Tree) = { + val nameStr = + tree match { + case o: Pkg => o.ref.syntax + "." + o.name.syntax + case o: Defn.Object => o.name.syntax + case c: Defn.Class => c.name.syntax + case t: Defn.Trait => t.name.syntax + case t: Defn.Type => t.name.syntax + case t: Decl.Type => t.name.syntax + case d: Defn.Def => d.name.syntax + case d: Decl.Def => d.name.syntax + case v: Defn.Val => genNameSyntax(v.pats) + case v: Decl.Val => genNameSyntax(v.pats) + case v: Defn.Var => genNameSyntax(v.pats) + case v: Decl.Var => genNameSyntax(v.pats) + case s: Term.Select => s.syntax + case s: Type.Select => s.syntax + case n: Term.Name => n.syntax + case n: Type.Name => n.syntax + case _ => "" + } + if (nameStr.isEmpty) { + tree.productPrefix + } else { + tree.productPrefix + s": $nameStr" + } + } + + private def genNameSyntax(pats: List[Pat]): String = + pats + .collect({ case Pat.Var(name) => name }) + .mkString(", ") + + /** + * OwnerTree is just simple tree with an arbritraty , + * The issue is we cannot use the name Tree as we do not want + * conflicts with scalameta.tree + * + * TODO: Consider moving this out of scalagen + */ + type GeneratorTree = Cofree[List, Tree] + + /** + * Partially applied alias for OwnerTree. Allows use as a Functor/Monad etc. + */ + type GeneratorTreeF[A] = Cofree[List, A] + + val regularTraversal: Traversal[GeneratorTreeF[Tree], Tree] = + Traversal.fromTraverse[GeneratorTreeF, Tree](Traverse[GeneratorTreeF]) + + val ownerPrism: Prism[GeneratorTree, GeneratorTree] = + Prism[GeneratorTree, GeneratorTree](t => { + if (t.head.isOwner) Some(t) + else None + })(identity) + + val ownerTraversal: Traversal[GeneratorTreeF[Tree], Tree] = + ownerPrism.composeTraversal(regularTraversal) + + def generatorPrism(gs: Set[Generator]): Prism[GeneratorTree, GeneratorTree] = + Prism[GeneratorTree, GeneratorTree](t => { + if (hasGenerator(t.head, gs)) Some(t) + else None + })(identity) + + private def hasMatchingGenerator(a: Mod.Annot, gs: Set[Generator]): Boolean = + gs.exists { g => + a.init.tpe match { + case Type.Name(value) => g.name == value + case _ => false + } + } + + private def hasGenerator(tree: Tree, gs: Set[Generator]): Boolean = + retrieveAnnotExtractInstance(tree) + .map(_.extract(tree)) + .exists(_.exists(hasMatchingGenerator(_, gs))) + + def generatorTraversal(gs: Set[Generator]): Traversal[GeneratorTree, Tree] = + ownerPrism + .composePrism(generatorPrism(gs)) + .composeTraversal(regularTraversal) + +} diff --git a/scalagen/src/main/scala/org/scalameta/scalagen/OwnerTree.scala b/scalagen/src/main/scala/org/scalameta/scalagen/OwnerTree.scala deleted file mode 100644 index c2ba5eb..0000000 --- a/scalagen/src/main/scala/org/scalameta/scalagen/OwnerTree.scala +++ /dev/null @@ -1,63 +0,0 @@ -package org.scalameta.scalagen - -import cats._ -import cats.implicits._ -import scala.meta._ -import scala.meta.gen._ - -case class OwnerTree[A](value: A, children: List[OwnerTree[A]]) - -object OwnerTree { - - def apply(t: Tree): OwnerTree[Tree] = - OwnerTree(t, t.ownerChildren.map(OwnerTree(_))) - - implicit val treeFunctorInstance: Functor[OwnerTree] = new Functor[OwnerTree] { - override def map[A, B](t: OwnerTree[A])(f: A => B): OwnerTree[B] = { - OwnerTree( - f(t.value), - t.children.map(map(_)(f)) - ) - } - } - - implicit val treeFoldableInstance: Foldable[OwnerTree] = new Foldable[OwnerTree] { - - override def foldLeft[A, B](fa: OwnerTree[A], b: B)(f: (B, A) => B): B = - fa.children.foldLeft(f(b, fa.value))((b, a) => f(b, a.value)) - - override def foldRight[A, B](t: OwnerTree[A], b: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - f(t.value, t.children.foldRight(b)((t, b) => f(t.value, b))) - } - - /** - * Produce a product prefix based owner tree representation - * - * Defn.Class - * - Defn.Var - * - Defn.Def - */ - implicit def treeShowInstance: Show[OwnerTree[Tree]] = - Show.show(t => { - if (t.children.isEmpty) { - t.value.productPrefix - } else { - val childString = - t.children - .map(showChildIndented) - .mkString("\n - ") - - s"""${t.value.productPrefix} - | - $childString""".stripMargin - } - }) - - /** - * Will return the result of Show, indented by 2 - */ - private val showChildIndented: OwnerTree[Tree] => String = - Show[OwnerTree[Tree]] - .show(_) - .lines - .mkString("\n ") -} diff --git a/scalagen/src/main/scala/org/scalameta/scalagen/Runner2.scala b/scalagen/src/main/scala/org/scalameta/scalagen/Runner2.scala new file mode 100644 index 0000000..d526b77 --- /dev/null +++ b/scalagen/src/main/scala/org/scalameta/scalagen/Runner2.scala @@ -0,0 +1,14 @@ +package org.scalameta.scalagen + +import org.scalameta.scalagen.GeneratorTree.GeneratorTree + +import scala.meta.{XtensionShow => _, _} +import scala.meta.gen._ + +object Runner2 { + + def apply(t: Tree, gens: Set[Generator]): Option[Tree] = + expandGenerators(GeneratorTree(t), gens) + + private def expandGenerators(t: GeneratorTree, gs: Set[Generator]): Option[Tree] = ??? +} \ No newline at end of file diff --git a/scalagen/src/main/scala/scala/meta/gen/implicits/StatOwner.scala b/scalagen/src/main/scala/scala/meta/gen/implicits/StatOwner.scala index 52489cf..5f227f2 100644 --- a/scalagen/src/main/scala/scala/meta/gen/implicits/StatOwner.scala +++ b/scalagen/src/main/scala/scala/meta/gen/implicits/StatOwner.scala @@ -35,7 +35,7 @@ trait StatOwner { /** * Whether this tree is indeed an "Owner" */ - def isOwner(t: Tree): Boolean = + def isOwner: Boolean = t match { case _: Source | _: Pkg | _: Defn => true case _ => false @@ -45,7 +45,7 @@ trait StatOwner { t.parent.flatMap { case b: Term.Block => b.owner case t: Template => t.owner - case p if isOwner(p) => Some(p) + case p if p.isOwner => Some(p) case _ => None } @@ -53,7 +53,7 @@ trait StatOwner { t.children.flatMap { case b: Term.Block => b.ownerChildren case t: Template => t.ownerChildren - case p if isOwner(p) => List(p) + case p if p.isOwner => List(p) case _ => Nil } } diff --git a/scalagen/src/test/scala/org/scalameta/scalagen/GeneratorTreeTests.scala b/scalagen/src/test/scala/org/scalameta/scalagen/GeneratorTreeTests.scala new file mode 100644 index 0000000..89d8227 --- /dev/null +++ b/scalagen/src/test/scala/org/scalameta/scalagen/GeneratorTreeTests.scala @@ -0,0 +1,208 @@ +package org.scalameta.scalagen + +import org.scalatest.FunSuite + +import scala.meta.Tree +import scala.meta.Source +import scala.meta.quasiquotes._ +import GeneratorTree._ +import cats._ +import cats.implicits._ + +class GeneratorTreeTests extends FunSuite { + + test("Proof that generator tree tailForced is identical to calling children") { + val obj = q"object Foo" + val gTreeChildren = GeneratorTree(obj).tailForced.map(_.head) + assert(obj.children === gTreeChildren) + } + + test("Show on very small tree") { + val expected = + """ - Defn.Object: Foo + | - Term.Name: Foo + | - Template + | - Self + | - Name.Anonymous""".stripMargin + + val shown = GeneratorTree(q"object Foo").show + + withClue("\nShown:\n" + shown + "\n\n") { + assert(expected == shown) + } + } + + test("Test Skipping of traversal nodes") { + val expected = + """ - Defn.Object: Foo + | - Defn.Object: Bar""".stripMargin + + val gTree = GeneratorTree(q"object Foo { object Bar }") + val shown = GeneratorTree.genTraversalString(ownerTraversal, gTree) + withClue("\nShown:\n" + shown + "\n\n") { + assert(expected == shown) + } + } + + val src: Source = + source"""package org.scala.meta + + class Baz(a: Int, b: Foo) { + def blargh = { + val foo = { + case object Bar { + val foo = new Bar { + val baz = () => { + val bar = 2 + } + } + } + } + } + } + + @DeleteMe + object Foo { + def bar = 2 + }""" + + val raw: String = + """ - Source + | - Pkg: org.scala.meta.meta + | - Term.Select: org.scala.meta + | - Term.Select: org.scala + | - Term.Name: org + | - Term.Name: scala + | - Term.Name: meta + | - Defn.Class: Baz + | - Type.Name: Baz + | - Ctor.Primary + | - Name.Anonymous + | - Term.Param + | - Term.Name: a + | - Type.Name: Int + | - Term.Param + | - Term.Name: b + | - Type.Name: Foo + | - Template + | - Self + | - Name.Anonymous + | - Defn.Def: blargh + | - Term.Name: blargh + | - Term.Block + | - Defn.Val: foo + | - Pat.Var + | - Term.Name: foo + | - Term.Block + | - Defn.Object: Bar + | - Mod.Case + | - Term.Name: Bar + | - Template + | - Self + | - Name.Anonymous + | - Defn.Val: foo + | - Pat.Var + | - Term.Name: foo + | - Term.NewAnonymous + | - Template + | - Init + | - Type.Name: Bar + | - Name.Anonymous + | - Self + | - Name.Anonymous + | - Defn.Val: baz + | - Pat.Var + | - Term.Name: baz + | - Term.Function + | - Term.Block + | - Defn.Val: bar + | - Pat.Var + | - Term.Name: bar + | - Lit.Int + | - Defn.Object: Foo + | - Mod.Annot + | - Init + | - Type.Name: DeleteMe + | - Name.Anonymous + | - Term.Name: Foo + | - Template + | - Self + | - Name.Anonymous + | - Defn.Def: bar + | - Term.Name: bar + | - Lit.Int""".stripMargin + + // Note: If we were to support anon classes + // and functions this would be different + val owner: String = + """ - Source + | - Pkg: org.scala.meta.meta + | - Defn.Class: Baz + | - Defn.Def: blargh + | - Defn.Val: foo + | - Defn.Object: Bar + | - Defn.Val: foo + | - Defn.Val: baz + | - Defn.Val: bar + | - Defn.Object: Foo + | - Defn.Def: bar""".stripMargin + + val generators: String = + """ - Defn.Object: Foo""".stripMargin + + val ot = GeneratorTree(src) + + test("Raw") { + withClue(s""" + |===================== + | Expected + |===================== + |$raw + |===================== + | Actual + |===================== + |${ot.show} + |===================== + |""".stripMargin) { + assert(raw == ot.show) + } + } + + test("Owner") { + val str = GeneratorTree.genTraversalString(ownerTraversal, ot) + withClue(s""" + |===================== + | Expected + |===================== + |$owner + |===================== + | Actual + |===================== + |$str + |===================== + |""".stripMargin) { + + assert(owner == str) + } + } + + test("Generators") { + + val str = GeneratorTree.genTraversalString(generatorTraversal(Set(DeleteMe)), ot) + + withClue(s""" + |===================== + | Expected + |===================== + |$generators + |===================== + | Actual + |===================== + |$str + |===================== + """.stripMargin) { + + assert(generators == str) + } + } +} diff --git a/scalagen/src/test/scala/org/scalameta/scalagen/StatOwnerTreeTests.scala b/scalagen/src/test/scala/org/scalameta/scalagen/StatOwnerTreeTests.scala deleted file mode 100644 index 2fc5c71..0000000 --- a/scalagen/src/test/scala/org/scalameta/scalagen/StatOwnerTreeTests.scala +++ /dev/null @@ -1,81 +0,0 @@ -package org.scalameta.scalagen - -import org.scalatest.FunSuite - -import scala.meta._ -import cats._ -import cats.implicits._ - -class StatOwnerTreeTests extends FunSuite { - - test("Single Owner Tree") { - val src: Member = q"object Foo" - - val expected: String = "Defn.Object" - - val res = OwnerTree(src) - val str = Show[OwnerTree[Tree]].show(res) - - withClue(str) { - assert(expected == str) - } - } - - test("Two Owner Tree") { - val src: Member = q"object Foo { def bar = 2 }" - - val expected: String = - """Defn.Object - | - Defn.Def""".stripMargin - - val res = OwnerTree(src) - val str = Show[OwnerTree[Tree]].show(res) - - withClue("Clue:\n\n" + str + "\n\n") { - assert(expected == str) - } - } - - test("Complex Owner Tree") { - val src: Source = - source"""package org.scala.meta - - class Baz(a: Int, b: Foo) { - def blargh = { - val foo = { - case object Bar { - val foo = new Bar { - val baz = () => { - val bar = 2 - } - } - } - } - } - } - - object Foo { - def bar = 2 - }""" - - // Note: If we were to support anon classes - // and functions this would be different - val expected: String = - """Source - | - Pkg - | - Defn.Class - | - Defn.Def - | - Defn.Val - | - Defn.Object - | - Defn.Val - | - Defn.Object - | - Defn.Def""".stripMargin - - val res = OwnerTree(src) - val str = Show[OwnerTree[Tree]].show(res) - - withClue("Clue:\n\n" + str + "\n\n") { - assert(expected == str) - } - } -} From 1e9f55daeb67bba742403893468d008b6cf46cea Mon Sep 17 00:00:00 2001 From: David Dudson Date: Tue, 30 Jan 2018 03:46:21 +1300 Subject: [PATCH 3/3] WIP Generator contexts --- .../scalameta/scalagen/GeneratorTree.scala | 91 +++++++++++-------- .../org/scalameta/scalagen/Runner2.scala | 4 +- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/scalagen/src/main/scala/org/scalameta/scalagen/GeneratorTree.scala b/scalagen/src/main/scala/org/scalameta/scalagen/GeneratorTree.scala index 0b45db1..3cd2864 100644 --- a/scalagen/src/main/scala/org/scalameta/scalagen/GeneratorTree.scala +++ b/scalagen/src/main/scala/org/scalameta/scalagen/GeneratorTree.scala @@ -9,14 +9,29 @@ import scala.meta.{XtensionShow => _, _} import scala.meta.gen.TypeclassExtractors.retrieveAnnotExtractInstance import scala.meta.gen._ +case class GeneratorInputContext(src: Tree, toExpand: List[Generator]) object GeneratorTree { + /** + * OwnerTree is just simple tree with an arbritraty , + * The issue is we cannot use the name Tree as we do not want + * conflicts with scalameta.tree + * + * TODO: Consider moving this out of scalagen + */ + type GeneratorTree = Cofree[List, GeneratorInputContext] + + /** + * Partially applied alias for OwnerTree. Allows use as a Functor/Monad etc. + */ + type GeneratorTreeF[A] = Cofree[List, A] + /** * Lazily build a Cofree from this tree. * * Cofree objects are only created as the tree is traversed. */ - def apply(t: Tree): GeneratorTree = + def apply(t: Tree): GeneratorTreeF[Tree] = Cofree(t, Eval.later(t.children.map(apply))) /** @@ -34,7 +49,9 @@ object GeneratorTree { /** * Will print all nodes visited by the given traversal */ - def genTraversalString(t: Traversal[GeneratorTreeF[Tree], Tree], ot: GeneratorTree): String = { + def genTraversalString( + t: Traversal[GeneratorTreeF[GeneratorInputContext], GeneratorInputContext], + ot: GeneratorTree): String = { val childString = ot.tailForced .map(genTraversalString(t, _)) @@ -48,10 +65,10 @@ object GeneratorTree { case None => error(childString) childString - case Some(tree) if childString.isEmpty => - s" - ${treePrefixAndName(tree)}" - case Some(tree) => - s""" - ${treePrefixAndName(tree)} + case Some(ctx) if childString.isEmpty => + s" - ${treePrefixAndName(ctx.src)}" + case Some(ctx) => + s""" - ${treePrefixAndName(ctx.src)} | $childString""".stripMargin } @@ -99,54 +116,52 @@ object GeneratorTree { .collect({ case Pat.Var(name) => name }) .mkString(", ") - /** - * OwnerTree is just simple tree with an arbritraty , - * The issue is we cannot use the name Tree as we do not want - * conflicts with scalameta.tree - * - * TODO: Consider moving this out of scalagen - */ - type GeneratorTree = Cofree[List, Tree] + def regularTraversal[A]: Traversal[GeneratorTreeF[A], A] = + Traversal.fromTraverse[GeneratorTreeF, A](Traverse[GeneratorTreeF]) - /** - * Partially applied alias for OwnerTree. Allows use as a Functor/Monad etc. - */ - type GeneratorTreeF[A] = Cofree[List, A] - - val regularTraversal: Traversal[GeneratorTreeF[Tree], Tree] = - Traversal.fromTraverse[GeneratorTreeF, Tree](Traverse[GeneratorTreeF]) - - val ownerPrism: Prism[GeneratorTree, GeneratorTree] = - Prism[GeneratorTree, GeneratorTree](t => { + val ownerPrism: Prism[GeneratorTreeF[Tree], GeneratorTreeF[Tree]] = + Prism[GeneratorTreeF[Tree], GeneratorTreeF[Tree]](t => { if (t.head.isOwner) Some(t) else None })(identity) val ownerTraversal: Traversal[GeneratorTreeF[Tree], Tree] = - ownerPrism.composeTraversal(regularTraversal) - - def generatorPrism(gs: Set[Generator]): Prism[GeneratorTree, GeneratorTree] = - Prism[GeneratorTree, GeneratorTree](t => { - if (hasGenerator(t.head, gs)) Some(t) - else None - })(identity) + ownerPrism.composeTraversal(regularTraversal[Tree]) + + def generatorPrism(gs: Set[Generator]): Prism[GeneratorTreeF[Tree], GeneratorTree] = + Prism[GeneratorTreeF[Tree], GeneratorTree](t => { + if (hasGenerator(t.head, gs)) { + val context = GeneratorInputContext(t.head, getGeneratorsToExpand(t.head, gs)) + Some(Cofree(context, Eval.now(List.empty[GeneratorTree]))) + } else { + None + } + })(_.map(_.src)) private def hasMatchingGenerator(a: Mod.Annot, gs: Set[Generator]): Boolean = - gs.exists { g => - a.init.tpe match { - case Type.Name(value) => g.name == value - case _ => false - } + gs.exists(isMatching(a, _)) + + private def isMatching(a: Mod.Annot, g: Generator) = { + a.init.tpe match { + case Type.Name(value) => g.name == value + case _ => false } + } private def hasGenerator(tree: Tree, gs: Set[Generator]): Boolean = retrieveAnnotExtractInstance(tree) .map(_.extract(tree)) .exists(_.exists(hasMatchingGenerator(_, gs))) - def generatorTraversal(gs: Set[Generator]): Traversal[GeneratorTree, Tree] = + private def getGeneratorsToExpand(tree: Tree, gs: Set[Generator]): List[Generator] = + retrieveAnnotExtractInstance(tree) + .fold(List[Mod.Annot]())(_.extract(tree)) + .flatMap(annot => gs.find(isMatching(annot, _))) + + def generatorTraversal( + gs: Set[Generator]): Traversal[GeneratorTreeF[Tree], GeneratorInputContext] = ownerPrism .composePrism(generatorPrism(gs)) - .composeTraversal(regularTraversal) + .composeTraversal(regularTraversal[GeneratorInputContext]) } diff --git a/scalagen/src/main/scala/org/scalameta/scalagen/Runner2.scala b/scalagen/src/main/scala/org/scalameta/scalagen/Runner2.scala index d526b77..75bff75 100644 --- a/scalagen/src/main/scala/org/scalameta/scalagen/Runner2.scala +++ b/scalagen/src/main/scala/org/scalameta/scalagen/Runner2.scala @@ -1,6 +1,6 @@ package org.scalameta.scalagen -import org.scalameta.scalagen.GeneratorTree.GeneratorTree +import org.scalameta.scalagen.GeneratorTree.{GeneratorTree, GeneratorTreeF} import scala.meta.{XtensionShow => _, _} import scala.meta.gen._ @@ -10,5 +10,5 @@ object Runner2 { def apply(t: Tree, gens: Set[Generator]): Option[Tree] = expandGenerators(GeneratorTree(t), gens) - private def expandGenerators(t: GeneratorTree, gs: Set[Generator]): Option[Tree] = ??? + private def expandGenerators(t: GeneratorTreeF[Tree], gs: Set[Generator]): Option[Tree] = ??? } \ No newline at end of file