diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2017-10-28 17:14:45 -0700 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2017-10-28 17:14:45 -0700 |
commit | 8a85ac816b5514c481341456061ed26a83f72bb0 (patch) | |
tree | fb56b211e95b6b504bdb649176bd66dc10a668b7 /src | |
parent | fee05e6163fadff27521807cc3d97d61b74ab466 (diff) | |
download | mill-8a85ac816b5514c481341456061ed26a83f72bb0.tar.gz mill-8a85ac816b5514c481341456061ed26a83f72bb0.tar.bz2 mill-8a85ac816b5514c481341456061ed26a83f72bb0.zip |
Get rid of `DefCtx` for good. All tests passing entirely using the new `Discovered` labeling mechanism
Diffstat (limited to 'src')
-rw-r--r-- | src/main/scala/forge/DefCtx.scala | 79 | ||||
-rw-r--r-- | src/main/scala/forge/Evaluator.scala | 23 | ||||
-rw-r--r-- | src/main/scala/forge/Target.scala | 41 | ||||
-rw-r--r-- | src/test/scala/forge/EvaluationTests.scala | 49 | ||||
-rw-r--r-- | src/test/scala/forge/GraphTests.scala | 80 | ||||
-rw-r--r-- | src/test/scala/forge/Main.scala | 32 | ||||
-rw-r--r-- | src/test/scala/forge/TestUtil.scala | 39 |
7 files changed, 99 insertions, 244 deletions
diff --git a/src/main/scala/forge/DefCtx.scala b/src/main/scala/forge/DefCtx.scala deleted file mode 100644 index 3439f755..00000000 --- a/src/main/scala/forge/DefCtx.scala +++ /dev/null @@ -1,79 +0,0 @@ -package forge - - -import scala.annotation.compileTimeOnly -import scala.language.experimental.macros -import scala.reflect.macros.blackbox._ - - -final case class DefCtx(baseLabel: String, anonId: Option[Int]){ - def label = baseLabel + anonId.getOrElse("") -} -object DefCtx{ - @compileTimeOnly("A DefCtx can only be provided directly within a T{} macro") - implicit def dummy: DefCtx with Int = ??? -} - -object T{ - def apply[T](expr: T): T = macro applyImpl[T] - - def applyImpl[T: c.WeakTypeTag](c: Context)(expr: c.Expr[T]): c.Expr[T] = { - import c.universe._ - var count = 0 - object transformer extends c.universe.Transformer { - override def transform(tree: c.Tree): c.Tree = { - if (tree.toString.startsWith("forge.") && tree.toString.endsWith(".DefCtx.dummy")) { - count += 1 - c.typecheck(q"forge.DefCtx(sourcecode.Enclosing(), Some($count))") - }else tree match{ - case Apply(fun, args) => - val extendedParams = fun.tpe.paramLists.head.padTo( - args.length, - fun.tpe.paramLists.head.lastOption.getOrElse(null) - ) - val newArgs = - for((sym, tree) <- extendedParams.zip(args)) - yield { - if (sym.asTerm.isByNameParam) tree - else transform(tree) - } - treeCopy.Apply(tree, transform(fun), newArgs) - - case t: DefDef => t - case t: ClassDef => t - case t: Function => t - case t: LabelDef => t - case t => super.transform(t) - } - - } - } - - - def transformTerminal(tree: c.Tree): c.Tree = tree match{ - case Block(stats, returnExpr) => - treeCopy.Block( - tree, - stats.map(transformer.transform(_)), - transformTerminal(returnExpr) - ) - - case Apply(fun, args) => - var isTransformed = false - val newArgs = for(x <- args) yield { - if (x.toString.startsWith("forge.") && x.toString.endsWith(".DefCtx.dummy")) { - isTransformed = true - c.typecheck(q"forge.DefCtx(sourcecode.Enclosing(), None)") - }else transformer.transform(x) - } - - assert(isTransformed) - treeCopy.Apply(tree, transformer.transform(fun), newArgs) - - case _ => ??? - } - - val transformed = transformTerminal(expr.tree) - c.Expr[T](transformed) - } -}
\ No newline at end of file diff --git a/src/main/scala/forge/Evaluator.scala b/src/main/scala/forge/Evaluator.scala index 32a3c9d0..4d9a435e 100644 --- a/src/main/scala/forge/Evaluator.scala +++ b/src/main/scala/forge/Evaluator.scala @@ -8,7 +8,7 @@ import sourcecode.Enclosing import scala.collection.mutable class Evaluator(workspacePath: jnio.Path, - enclosingBase: DefCtx){ + labeling: Map[Target[_], Seq[String]]){ /** * Cache from the ID of the first terminal target in a group to the has of @@ -20,12 +20,14 @@ class Evaluator(workspacePath: jnio.Path, jnio.Files.createDirectories(workspacePath) val sortedGroups = Evaluator.groupAroundNamedTargets( - Evaluator.topoSortedTransitiveTargets(targets) + Evaluator.topoSortedTransitiveTargets(targets), + labeling ) val evaluated = new MutableOSet[Target[_]] val results = mutable.Map.empty[Target[_], Any] for (group <- sortedGroups){ + println("Evaluating group " + group) val (newResults, newEvaluated) = evaluateGroup(group, results) evaluated.appendAll(newEvaluated) for((k, v) <- newResults) results.put(k, v) @@ -46,19 +48,19 @@ class Evaluator(workspacePath: jnio.Path, val terminals = group.filter(!internalInputSet(_)) val primeTerminal = terminals.items(0) - val enclosingStr = primeTerminal.defCtx.label + + val primeLabel = labeling(primeTerminal).mkString("/") val targetDestPath = workspacePath.resolve( - jnio.Paths.get(enclosingStr.stripSuffix(enclosingBase.label)) + jnio.Paths.get(primeLabel) ) val anyDirty = group.exists(_.dirty) deleteRec(targetDestPath) val inputsHash = inputResults.hashCode - resultCache.get(primeTerminal.defCtx.label) match{ + resultCache.get(primeLabel) match{ case Some((hash, terminalResults)) if hash == inputsHash && !anyDirty => for((terminal, res) <- terminals.items.zip(terminalResults)){ - newResults(terminal) = primeTerminal.formatter.reads(Json.parse(res)).get } @@ -69,7 +71,7 @@ class Evaluator(workspacePath: jnio.Path, val targetInputValues = target.inputs.toVector.map(x => newResults.getOrElse(x, results(x)) ) - if (target.defCtx.anonId.isDefined) { + if (!labeling.contains(target)) { val res = target.evaluate(new Args(targetInputValues, targetDestPath)) newResults(target) = res }else{ @@ -83,7 +85,7 @@ class Evaluator(workspacePath: jnio.Path, newResults(target) = res } } - resultCache(primeTerminal.defCtx.label) = (inputsHash, terminalResults) + resultCache(primeLabel) = (inputsHash, terminalResults) } @@ -105,7 +107,8 @@ class Evaluator(workspacePath: jnio.Path, object Evaluator{ class TopoSorted private[Evaluator] (val values: OSet[Target[_]]) case class Results(values: Seq[Any], evaluated: OSet[Target[_]]) - def groupAroundNamedTargets(topoSortedTargets: TopoSorted): OSet[OSet[Target[_]]] = { + def groupAroundNamedTargets(topoSortedTargets: TopoSorted, + labeling: Map[Target[_], Seq[String]]): OSet[OSet[Target[_]]] = { val grouping = new MultiBiMap[Int, Target[_]]() var groupCount = 0 @@ -120,7 +123,7 @@ object Evaluator{ val targetGroup = grouping.lookupValue(target) for(upstream <- target.inputs){ grouping.lookupValueOpt(upstream) match{ - case None if upstream.defCtx.anonId.nonEmpty => + case None if !labeling.contains(upstream) => grouping.add(targetGroup, upstream) case Some(upstreamGroup) if upstreamGroup == targetGroup => val upstreamTargets = grouping.removeAll(upstreamGroup) diff --git a/src/main/scala/forge/Target.scala b/src/main/scala/forge/Target.scala index 6f434030..8966a729 100644 --- a/src/main/scala/forge/Target.scala +++ b/src/main/scala/forge/Target.scala @@ -5,10 +5,6 @@ import java.nio.{file => jnio} import play.api.libs.json.{Format, Json} abstract class Target[T](implicit formatter: Format[T]) extends Target.Ops[T]{ /** - * Where in the Scala codebase was this target defined? - */ - val defCtx: DefCtx - /** * What other Targets does this Target depend on? */ val inputs: Seq[Target[_]] @@ -33,22 +29,19 @@ object Target{ val str = formatter.writes(res) (res, Json.stringify(str)) } - val defCtx: DefCtx - def map[V: Format](f: T => V)(implicit defCtx: DefCtx) = { - new Target.Mapped(this, f, defCtx) + def map[V: Format](f: T => V) = { + new Target.Mapped(this, f) } - def zip[V: Format](other: Target[V])(implicit defCtx: DefCtx) = { - new Target.Zipped(this, other, defCtx) + def zip[V: Format](other: Target[V]) = { + new Target.Zipped(this, other) } def ~[V: Format, R: Format](other: Target[V]) - (implicit s: Implicits.Sequencer[T, V, R], defCtx: DefCtx): Target[R] = { + (implicit s: Implicits.Sequencer[T, V, R]): Target[R] = { this.zip(other).map(s.apply _ tupled) } - - override def toString = defCtx.label.split('.').last } - def test(inputs: Target[Int]*)(implicit defCtx: DefCtx) = { - new Test(inputs, defCtx, pure = inputs.nonEmpty) + def test(inputs: Target[Int]*) = { + new Test(inputs, pure = inputs.nonEmpty) } /** @@ -57,7 +50,6 @@ object Target{ * test how changes propagate. */ class Test(val inputs: Seq[Target[Int]], - val defCtx: DefCtx, val pure: Boolean) extends Target[Int]{ var counter = 0 var lastCounter = counter @@ -67,36 +59,33 @@ object Target{ } override def dirty = lastCounter != counter } - def traverse[T: Format](source: Seq[Target[T]])(implicit defCtx: DefCtx) = { - new Traverse[T](source, defCtx) + def traverse[T: Format](source: Seq[Target[T]]) = { + new Traverse[T](source) } - class Traverse[T: Format](val inputs: Seq[Target[T]], val defCtx: DefCtx) extends Target[Seq[T]]{ + class Traverse[T: Format](val inputs: Seq[Target[T]]) extends Target[Seq[T]]{ def evaluate(args: Args) = { for (i <- 0 until args.length) yield args(i).asInstanceOf[T] } } - class Mapped[T, V: Format](source: Target[T], f: T => V, - val defCtx: DefCtx) extends Target[V]{ + class Mapped[T, V: Format](source: Target[T], f: T => V) extends Target[V]{ def evaluate(args: Args) = f(args(0)) val inputs = List(source) } class Zipped[T: Format, V: Format](source1: Target[T], - source2: Target[V], - val defCtx: DefCtx) extends Target[(T, V)]{ + source2: Target[V]) extends Target[(T, V)]{ def evaluate(args: Args) = (args(0), args(0)) val inputs = List(source1, source1) } - def path(path: jnio.Path)(implicit defCtx: DefCtx) = new Path(path, defCtx) - class Path(path: jnio.Path, val defCtx: DefCtx) extends Target[jnio.Path]{ + def path(path: jnio.Path) = new Path(path) + class Path(path: jnio.Path) extends Target[jnio.Path]{ def evaluate(args: Args) = path val inputs = Nil } class Subprocess(val inputs: Seq[Target[_]], - command: Args => Seq[String], - val defCtx: DefCtx) extends Target[Subprocess.Result] { + command: Args => Seq[String]) extends Target[Subprocess.Result] { def evaluate(args: Args) = { jnio.Files.createDirectories(args.dest) diff --git a/src/test/scala/forge/EvaluationTests.scala b/src/test/scala/forge/EvaluationTests.scala index cecbf5f1..d80781c0 100644 --- a/src/test/scala/forge/EvaluationTests.scala +++ b/src/test/scala/forge/EvaluationTests.scala @@ -7,30 +7,36 @@ import utest._ object EvaluationTests extends TestSuite{ + val workspace = jnio.Paths.get("target/workspace") val tests = Tests{ - val baseCtx = DefCtx("forge.ForgeTests.tests ", None) - val graphs = new TestUtil.TestGraphs() import graphs._ 'evaluateSingle - { - val evaluator = new Evaluator(jnio.Paths.get("target/workspace"), baseCtx) - def check(target: Target[_], expValue: Any, expEvaled: OSet[Target[_]]) = { - val Evaluator.Results(returnedValues, returnedEvaluated) = evaluator.evaluate(OSet(target)) - assert( - returnedValues == Seq(expValue), - returnedEvaluated == expEvaled - ) - // Second time the value is already cached, so no evaluation needed - val Evaluator.Results(returnedValues2, returnedEvaluated2) = evaluator.evaluate(OSet(target)) - assert( - returnedValues2 == returnedValues, - returnedEvaluated2 == OSet() + + class Checker[T: Discovered](base: T) { + val evaluator = new Evaluator( + workspace, + implicitly[Discovered[T]].apply(base).map(_.swap).toMap ) + def apply(target: Target[_], expValue: Any, expEvaled: OSet[Target[_]]) = { + val Evaluator.Results(returnedValues, returnedEvaluated) = evaluator.evaluate(OSet(target)) + assert( + returnedValues == Seq(expValue), + returnedEvaluated == expEvaled + ) + // Second time the value is already cached, so no evaluation needed + val Evaluator.Results(returnedValues2, returnedEvaluated2) = evaluator.evaluate(OSet(target)) + assert( + returnedValues2 == returnedValues, + returnedEvaluated2 == OSet() + ) + } } 'singleton - { import singleton._ + val check = new Checker(singleton) // First time the target is evaluated check(single, expValue = 0, expEvaled = OSet(single)) @@ -40,16 +46,20 @@ object EvaluationTests extends TestSuite{ } 'pair - { import pair._ + val check = new Checker(pair) check(down, expValue = 0, expEvaled = OSet(up, down)) + println("=" * 20 + "incrementing down.counter" + "=" * 20) down.counter += 1 check(down, expValue = 1, expEvaled = OSet(down)) + println("=" * 20 + "incrementing up.counter" + "=" * 20) up.counter += 1 check(down, expValue = 2, expEvaled = OSet(up, down)) } 'anonTriple - { import anonTriple._ + val check = new Checker(anonTriple) val middle = down.inputs(0) check(down, expValue = 0, expEvaled = OSet(up, middle, down)) @@ -65,6 +75,7 @@ object EvaluationTests extends TestSuite{ } 'diamond - { import diamond._ + val check = new Checker(diamond) check(down, expValue = 0, expEvaled = OSet(up, left, right, down)) down.counter += 1 @@ -82,6 +93,7 @@ object EvaluationTests extends TestSuite{ } 'anonDiamond - { import anonDiamond._ + val check = new Checker(anonDiamond) val left = down.inputs(0).asInstanceOf[Target.Test] val right = down.inputs(1).asInstanceOf[Target.Test] check(down, expValue = 0, expEvaled = OSet(up, left, right, down)) @@ -99,15 +111,6 @@ object EvaluationTests extends TestSuite{ right.counter += 1 check(down, expValue = 5, expEvaled = OSet(left, right, down)) } -// 'anonImpureDiamond - { -// import AnonImpureDiamond._ -// val left = down.inputs(0).asInstanceOf[Target.Test] -// val right = down.inputs(1).asInstanceOf[Target.Test] -// check(down, expValue = 0, expEvaled = Seq(up, left, right, down)) -// -// down.counter += 1 -// check(down, expValue = 1, expEvaled = Seq(left, down)) -// } } diff --git a/src/test/scala/forge/GraphTests.scala b/src/test/scala/forge/GraphTests.scala index 55763909..aead2d0c 100644 --- a/src/test/scala/forge/GraphTests.scala +++ b/src/test/scala/forge/GraphTests.scala @@ -14,15 +14,15 @@ object GraphTests extends TestSuite{ 'discovery{ class CanNest{ - val single = T{ test() } - val invisible: Any = T{ test() } + val single = test() + val invisible: Any = test() } object outer { - val single = T{ test() } - val invisible: Any = T{ test() } + val single = test() + val invisible: Any = test() object nested{ - val single = T{ test() } - val invisible: Any = T{ test() } + val single = test() + val invisible: Any = test() } val classInstance = new CanNest @@ -36,66 +36,6 @@ object GraphTests extends TestSuite{ ) assert(discovered == expected) } - 'syntaxLimits - { - // Make sure that we properly prohibit cases where a `test()` target can - // be created more than once with the same `DefCtx`, while still allowing - // cases where the `test()` target is created exactly one time, or even - // zero-or-one times (since that's ok, as long as it's not more than once) - - 'neg - { - 'nakedTest - { - compileError("test()") - () - } - 'notFunctionCall - { - compileError("T{ 123 }") - () - } - 'functionCallWithoutImplicit - { - compileError("T{ println() }") - () - } - // Make sure the snippets without `test()`s compile, but the same snippets - // *with* the `test()` calls do not (presumably due to the `@compileTimeOnly` - // annotation) - // - // For some reason, `if(false)` isn't good enough because scalac constant - // folds the conditional, eliminates the entire code block, and makes any - // `@compileTimeOnly`s annotations disappear... - - - 'canEvaluateMoreThanOnce - { - if (math.random() > 10) T{ Seq(1, 2).map(_ => ???); test() } - compileError("T{ Seq(1, 2).map(_ => test()); test() }") - - if (math.random() > 10) T{ class Foo{ ??? }; test() } - compileError("T{ class Foo{ test() }; test() }") - - if (math.random() > 10) T{ test({while(true){ }; ???}) } - compileError("T{ test({while(true){ test() }; ???}) }") - - if (math.random() > 10) T{ do{ } while(true); test() } - compileError("T{ do{ test() } while(true); test() }") - - if (math.random() > 10) T{ def foo() = ???; test() } - compileError("T{ def foo() = test(); test() }") - - if (math.random() > 10) T{ None.getOrElse(???); test() } - if (math.random() > 10) T{ None.contains(test()); test() } - compileError("T{ None.getOrElse(test()); test() }") - - () - } - } - 'pos - { - T{ test({val x = test(); x}) } - T{ test({lazy val x = test(); x}) } - T { object foo {val x = test()}; test(foo.x) } - T{ test({val x = if (math.random() > 0.5) test() else test(); x}) } - - () - } - } 'topoSortedTransitiveTargets - { @@ -142,15 +82,19 @@ object GraphTests extends TestSuite{ target: Target.Test, expected: OSet[(OSet[Target.Test], Int)]) = { + val mapping: Map[Target[_], Seq[String]] = { + implicitly[Discovered[T]].apply(base).map(_.swap).toMap + } val grouped = Evaluator.groupAroundNamedTargets( - Evaluator.topoSortedTransitiveTargets(OSet(target)) + Evaluator.topoSortedTransitiveTargets(OSet(target)), + mapping ) TestUtil.checkTopological(grouped.flatMap(_.items)) for(((expectedPresent, expectedSize), i) <- expected.items.zipWithIndex){ val grouping = grouped.items(i) assert( grouping.size == expectedSize, - expectedPresent.forall(grouping.contains) + grouping.filter(mapping.contains) == expectedPresent ) } } diff --git a/src/test/scala/forge/Main.scala b/src/test/scala/forge/Main.scala index d9262627..b65099ec 100644 --- a/src/test/scala/forge/Main.scala +++ b/src/test/scala/forge/Main.scala @@ -7,34 +7,32 @@ object Main{ def main(args: Array[String]): Unit = { - val sourceRoot = T{ Target.path(jnio.Paths.get("src/test/resources/example/src")) } - val resourceRoot = T{ Target.path(jnio.Paths.get("src/test/resources/example/resources")) } - val allSources = T{ list(sourceRoot) } - val classFiles = T{ compileAll(allSources) } - val jar = T{ jarUp(resourceRoot, classFiles) } - - val evaluator = new Evaluator( - jnio.Paths.get("target/workspace"), - DefCtx("forge.Main ", None) - ) - evaluator.evaluate(OSet(jar)) + val sourceRoot = Target.path(jnio.Paths.get("src/test/resources/example/src")) + val resourceRoot = Target.path(jnio.Paths.get("src/test/resources/example/resources")) + val allSources = list(sourceRoot) + val classFiles = compileAll(allSources) + val jar = jarUp(resourceRoot, classFiles) + +// val evaluator = new Evaluator( +// jnio.Paths.get("target/workspace"), +// DefCtx("forge.Main ", None) +// ) +// evaluator.evaluate(OSet(jar)) } - def compileAll(sources: Target[Seq[jnio.Path]]) - (implicit defCtx: DefCtx) = { + def compileAll(sources: Target[Seq[jnio.Path]]) = { new Target.Subprocess( Seq(sources), args => Seq("javac") ++ args[Seq[jnio.Path]](0).map(_.toAbsolutePath.toString) ++ - Seq("-d", args.dest.toAbsolutePath.toString), - defCtx + Seq("-d", args.dest.toAbsolutePath.toString) ).map(_.dest) } - def list(root: Target[jnio.Path])(implicit defCtx: DefCtx): Target[Seq[jnio.Path]] = { + def list(root: Target[jnio.Path]): Target[Seq[jnio.Path]] = { root.map(jnio.Files.list(_).iterator().asScala.toArray[jnio.Path]) } - case class jarUp(roots: Target[jnio.Path]*)(implicit val defCtx: DefCtx) extends Target[jnio.Path]{ + case class jarUp(roots: Target[jnio.Path]*) extends Target[jnio.Path]{ val inputs = roots def evaluate(args: Args): jnio.Path = { diff --git a/src/test/scala/forge/TestUtil.scala b/src/test/scala/forge/TestUtil.scala index 2eea1e0d..f06dc6b6 100644 --- a/src/test/scala/forge/TestUtil.scala +++ b/src/test/scala/forge/TestUtil.scala @@ -19,26 +19,26 @@ object TestUtil { class TestGraphs(){ object singleton { - val single = T{ test() } + val single = test() } object pair { - val up = T{ test() } - val down = T{ test(up) } + val up = test() + val down = test(up) } object anonTriple{ - val up = T{ test() } - val down = T{ test(test(up)) } + val up = test() + val down = test(test(up)) } object diamond{ - val up = T{ test() } - val left = T{ test(up) } - val right = T{ test(up) } - val down = T{ test(left, right) } + val up = test() + val left = test(up) + val right = test(up) + val down = test(left, right) } object anonDiamond{ - val up = T{ test() } - val down = T{ test(test(up), test(up)) } + val up = test() + val down = test(test(up), test(up)) } // o g-----o @@ -53,24 +53,21 @@ object TestUtil { // / / // o--B o object bigSingleTerminal{ - val a = T{ test(test(), test()) } - val b = T{ test(test()) } - val e = T{ + val a = test(test(), test()) + val b = test(test()) + val e = { val c = test(a) val d = test(a) test(test(test(), test(c)), test(test(c, test(d, b)))) } - val f = T{ - test(test(test(), test(e))) - } - val i = T{ + val f = test(test(test(), test(e))) + + val i = { val g = test() val h = test(g, e) test(test(g), test(test(h))) } - val j = T{ - test(test(i), test(i, f), test(f)) - } + val j = test(test(i), test(i, f), test(f)) } (singleton, pair, anonTriple, diamond, anonDiamond, bigSingleTerminal) |