mirror of https://github.com/sbt/sbt.git
Construct input tasks in multiple steps to allow input task reuse. Fixes #407.
This commit is contained in:
parent
d426035da3
commit
ee5f09d810
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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._
|
||||
|
|
|
|||
Loading…
Reference in New Issue