diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/src/main/scala/forge/Target.scala | 26 | ||||
-rw-r--r-- | core/src/test/scala/forge/CacherTests.scala | 35 |
2 files changed, 56 insertions, 5 deletions
diff --git a/core/src/main/scala/forge/Target.scala b/core/src/main/scala/forge/Target.scala index 635acb8e..a829d433 100644 --- a/core/src/main/scala/forge/Target.scala +++ b/core/src/main/scala/forge/Target.scala @@ -51,13 +51,33 @@ object Target{ } def impl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]): c.Expr[Target[T]] = { import c.universe._ + def rec(t: Tree): Iterator[c.Tree] = Iterator(t) ++ t.children.flatMap(rec(_)) val bound = collection.mutable.Buffer.empty[(c.Tree, Symbol)] - val OptionGet = c.universe.typeOf[Target[_]].member(TermName("apply")) + val targetApplySym = c.universe.typeOf[Target[_]].member(TermName("apply")) // 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 macroSource = t.tree.pos.source val transformed = c.internal.typingTransform(t.tree) { - case (t @ q"$fun.apply()", api) if t.symbol == OptionGet => + case (t @ q"$fun.apply()", api) if t.symbol == targetApplySym => + + val used = rec(t) + val banned = used.filter(x => + x.symbol.pos.source == macroSource && + x.symbol.pos.start >= startPos && + x.symbol.pos.end <= endPos + ) + if (banned.hasNext){ + val banned0 = banned.next() + c.abort( + banned0.pos, + "Target#apply() call cannot use `" + banned0.symbol + "` defined within the T{...} block" + ) + } val tempName = c.freshName(TermName("tmp")) val tempSym = c.internal.newTermSymbol(api.currentOwner, tempName) c.internal.setInfo(tempSym, t.tpe) @@ -77,7 +97,7 @@ object Target{ def wrapCached[T](c: Context)(t: c.Tree) = { import c.universe._ val owner = c.internal.enclosingOwner - val ownerIsCacherClass = owner.owner.asClass.baseClasses.exists(_.fullName == "forge.Target.Cacher") + val ownerIsCacherClass = owner.owner.isClass && owner.owner.asClass.baseClasses.exists(_.fullName == "forge.Target.Cacher") if (ownerIsCacherClass && !owner.isMethod){ c.abort( diff --git a/core/src/test/scala/forge/CacherTests.scala b/core/src/test/scala/forge/CacherTests.scala index de8265bd..d77287ac 100644 --- a/core/src/test/scala/forge/CacherTests.scala +++ b/core/src/test/scala/forge/CacherTests.scala @@ -50,8 +50,39 @@ object CacherTests extends TestSuite{ val expectedMsg = "T{} members defined in a Cacher class/trait/object body must be defs" - val err1 = compileError("object Foo extends Target.Cacher{ val x = T{1} }") - assert(err1.msg == expectedMsg) + val err = compileError("object Foo extends Target.Cacher{ val x = T{1} }") + assert(err.msg == expectedMsg) + } + 'badTmacro - { + // Make sure we can reference values from outside the T{...} block as part + // of our `Target#apply()` calls, but we cannot reference any values that + // come from inside the T{...} block + 'pos - { + val a = T{ 1 } + val arr = Array(a) + val b = { + val c = 0 + T{ + arr(c)() + } + } + } + 'neg - { + + val expectedMsg = + "Target#apply() call cannot use `value n` defined within the T{...} block" + val err = compileError("""{ + val a = T{ 1 } + val arr = Array(a) + val b = { + T{ + val n = 0 + arr(n)() + } + } + }""") + assert(err.msg == expectedMsg) + } } } } |