Construct input tasks in multiple steps to allow input task reuse. Fixes #407.

This commit is contained in:
Mark Harrah 2013-03-08 14:23:30 -05:00
parent d426035da3
commit ee5f09d810
3 changed files with 74 additions and 66 deletions

View File

@ -20,11 +20,11 @@ object ContextUtil {
*
* Given `myImplicitConversion(someValue).extensionMethod`, where `extensionMethod` is a macro that uses this
* method, the result of this method is `f(<Tree of someValue>)`. */
def selectMacroImpl[T: c.WeakTypeTag, S: c.WeakTypeTag](c: Context)(f: (c.Expr[S], c.Position) => c.Expr[T]): c.Expr[T] =
def selectMacroImpl[T: c.WeakTypeTag](c: Context)(f: (c.Expr[Any], c.Position) => c.Expr[T]): c.Expr[T] =
{
import c.universe._
c.macroApplication match {
case s @ Select(Apply(_, t :: Nil), tp) => f( c.Expr[S](t), s.pos )
case s @ Select(Apply(_, t :: Nil), tp) => f( c.Expr[Any](t), s.pos )
case x => unexpectedTree(x)
}
}
@ -61,24 +61,18 @@ final class ContextUtil[C <: Context](val ctx: C)
vd
}
/* Tests whether a Tree is a Select on `methodName`. */
def isWrapper(methodName: String): Tree => Boolean = {
case Select(_, nme) => nme.decoded == methodName
case _ => false
}
lazy val parameterModifiers = Modifiers(Flag.PARAM)
/** Collects all definitions in the tree for use in checkReferences.
* This excludes definitions in wrapped expressions because checkReferences won't allow nested dereferencing anyway. */
def collectDefs(tree: Tree, isWrapper: Tree => Boolean): collection.Set[Symbol] =
def collectDefs(tree: Tree, isWrapper: (String, Type, Tree) => Boolean): collection.Set[Symbol] =
{
val defs = new collection.mutable.HashSet[Symbol]
// adds the symbols for all non-Ident subtrees to `defs`.
val process = new Traverser {
override def traverse(t: Tree) = t match {
case _: Ident => ()
case ApplyTree(TypeApply(fun, tpe :: Nil), qual :: Nil) if isWrapper(fun) => ()
case ApplyTree(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil) if isWrapper(nme.decoded, tpe.tpe, qual) => ()
case tree =>
if(tree.symbol ne null) defs += tree.symbol;
super.traverse(tree)
@ -95,8 +89,9 @@ final class ContextUtil[C <: Context](val ctx: C)
/** A function that checks the provided tree for illegal references to M instances defined in the
* expression passed to the macro and for illegal dereferencing of M instances. */
def checkReferences(defs: collection.Set[Symbol], isWrapper: Tree => Boolean): Tree => Unit = {
case s @ ApplyTree(TypeApply(fun, tpe :: Nil), qual :: Nil) => if(isWrapper(fun)) ctx.error(s.pos, DynamicDependencyError)
def checkReferences(defs: collection.Set[Symbol], isWrapper: (String, Type, Tree) => Boolean): Tree => Unit = {
case s @ ApplyTree(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil) =>
if(isWrapper(nme.decoded, tpe.tpe, qual)) ctx.error(s.pos, DynamicDependencyError)
case id @ Ident(name) if illegalReference(defs, id.symbol) => ctx.error(id.pos, DynamicReferenceError + ": " + name)
case _ => ()
}
@ -189,10 +184,10 @@ final class ContextUtil[C <: Context](val ctx: C)
}
/** Substitutes wrappers in tree `t` with the result of `subWrapper`.
* A wrapper is a Tree of the form `f[T](v)` for which isWrapper(<Tree of f>) returns true.
* A wrapper is a Tree of the form `f[T](v)` for which isWrapper(<Tree of f>, <Underlying Type>, <qual>.target) returns true.
* Typically, `f` is a `Select` or `Ident`.
* The wrapper is replaced with the result of `subWrapper(<Type of T>, <Tree of v>)` */
def transformWrappers(t: Tree, isWrapper: Tree => Boolean, subWrapper: (Type, Tree) => Tree): Tree =
def transformWrappers(t: Tree, subWrapper: (String, Type, Tree) => Converted[ctx.type]): Tree =
{
// the main tree transformer that replaces calls to InputWrapper.wrap(x) with
// plain Idents that reference the actual input value
@ -201,9 +196,11 @@ final class ContextUtil[C <: Context](val ctx: C)
override def transform(tree: Tree): Tree =
tree match
{
case ApplyTree(TypeApply(fun, targ :: Nil), qual :: Nil) if isWrapper(fun) =>
assert(qual.tpe != null, "Internal error: null type for wrapped tree with " + qual.getClass + "\n\t" + qual + "\n in " + t)
subWrapper(targ.tpe, qual)
case ApplyTree(TypeApply(Select(_, nme), targ :: Nil), qual :: Nil) => subWrapper(nme.decoded, targ.tpe, qual) match {
case Converted.Success(t, finalTx) => finalTx(t)
case Converted.Failure(p,m) => ctx.abort(p, m)
case _: Converted.NotApplicable[_] => super.transform(tree)
}
case _ => super.transform(tree)
}
}

View File

@ -0,0 +1,38 @@
package sbt
package appmacro
import scala.reflect._
import macros._
import Types.idFun
abstract class Convert
{
def apply[T: c.WeakTypeTag](c: Context)(nme: String, in: c.Tree): Converted[c.type]
def asPredicate(c: Context): (String, c.Type, c.Tree) => Boolean =
(n,tpe,tree) => {
val tag = c.WeakTypeTag(tpe)
apply(c)(n,tree)(tag).isSuccess
}
}
sealed trait Converted[C <: Context with Singleton] {
def isSuccess: Boolean
def transform(f: C#Tree => C#Tree): Converted[C]
}
object Converted {
def NotApplicable[C <: Context with Singleton] = new NotApplicable[C]
final case class Failure[C <: Context with Singleton](position: C#Position, message: String) extends Converted[C] {
def isSuccess = false
def transform(f: C#Tree => C#Tree): Converted[C] = new Failure(position, message)
}
final class NotApplicable[C <: Context with Singleton] extends Converted[C] {
def isSuccess = false
def transform(f: C#Tree => C#Tree): Converted[C] = this
}
final case class Success[C <: Context with Singleton](tree: C#Tree, finalTransform: C#Tree => C#Tree) extends Converted[C] {
def isSuccess = true
def transform(f: C#Tree => C#Tree): Converted[C] = Success(f(tree), finalTransform)
}
object Success {
def apply[C <: Context with Singleton](tree: C#Tree): Success[C] = Success(tree, idFun)
}
}

View File

@ -16,10 +16,7 @@ trait Instance
def map[S,T](in: M[S], f: S => T): M[T]
def pure[T](t: () => T): M[T]
}
trait Convert
{
def apply[T: c.WeakTypeTag](c: scala.reflect.macros.Context)(in: c.Tree): c.Tree
}
trait MonadInstance extends Instance
{
def flatten[T](in: M[M[T]]): M[T]
@ -29,39 +26,6 @@ trait MonadInstance extends Instance
import macros._
import reflect.internal.annotations.compileTimeOnly
// This needs to be moved to main/settings
object InputWrapper
{
/** The name of the wrapper method should be obscure.
* Wrapper checking is based solely on this name, so it must not conflict with a user method name.
* The user should never see this method because it is compile-time only and only used internally by the task macro system.*/
final val WrapName = "wrap_\u2603\u2603"
@compileTimeOnly("`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.")
def wrap_\u2603\u2603[T](in: Any): T = sys.error("This method is an implementation detail and should not be referenced.")
def wrapKey[T: c.WeakTypeTag](c: Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] = wrapImpl[T,InputWrapper.type](c, InputWrapper, WrapName)(ts, pos)
/** Wraps an arbitrary Tree in a call to the `<s>.<wrapName>` method of this module for later processing by an enclosing macro.
* The resulting Tree is the manually constructed version of:
*
* `c.universe.reify { <s>.<wrapName>[T](ts.splice) }`
*/
def wrapImpl[T: c.WeakTypeTag, S <: AnyRef with Singleton](c: Context, s: S, wrapName: String)(ts: c.Expr[Any], pos: c.Position)(implicit it: c.TypeTag[s.type]): c.Expr[T] =
{
import c.universe.{Apply=>ApplyTree,_}
val util = new ContextUtil[c.type](c)
val iw = util.singleton(s)
val tpe = c.weakTypeOf[T]
val nme = newTermName(wrapName).encoded
val sel = util.select(Ident(iw), nme)
sel.setPos(pos) // need to set the position on Select, because that is where the compileTimeOnly check looks
val tree = ApplyTree(TypeApply(sel, TypeTree(tpe) :: Nil), ts.tree :: Nil)
tree.setPos(ts.tree.pos)
c.Expr[T](tree)
}
}
object Instance
{
final val ApplyName = "app"
@ -71,11 +35,17 @@ object Instance
final val InstanceTCName = "M"
final class Input[U <: Universe with Singleton](val tpe: U#Type, val expr: U#Tree, val local: U#ValDef)
trait Transform[C <: Context with Singleton, N[_]] {
def apply(in: C#Tree): C#Tree
}
def idTransform[C <: Context with Singleton]: Transform[C,Id] = new Transform[C,Id] {
def apply(in: C#Tree): C#Tree = in
}
/** Implementation of a macro that provides a direct syntax for applicative functors and monads.
* It is intended to be used in conjunction with another macro that conditions the inputs.
*
* This method processes the Tree `t` to find inputs of the form `InputWrapper.wrap[T]( input )`
* This method processes the Tree `t` to find inputs of the form `wrap[T]( input )`
* This form is typically constructed by another macro that pretends to be able to get a value of type `T`
* from a value convertible to `M[T]`. This `wrap(input)` form has two main purposes.
* First, it identifies the inputs that should be transformed.
@ -85,7 +55,7 @@ object Instance
* allowing the original `Tree` and `Type` to be hidden behind the raw `T` type. This method will remove the call to `wrap`
* so that it is not actually called at runtime.
*
* Each `input` in each expression of the form `InputWrapper.wrap[T]( input )` is transformed by `convert`.
* Each `input` in each expression of the form `wrap[T]( input )` is transformed by `convert`.
* This transformation converts the input Tree to a Tree of type `M[T]`.
* The original wrapped expression `wrap(input)` is replaced by a reference to a new local `val $x: T`, where `$x` is a fresh name.
* These converted inputs are passed to `builder` as well as the list of these synthetic `ValDef`s.
@ -107,18 +77,18 @@ object Instance
* If this is for multi-input flatMap (app followed by flatMap),
* this should be the argument wrapped in Right.
*/
def contImpl[T](c: Context, i: Instance with Singleton, convert: Convert, builder: TupleBuilder)(t: Either[c.Expr[T], c.Expr[i.M[T]]])(
implicit tt: c.WeakTypeTag[T], it: c.TypeTag[i.type]): c.Expr[i.M[T]] =
def contImpl[T,N[_]](c: Context, i: Instance with Singleton, convert: Convert, builder: TupleBuilder)(t: Either[c.Expr[T], c.Expr[i.M[T]]], inner: Transform[c.type,N])(
implicit tt: c.WeakTypeTag[T], nt: c.WeakTypeTag[N[T]], it: c.TypeTag[i.type]): c.Expr[i.M[N[T]]] =
{
import c.universe.{Apply=>ApplyTree,_}
val util = ContextUtil[c.type](c)
val mTC: Type = util.extractTC(i, InstanceTCName)
val mttpe: Type = appliedType(mTC, tt.tpe :: Nil).normalize
val mttpe: Type = appliedType(mTC, nt.tpe :: Nil).normalize
// the tree for the macro argument
val (tree, treeType) = t match {
case Left(l) => (l.tree, tt.tpe.normalize)
case Left(l) => (l.tree, nt.tpe.normalize)
case Right(r) => (r.tree, mttpe)
}
@ -126,7 +96,7 @@ object Instance
// A Tree that references the statically accessible Instance that provides the actual implementations of map, flatMap, ...
val instance = Ident(instanceSym)
val isWrapper: Tree => Boolean = util.isWrapper(InputWrapper.WrapName)
val isWrapper: (String, Type, Tree) => Boolean = convert.asPredicate(c)
type In = Input[c.universe.type]
var inputs = List[In]()
@ -194,16 +164,19 @@ object Instance
inputs ::= new Input(tpe, qual, vd)
util.refVal(vd)
}
def sub(tpe: Type, qual: Tree): Tree =
def sub(name: String, tpe: Type, qual: Tree): Converted[c.type] =
{
val tag = c.WeakTypeTag(tpe)
addType(tpe, convert(c)(qual)(tag) )
val tag = c.WeakTypeTag[T](tpe)
convert[T](c)(name, qual)(tag) transform { tree =>
addType(tpe, tree)
}
}
// applies the transformation
val tx = util.transformWrappers(tree, (n,tpe,t) => sub(n,tpe,t))
// resetting attributes must be: a) local b) done here and not wider or else there are obscure errors
val tr = makeApp( c.resetLocalAttrs( util.transformWrappers(tree, isWrapper, (tpe, tr) => sub(tpe, tr)) ) )
c.Expr[i.M[T]](tr)
val tr = makeApp( c.resetLocalAttrs( inner(tx) ) )
c.Expr[i.M[N[T]]](tr)
}
import Types._