summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2017-11-05 08:18:46 -0800
committerLi Haoyi <haoyi.sg@gmail.com>2017-11-05 08:18:46 -0800
commit8a873f3336400035a9fae5ed64a57b022d9b48da (patch)
treefc6b9a982b5a3573bd3b3eb694c11619bcdb4289
parent3ebb8788b8fb3d09dedb7ac11956d780dfadb34b (diff)
downloadmill-8a873f3336400035a9fae5ed64a57b022d9b48da.tar.gz
mill-8a873f3336400035a9fae5ed64a57b022d9b48da.tar.bz2
mill-8a873f3336400035a9fae5ed64a57b022d9b48da.zip
First set of standalone tests for the gnarly `Applicative` logic
-rw-r--r--core/src/main/scala/forge/define/Applicative.scala18
-rw-r--r--core/src/main/scala/forge/define/Target.scala7
-rw-r--r--core/src/test/scala/forge/ApplicativeMacrosTests.scala41
-rw-r--r--core/src/test/scala/forge/ApplicativeTests.scala88
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
+ )
+ }
+ }
+}
+