diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2017-11-05 08:18:46 -0800 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2017-11-05 08:18:46 -0800 |
commit | 8a873f3336400035a9fae5ed64a57b022d9b48da (patch) | |
tree | fc6b9a982b5a3573bd3b3eb694c11619bcdb4289 /core | |
parent | 3ebb8788b8fb3d09dedb7ac11956d780dfadb34b (diff) | |
download | mill-8a873f3336400035a9fae5ed64a57b022d9b48da.tar.gz mill-8a873f3336400035a9fae5ed64a57b022d9b48da.tar.bz2 mill-8a873f3336400035a9fae5ed64a57b022d9b48da.zip |
First set of standalone tests for the gnarly `Applicative` logic
Diffstat (limited to 'core')
-rw-r--r-- | core/src/main/scala/forge/define/Applicative.scala | 18 | ||||
-rw-r--r-- | core/src/main/scala/forge/define/Target.scala | 7 | ||||
-rw-r--r-- | core/src/test/scala/forge/ApplicativeMacrosTests.scala | 41 | ||||
-rw-r--r-- | core/src/test/scala/forge/ApplicativeTests.scala | 88 |
4 files changed, 97 insertions, 57 deletions
diff --git a/core/src/main/scala/forge/define/Applicative.scala b/core/src/main/scala/forge/define/Applicative.scala index ba71221e..ac7c6de5 100644 --- a/core/src/main/scala/forge/define/Applicative.scala +++ b/core/src/main/scala/forge/define/Applicative.scala @@ -11,7 +11,9 @@ object Applicative { @compileTimeOnly("Target#apply() can only be used with a T{...} block") def apply(): T = ??? } - trait Applyer[T[_]]{ + trait Applyer[W[_], T[_]]{ + def underlying[A](v: W[A]): T[_] + def map[A, B](a: T[A], f: A => B): T[B] def zipMap[R]()(f: () => R) = map(zip(), (_: Unit) => f()) def zipMap[A, R](a: T[A])(f: A => R) = map(a, f) @@ -55,20 +57,14 @@ object Applicative { // Derived from @olafurpg's // https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608 - val (startPos, endPos) = rec(t.tree) - .map(t => (t.pos.start, t.pos.end)) - .reduce[(Int, Int)]{ case ((s1, e1), (s2, e2)) => (math.min(s1, s2), math.max(e1, e2))} + val defs = rec(t.tree).filter(_.isDef).map(_.symbol).toSet - val macroSource = t.tree.pos.source val transformed = c.internal.typingTransform(t.tree) { case (t @ q"$fun.apply()", api) if t.symbol == targetApplySym => + val localDefs = rec(fun).filter(_.isDef).map(_.symbol).toSet val used = rec(t) - val banned = used.filter(x => - x.symbol.pos.source == macroSource && - x.symbol.pos.start >= startPos && - x.symbol.pos.end <= endPos - ) + val banned = used.filter(x => defs(x.symbol) && !localDefs(x.symbol)) if (banned.hasNext){ val banned0 = banned.next() c.abort( @@ -81,7 +77,7 @@ object Applicative { c.internal.setInfo(tempSym, t.tpe) val tempIdent = Ident(tempSym) c.internal.setType(tempIdent, t.tpe) - bound.append((fun, tempSym)) + bound.append((q"${c.prefix}.underlying($fun)", tempSym)) tempIdent case (t, api) => api.default(t) } diff --git a/core/src/main/scala/forge/define/Target.scala b/core/src/main/scala/forge/define/Target.scala index 0aafee89..b30db434 100644 --- a/core/src/main/scala/forge/define/Target.scala +++ b/core/src/main/scala/forge/define/Target.scala @@ -6,10 +6,7 @@ import forge.eval.PathRef import forge.util.{Args, JsonFormatters} import play.api.libs.json.{Format, Json} -import scala.annotation.compileTimeOnly -import scala.collection.mutable import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context abstract class Target[T] extends Target.Ops[T] with Applyable[T]{ /** @@ -29,8 +26,8 @@ abstract class Target[T] extends Target.Ops[T] with Applyable[T]{ def sideHash: Int = 0 } -object Target extends Applicative.Applyer[Target]{ - +object Target extends Applicative.Applyer[Target, Target]{ + def underlying[A](v: Target[A]) = v type Cacher = Applicative.Cacher[Target[_]] class Target0[T](t: T) extends Target[T]{ lazy val t0 = t diff --git a/core/src/test/scala/forge/ApplicativeMacrosTests.scala b/core/src/test/scala/forge/ApplicativeMacrosTests.scala deleted file mode 100644 index daeaa9b4..00000000 --- a/core/src/test/scala/forge/ApplicativeMacrosTests.scala +++ /dev/null @@ -1,41 +0,0 @@ -package forge -import forge.define.{Applicative, Target} -import utest._ -import language.experimental.macros - -object ApplicativeMacrosTests extends TestSuite /*with define.Applicative.Applyer[Option]*/ { - -// def apply[T](t: Option[T]): Option[T] = macro Applicative.impl0[Option, T] -// def apply[T](t: T): Option[T] = macro Applicative.impl[Option, T] -// -// type O[+T] = Option[T] -// def map[A, B](a: O[A], f: A => B) = a.map(f) -// def zip() = Some(()) -// def zip[A](a: O[A]) = a.map(Tuple1(_)) -// def zip[A, B](a: O[A], b: O[B]) = { -// for(a <- a; b <- b) yield (a, b) -// } -// def zip[A, B, C](a: O[A], b: O[B], c: O[C]) = { -// for(a <- a; b <- b; c <- c) yield (a, b, c) -// } -// def zip[A, B, C, D](a: O[A], b: O[B], c: O[C], d: O[D]) = { -// for(a <- a; b <- b; c <- c; d <- d) yield (a, b, c, d) -// } -// def zip[A, B, C, D, E](a: O[A], b: O[B], c: O[C], d: O[D], e: O[E]) = { -// for(a <- a; b <- b; c <- c; d <- d; e <- e) yield (a, b, c, d, e) -// } -// def zip[A, B, C, D, E, F](a: O[A], b: O[B], c: O[C], d: O[D], e: O[E], f: O[F]) ={ -// for(a <- a; b <- b; c <- c; d <- d; e <- e; f <- f) yield (a, b, c, d, e, f) -// } -// def zip[A, B, C, D, E, F, G](a: O[A], b: O[B], c: O[C], d: O[D], e: O[E], f: O[F], g: O[G]) = { -// for(a <- a; b <- b; c <- c; d <- d; e <- e; f <- f; g <- g) yield (a, b, c, d, e, f, g) -// } - val tests = Tests{ - 'hello - { -// assert( -// apply("lol " + 1) == Some("lol 1"), -// apply("lol " + None()) == None -// ) - } - } -} diff --git a/core/src/test/scala/forge/ApplicativeTests.scala b/core/src/test/scala/forge/ApplicativeTests.scala new file mode 100644 index 00000000..462cb180 --- /dev/null +++ b/core/src/test/scala/forge/ApplicativeTests.scala @@ -0,0 +1,88 @@ +package forge +import forge.define.Applicative +import utest._ +import language.experimental.macros + + +object ApplicativeTests extends TestSuite { + implicit def optionToOpt[T](o: Option[T]): Opt[T] = new Opt(o) + class Opt[T](val o: Option[T]) extends Applicative.Applyable[T] + object Opt extends define.Applicative.Applyer[Opt, Option]{ + + def underlying[A](v: Opt[A]) = v.o + def apply[T](t: Option[T]): Option[T] = macro Applicative.impl0[Option, T] + def apply[T](t: T): Option[T] = macro Applicative.impl[Option, T] + + type O[+T] = Option[T] + def map[A, B](a: O[A], f: A => B) = a.map(f) + def zip() = Some(()) + def zip[A](a: O[A]) = a.map(Tuple1(_)) + def zip[A, B](a: O[A], b: O[B]) = { + for(a <- a; b <- b) yield (a, b) + } + def zip[A, B, C](a: O[A], b: O[B], c: O[C]) = { + for(a <- a; b <- b; c <- c) yield (a, b, c) + } + def zip[A, B, C, D](a: O[A], b: O[B], c: O[C], d: O[D]) = { + for(a <- a; b <- b; c <- c; d <- d) yield (a, b, c, d) + } + def zip[A, B, C, D, E](a: O[A], b: O[B], c: O[C], d: O[D], e: O[E]) = { + for(a <- a; b <- b; c <- c; d <- d; e <- e) yield (a, b, c, d, e) + } + def zip[A, B, C, D, E, F](a: O[A], b: O[B], c: O[C], d: O[D], e: O[E], f: O[F]) ={ + for(a <- a; b <- b; c <- c; d <- d; e <- e; f <- f) yield (a, b, c, d, e, f) + } + def zip[A, B, C, D, E, F, G](a: O[A], b: O[B], c: O[C], d: O[D], e: O[E], f: O[F], g: O[G]) = { + for(a <- a; b <- b; c <- c; d <- d; e <- e; f <- f; g <- g) yield (a, b, c, d, e, f, g) + } + } + class Counter{ + var value = 0 + def apply() = { + value += 1 + value + } + } + + val tests = Tests{ + + 'selfContained - { + + 'simple - assert(Opt("lol " + 1) == Some("lol 1")) + 'singleSome - assert(Opt("lol " + Some("hello")()) == Some("lol hello")) + 'twoSomes - assert(Opt(Some("lol ")() + Some("hello")()) == Some("lol hello")) + 'singleNone - assert(Opt("lol " + None()) == None) + 'twoNones - assert(Opt("lol " + None() + None()) == None) + } + 'capturing - { + val lol = "lol " + def hell(o: String) = "hell" + o + 'simple - assert(Opt(lol + 1) == Some("lol 1")) + 'singleSome - assert(Opt(lol + Some(hell("o"))()) == Some("lol hello")) + 'twoSomes - assert(Opt(Some(lol)() + Some(hell("o"))()) == Some("lol hello")) + 'singleNone - assert(Opt(lol + None()) == None) + 'twoNones - assert(Opt(lol + None() + None()) == None) + } + 'allowedLocalDef - { + // Although x is defined inside the Opt{...} block, it is also defined + // within the LHS of the Applyable#apply call, so it is safe to life it + // out into the `zipMap` arguments list. + val res = Opt{ "lol " + Some("hello").flatMap(x => Some(x)).apply() } + assert(res == Some("lol hello")) + } + 'upstreamAlwaysEvaluated - { + // Validate the assumption that whether or not control-flow reaches the + // Applyable#apply call inside an Opt{...} block, we always evaluate the + // LHS of the Applyable#apply because it gets lifted out of any control + // flow statements + val counter = new Counter() + def up = Opt{ "lol " + counter() } + val down = Opt{ if ("lol".length > 10) up() else "fail" } + assert( + down == Some("fail"), + counter.value == 1 + ) + } + } +} + |