From 3b10725ee3a1f84855a0654c5e386bc7465816d3 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sat, 4 Nov 2017 09:09:02 -0700 Subject: First experiment using `Cacher interface` combined with `Caller` implicits to turn `def foo = T{}` into pseudo-`lazy val`s, that we can override in subclasses using stackable traits --- core/src/main/scala/forge/Discovered.scala | 6 +- core/src/main/scala/forge/Evaluator.scala | 1 + core/src/main/scala/forge/Target.scala | 15 ++++- core/src/main/scala/forge/util/Caller.scala | 12 ++++ core/src/test/scala/forge/GraphTests.scala | 66 +++++++++++++++++++++- .../src/test/scala/forge/JavaCompileJarTests.scala | 19 +++---- core/src/test/scala/forge/TestGraphs.scala | 29 ++++++++++ 7 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 core/src/main/scala/forge/util/Caller.scala (limited to 'core/src') diff --git a/core/src/main/scala/forge/Discovered.scala b/core/src/main/scala/forge/Discovered.scala index 03577b1f..e3aa31f9 100644 --- a/core/src/main/scala/forge/Discovered.scala +++ b/core/src/main/scala/forge/Discovered.scala @@ -29,7 +29,11 @@ object Discovered { val tpe = c.weakTypeTag[T].tpe def rec(segments: List[String], t: c.Type): Seq[Seq[String]] = for { m <- t.members.toSeq - if m.isTerm && (m.asTerm.isGetter || m.asTerm.isLazy) || m.isModule + if + (m.isTerm && (m.asTerm.isGetter || m.asTerm.isLazy)) || + m.isModule || + (m.isMethod && m.typeSignature.paramLists.isEmpty && m.typeSignature.resultType <:< c.weakTypeOf[Target[_]]) + res <- { val extendedSegments = m.name.toString :: segments val self = diff --git a/core/src/main/scala/forge/Evaluator.scala b/core/src/main/scala/forge/Evaluator.scala index 50dc46d4..3ef605b7 100644 --- a/core/src/main/scala/forge/Evaluator.scala +++ b/core/src/main/scala/forge/Evaluator.scala @@ -105,6 +105,7 @@ class Evaluator(workspacePath: Path, ) val args = new Args(targetInputValues, targetDestPath) + val res = target.evaluate(args) for(targetLabel <- labeling.get(target)){ terminalResults(target) = targetLabel diff --git a/core/src/main/scala/forge/Target.scala b/core/src/main/scala/forge/Target.scala index c1790960..df9d9cc3 100644 --- a/core/src/main/scala/forge/Target.scala +++ b/core/src/main/scala/forge/Target.scala @@ -32,6 +32,14 @@ abstract class Target[T] extends Target.Ops[T]{ } object Target{ + trait Cacher{ + val cacherLazyMap = mutable.Map.empty[(Any, sourcecode.Line, sourcecode.Enclosing), Target[_]] + def T[T](t: T): Target[T] = macro impl[T] + def T[T](t: => Target[T]) + (implicit c: forge.util.Caller, l: sourcecode.Line, e: sourcecode.Enclosing): Target[T] = { + cacherLazyMap.getOrElseUpdate((c, l, e), t).asInstanceOf[Target[T]] + } + } class Target0[T](t: T) extends Target[T]{ lazy val t0 = t val inputs = Nil @@ -68,7 +76,12 @@ object Target{ val bindings = symbols.map(c.internal.valDef(_)) - val embedded = q"new forge.Target.Target1(forge.zipMap(..$exprs){ (..$bindings) => $transformed })" + val newTargetTree = q"new forge.Target.Target1(forge.zipMap(..$exprs){ (..$bindings) => $transformed })" + + val embedded = + if (!(c.prefix.tree.tpe <:< typeOf[Cacher])) newTargetTree + else q"${c.prefix}.T($newTargetTree)" + c.Expr[Target[T]](embedded) } diff --git a/core/src/main/scala/forge/util/Caller.scala b/core/src/main/scala/forge/util/Caller.scala new file mode 100644 index 00000000..30dcb22a --- /dev/null +++ b/core/src/main/scala/forge/util/Caller.scala @@ -0,0 +1,12 @@ +package forge.util + +import scala.reflect.macros.blackbox +import language.experimental.macros +case class Caller(value: Any) +object Caller { + implicit def generate: Caller = macro impl + def impl(c: blackbox.Context): c.Tree = { + import c.universe._ + q"new _root_.forge.util.Caller(this)" + } +} diff --git a/core/src/test/scala/forge/GraphTests.scala b/core/src/test/scala/forge/GraphTests.scala index 572e459e..ca825683 100644 --- a/core/src/test/scala/forge/GraphTests.scala +++ b/core/src/test/scala/forge/GraphTests.scala @@ -70,6 +70,54 @@ object GraphTests extends TestSuite{ diamond.down ) ) + 'defCachedDiamond - check( + targets = OSet(defCachedDiamond.down), + expected = OSet( + defCachedDiamond.up, + defCachedDiamond.down.inputs(0), + defCachedDiamond.down.inputs(1), + defCachedDiamond.down + ) + ) + 'borkedCachedDiamond - { + // Make sure these fail because `def`s without `Cacher` will re-evaluate + // each time, returning different sets of targets. + // + // Maybe later we can convert them into compile errors somehow + * - intercept[Throwable]{ + check( + targets = OSet(borkedCachedDiamond1.down), + expected = OSet( + borkedCachedDiamond1.up, + borkedCachedDiamond1.down.inputs(0), + borkedCachedDiamond1.down.inputs(1), + borkedCachedDiamond1.down + ) + ) + } + * - intercept[Throwable]{ + check( + targets = OSet(borkedCachedDiamond2.down), + expected = OSet( + borkedCachedDiamond2.up, + borkedCachedDiamond2.down.inputs(0), + borkedCachedDiamond2.down.inputs(1), + borkedCachedDiamond2.down + ) + ) + } + * - intercept[Throwable]{ + check( + targets = OSet(borkedCachedDiamond3.down), + expected = OSet( + borkedCachedDiamond3.up, + borkedCachedDiamond3.down.inputs(0), + borkedCachedDiamond3.down.inputs(1), + borkedCachedDiamond3.down + ) + ) + } + } 'bigSingleTerminal - { val result = Evaluator.topoSortedTransitiveTargets(OSet(bigSingleTerminal.j)).values TestUtil.checkTopological(result) @@ -78,9 +126,9 @@ object GraphTests extends TestSuite{ } 'groupAroundNamedTargets - { - def check[T: Discovered](base: T, - target: TestUtil.Test, - expected: OSet[(OSet[TestUtil.Test], Int)]) = { + def check[T: Discovered, R <: Target[Int]](base: T, + target: R, + expected: OSet[(OSet[R], Int)]) = { val mapping = Discovered.mapping(base) val topoSortedTransitive = Evaluator.topoSortedTransitiveTargets(OSet(target)) @@ -122,6 +170,18 @@ object GraphTests extends TestSuite{ OSet(diamond.down) -> 1 ) ) + + 'defCachedDiamond - check( + defCachedDiamond, + defCachedDiamond.down, + OSet( + OSet(defCachedDiamond.up) -> 1, + OSet(defCachedDiamond.left) -> 1, + OSet(defCachedDiamond.right) -> 1, + OSet(defCachedDiamond.down) -> 1 + ) + ) + 'anonDiamond - check( anonDiamond, anonDiamond.down, diff --git a/core/src/test/scala/forge/JavaCompileJarTests.scala b/core/src/test/scala/forge/JavaCompileJarTests.scala index c1a055ea..8b292a88 100644 --- a/core/src/test/scala/forge/JavaCompileJarTests.scala +++ b/core/src/test/scala/forge/JavaCompileJarTests.scala @@ -50,27 +50,26 @@ object JavaCompileJarTests extends TestSuite{ mkdir(pwd / 'target / 'workspace / 'javac) cp(javacSrcPath, javacDestPath) - object Build { - val sourceRootPath = javacDestPath / 'src - val resourceRootPath = javacDestPath / 'resources + object Build extends Target.Cacher{ + def sourceRootPath = javacDestPath / 'src + def resourceRootPath = javacDestPath / 'resources // sourceRoot -> allSources -> classFiles // | // v // resourceRoot ----> jar - val sourceRoot = Target.path(sourceRootPath) - val resourceRoot = Target.path(resourceRootPath) - val allSources = T{ - ls.rec(sourceRoot().path).map(PathRef(_)) - } - val classFiles = compileAll(allSources) - val jar = jarUp(resourceRoot, classFiles) + def sourceRoot = T{ Target.path(sourceRootPath) } + def resourceRoot = T{ Target.path(resourceRootPath) } + def allSources = T{ ls.rec(sourceRoot().path).map(PathRef(_)) } + def classFiles = T{ compileAll(allSources) } + def jar = T{ jarUp(resourceRoot, classFiles) } } import Build._ val mapping = Discovered.mapping(Build) def check(targets: OSet[Target[_]], expected: OSet[Target[_]]) = { val evaluator = new Evaluator(workspacePath, mapping) + val evaluated = evaluator.evaluate(targets).evaluated.filter(mapping.contains) assert(evaluated == expected) } diff --git a/core/src/test/scala/forge/TestGraphs.scala b/core/src/test/scala/forge/TestGraphs.scala index 0ee48a18..5960ed4c 100644 --- a/core/src/test/scala/forge/TestGraphs.scala +++ b/core/src/test/scala/forge/TestGraphs.scala @@ -1,5 +1,6 @@ package forge +import forge.Target.Cacher import forge.TestUtil.test class TestGraphs(){ @@ -42,6 +43,34 @@ class TestGraphs(){ val down = test(test(up), test(up)) } + object defCachedDiamond extends Cacher{ + def up = T{ test() } + def left = T{ test(up) } + def right = T{ test(up) } + def down = T{ test(left, right) } + } + + object borkedCachedDiamond1 { + def up = T{ test() } + def left = T{ test(up) } + def right = T{ test(up) } + def down = T{ test(left, right) } + } + + object borkedCachedDiamond2 extends Cacher { + def up = test() + def left = test(up) + def right = test(up) + def down = test(left, right) + } + + object borkedCachedDiamond3 { + def up = test() + def left = test(up) + def right = test(up) + def down = test(left, right) + } + // o g-----o // \ \ \ // o o h-----I---o -- cgit v1.2.3