mirror of https://github.com/sbt/sbt.git
InputTask macro
Similar to task macros, the parsed value is accessed by calling `parsed` on a Parser[T], Initialize[Parser[T]], or Initialize[State => Parser[T]]. Values of tasks and settings may be accessed as usual via `value`.
This commit is contained in:
parent
c2760ecbdd
commit
40a034c3a7
|
|
@ -4,11 +4,32 @@ package appmacro
|
||||||
import scala.reflect._
|
import scala.reflect._
|
||||||
import macros._
|
import macros._
|
||||||
import scala.tools.nsc.Global
|
import scala.tools.nsc.Global
|
||||||
|
import ContextUtil.{DynamicDependencyError, DynamicReferenceError}
|
||||||
|
|
||||||
object ContextUtil {
|
object ContextUtil {
|
||||||
|
final val DynamicDependencyError = "Illegal dynamic dependency"
|
||||||
|
final val DynamicReferenceError = "Illegal dynamic reference"
|
||||||
|
|
||||||
/** Constructs an object with utility methods for operating in the provided macro context `c`.
|
/** Constructs an object with utility methods for operating in the provided macro context `c`.
|
||||||
* Callers should explicitly specify the type parameter as `c.type` in order to preserve the path dependent types. */
|
* Callers should explicitly specify the type parameter as `c.type` in order to preserve the path dependent types. */
|
||||||
def apply[C <: Context with Singleton](c: C): ContextUtil[C] = new ContextUtil(c)
|
def apply[C <: Context with Singleton](c: C): ContextUtil[C] = new ContextUtil(c)
|
||||||
|
|
||||||
|
|
||||||
|
/** Helper for implementing a no-argument macro that is introduced via an implicit.
|
||||||
|
* This method removes the implicit conversion and evaluates the function `f` on the target of the conversion.
|
||||||
|
*
|
||||||
|
* 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.Expr[T]): c.Expr[T] =
|
||||||
|
{
|
||||||
|
import c.universe._
|
||||||
|
c.macroApplication match {
|
||||||
|
case Select(Apply(_, t :: Nil), _) => f( c.Expr[S](t) )
|
||||||
|
case x => unexpectedTree(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def unexpectedTree[C <: Context](tree: C#Tree): Nothing = error("Unexpected macro application tree (" + tree.getClass + "): " + tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Utility methods for macros. Several methods assume that the context's universe is a full compiler (`scala.tools.nsc.Global`).
|
/** Utility methods for macros. Several methods assume that the context's universe is a full compiler (`scala.tools.nsc.Global`).
|
||||||
|
|
@ -42,6 +63,50 @@ final class ContextUtil[C <: Context](val ctx: C)
|
||||||
vd
|
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] =
|
||||||
|
{
|
||||||
|
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 tree =>
|
||||||
|
if(tree.symbol ne null) defs += tree.symbol;
|
||||||
|
super.traverse(tree)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process.traverse(tree)
|
||||||
|
defs
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A reference is illegal if it is to an M instance defined within the scope of the macro call.
|
||||||
|
* As an approximation, disallow referenced to any local definitions `defs`. */
|
||||||
|
def illegalReference(defs: collection.Set[Symbol], sym: Symbol): Boolean =
|
||||||
|
sym != null && sym != NoSymbol && defs.contains(sym)
|
||||||
|
|
||||||
|
/** 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)
|
||||||
|
case id @ Ident(name) if illegalReference(defs, id.symbol) => ctx.error(id.pos, DynamicReferenceError + ": " + name)
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructs a ValDef with a parameter modifier, a unique name, with the provided Type and with an empty rhs. */
|
||||||
|
def freshMethodParameter(tpe: Type): ValDef =
|
||||||
|
ValDef(parameterModifiers, freshTermName("p"), TypeTree(tpe), EmptyTree)
|
||||||
|
|
||||||
/** Constructs a ValDef with local modifiers and a unique name. */
|
/** Constructs a ValDef with local modifiers and a unique name. */
|
||||||
def localValDef(tpt: Tree, rhs: Tree): ValDef =
|
def localValDef(tpt: Tree, rhs: Tree): ValDef =
|
||||||
ValDef(localModifiers, freshTermName("q"), tpt, rhs)
|
ValDef(localModifiers, freshTermName("q"), tpt, rhs)
|
||||||
|
|
@ -75,8 +140,18 @@ final class ContextUtil[C <: Context](val ctx: C)
|
||||||
tc.setTypeSignature(PolyType(arg :: Nil, emptyTypeBounds))
|
tc.setTypeSignature(PolyType(arg :: Nil, emptyTypeBounds))
|
||||||
tc
|
tc
|
||||||
}
|
}
|
||||||
|
/** >: Nothing <: Any */
|
||||||
def emptyTypeBounds: TypeBounds = TypeBounds(definitions.NothingClass.toType, definitions.AnyClass.toType)
|
def emptyTypeBounds: TypeBounds = TypeBounds(definitions.NothingClass.toType, definitions.AnyClass.toType)
|
||||||
|
|
||||||
|
/** Create a Tree that references the `val` represented by `vd`. */
|
||||||
|
def refVal(vd: ValDef): Tree =
|
||||||
|
{
|
||||||
|
val t = Ident(vd.name)
|
||||||
|
assert(vd.tpt.tpe != null, "val type is null: " + vd + ", tpt: " + vd.tpt.tpe)
|
||||||
|
t.setType(vd.tpt.tpe)
|
||||||
|
t
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the Symbol that references the statically accessible singleton `i`. */
|
/** Returns the Symbol that references the statically accessible singleton `i`. */
|
||||||
def singleton[T <: AnyRef with Singleton](i: T)(implicit it: ctx.TypeTag[i.type]): Symbol =
|
def singleton[T <: AnyRef with Singleton](i: T)(implicit it: ctx.TypeTag[i.type]): Symbol =
|
||||||
it.tpe match {
|
it.tpe match {
|
||||||
|
|
@ -105,4 +180,27 @@ final class ContextUtil[C <: Context](val ctx: C)
|
||||||
assert(tc != NoType && tc.takesTypeArgs, "Invalid type constructor: " + tc)
|
assert(tc != NoType && tc.takesTypeArgs, "Invalid type constructor: " + tc)
|
||||||
tc
|
tc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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.
|
||||||
|
* 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 =
|
||||||
|
{
|
||||||
|
// the main tree transformer that replaces calls to InputWrapper.wrap(x) with
|
||||||
|
// plain Idents that reference the actual input value
|
||||||
|
object appTransformer extends Transformer
|
||||||
|
{
|
||||||
|
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 _ => super.transform(tree)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appTransformer.transform(t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -24,24 +24,45 @@ trait MonadInstance extends Instance
|
||||||
{
|
{
|
||||||
def flatten[T](in: M[M[T]]): M[T]
|
def flatten[T](in: M[M[T]]): M[T]
|
||||||
}
|
}
|
||||||
object InputWrapper
|
|
||||||
{
|
|
||||||
def wrap[T](in: Any): T = error("This method is an implementation detail and should not be referenced.")
|
|
||||||
}
|
|
||||||
|
|
||||||
import scala.reflect._
|
import scala.reflect._
|
||||||
import macros._
|
import macros._
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
// This method should be annotated as compile-time only when that feature is implemented
|
||||||
|
def wrap_\u2603\u2603[T](in: Any): T = error("This method is an implementation detail and should not be referenced.")
|
||||||
|
|
||||||
|
/** Wraps an arbitrary Tree in a call to the `wrap` method of this module for later processing by an enclosing macro.
|
||||||
|
* The resulting Tree is the manually constructed version of:
|
||||||
|
*
|
||||||
|
* `c.universe.reify { InputWrapper.<WrapName>[T](ts.splice) }`
|
||||||
|
*/
|
||||||
|
def wrapKey[T: c.WeakTypeTag](c: Context)(ts: c.Expr[Any]): c.Expr[T] =
|
||||||
|
{
|
||||||
|
import c.universe.{Apply=>ApplyTree,_}
|
||||||
|
val util = new ContextUtil[c.type](c)
|
||||||
|
val iw = util.singleton(InputWrapper)
|
||||||
|
val tpe = c.weakTypeOf[T]
|
||||||
|
val nme = newTermName(WrapName).encoded
|
||||||
|
val tree = ApplyTree(TypeApply(Select(Ident(iw), nme), TypeTree(tpe) :: Nil), ts.tree :: Nil)
|
||||||
|
tree.setPos(ts.tree.pos)
|
||||||
|
c.Expr[T](tree)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object Instance
|
object Instance
|
||||||
{
|
{
|
||||||
final val DynamicDependencyError = "Illegal dynamic dependency."
|
|
||||||
final val DynamicReferenceError = "Illegal dynamic reference."
|
|
||||||
final val ApplyName = "app"
|
final val ApplyName = "app"
|
||||||
final val FlattenName = "flatten"
|
final val FlattenName = "flatten"
|
||||||
final val PureName = "pure"
|
final val PureName = "pure"
|
||||||
final val MapName = "map"
|
final val MapName = "map"
|
||||||
final val InstanceTCName = "M"
|
final val InstanceTCName = "M"
|
||||||
final val WrapName = "wrap"
|
|
||||||
|
|
||||||
final class Input[U <: Universe with Singleton](val tpe: U#Type, val expr: U#Tree, val local: U#ValDef)
|
final class Input[U <: Universe with Singleton](val tpe: U#Type, val expr: U#Tree, val local: U#ValDef)
|
||||||
|
|
||||||
|
|
@ -99,41 +120,14 @@ object Instance
|
||||||
// A Tree that references the statically accessible Instance that provides the actual implementations of map, flatMap, ...
|
// A Tree that references the statically accessible Instance that provides the actual implementations of map, flatMap, ...
|
||||||
val instance = Ident(instanceSym)
|
val instance = Ident(instanceSym)
|
||||||
|
|
||||||
val parameterModifiers = Modifiers(Flag.PARAM)
|
val isWrapper: Tree => Boolean = util.isWrapper(InputWrapper.WrapName)
|
||||||
|
|
||||||
val wrapperSym = util.singleton(InputWrapper)
|
|
||||||
val wrapMethodSymbol = util.method(wrapperSym, WrapName)
|
|
||||||
def isWrapper(fun: Tree) = fun.symbol == wrapMethodSymbol
|
|
||||||
|
|
||||||
type In = Input[c.universe.type]
|
type In = Input[c.universe.type]
|
||||||
var inputs = List[In]()
|
var inputs = List[In]()
|
||||||
|
|
||||||
// constructs a ValDef with a parameter modifier, a unique name, with the provided Type and with an empty rhs
|
// Local definitions in the macro. This is used to ensure references are to M instances defined outside of the macro call.
|
||||||
def freshMethodParameter(tpe: Type): ValDef =
|
val defs = util.collectDefs(tree, isWrapper)
|
||||||
ValDef(parameterModifiers, freshTermName("p"), TypeTree(tpe), EmptyTree)
|
val checkQual: Tree => Unit = util.checkReferences(defs, isWrapper)
|
||||||
|
|
||||||
def freshTermName(prefix: String) = newTermName(c.fresh("$" + prefix))
|
|
||||||
|
|
||||||
/* Local definitions in the macro. This is used to ensure
|
|
||||||
* references are to M instances defined outside of the macro call.*/
|
|
||||||
val defs = new collection.mutable.HashSet[Symbol]
|
|
||||||
|
|
||||||
// a reference is illegal if it is to an M instance defined within the scope of the macro call
|
|
||||||
def illegalReference(sym: Symbol): Boolean =
|
|
||||||
sym != null && sym != NoSymbol && defs.contains(sym)
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
val checkQual: Tree => Unit = {
|
|
||||||
case s @ ApplyTree(fun, qual :: Nil) => if(isWrapper(fun)) c.error(s.pos, DynamicDependencyError)
|
|
||||||
case id @ Ident(name) if illegalReference(id.symbol) => c.error(id.pos, DynamicReferenceError)
|
|
||||||
case _ => ()
|
|
||||||
}
|
|
||||||
// adds the symbols for all non-Ident subtrees to `defs`.
|
|
||||||
val defSearch: Tree => Unit = {
|
|
||||||
case _: Ident => ()
|
|
||||||
case tree => if(tree.symbol ne null) defs += tree.symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
// transforms the original tree into calls to the Instance functions pure, map, ...,
|
// transforms the original tree into calls to the Instance functions pure, map, ...,
|
||||||
// resulting in a value of type M[T]
|
// resulting in a value of type M[T]
|
||||||
|
|
@ -163,7 +157,7 @@ object Instance
|
||||||
def single(body: Tree, input: In): Tree =
|
def single(body: Tree, input: In): Tree =
|
||||||
{
|
{
|
||||||
val variable = input.local
|
val variable = input.local
|
||||||
val param = ValDef(parameterModifiers, variable.name, variable.tpt, EmptyTree)
|
val param = ValDef(util.parameterModifiers, variable.name, variable.tpt, EmptyTree)
|
||||||
val typeApplied = TypeApply(Select(instance, MapName), variable.tpt :: TypeTree(treeType) :: Nil)
|
val typeApplied = TypeApply(Select(instance, MapName), variable.tpt :: TypeTree(treeType) :: Nil)
|
||||||
val mapped = ApplyTree(typeApplied, input.expr :: Function(param :: Nil, body) :: Nil)
|
val mapped = ApplyTree(typeApplied, input.expr :: Function(param :: Nil, body) :: Nil)
|
||||||
if(t.isLeft) mapped else flatten(mapped)
|
if(t.isLeft) mapped else flatten(mapped)
|
||||||
|
|
@ -173,7 +167,7 @@ object Instance
|
||||||
def arbArity(body: Tree, inputs: List[In]): Tree =
|
def arbArity(body: Tree, inputs: List[In]): Tree =
|
||||||
{
|
{
|
||||||
val result = builder.make(c)(mTC, inputs)
|
val result = builder.make(c)(mTC, inputs)
|
||||||
val param = freshMethodParameter( appliedType(result.representationC, util.idTC :: Nil) )
|
val param = util.freshMethodParameter( appliedType(result.representationC, util.idTC :: Nil) )
|
||||||
val bindings = result.extract(param)
|
val bindings = result.extract(param)
|
||||||
val f = Function(param :: Nil, Block(bindings, body))
|
val f = Function(param :: Nil, Block(bindings, body))
|
||||||
val ttt = TypeTree(treeType)
|
val ttt = TypeTree(treeType)
|
||||||
|
|
@ -192,30 +186,17 @@ object Instance
|
||||||
qual.foreach(checkQual)
|
qual.foreach(checkQual)
|
||||||
val vd = util.freshValDef(tpe, qual.symbol)
|
val vd = util.freshValDef(tpe, qual.symbol)
|
||||||
inputs ::= new Input(tpe, qual, vd)
|
inputs ::= new Input(tpe, qual, vd)
|
||||||
Ident(vd.name)
|
util.refVal(vd)
|
||||||
}
|
}
|
||||||
|
def sub(tpe: Type, qual: Tree): Tree =
|
||||||
// the main tree transformer that replaces calls to InputWrapper.wrap(x) with
|
|
||||||
// plain Idents that reference the actual input value
|
|
||||||
object appTransformer extends Transformer
|
|
||||||
{
|
{
|
||||||
override def transform(tree: Tree): Tree =
|
val tag = c.WeakTypeTag(tpe)
|
||||||
tree match
|
addType(tpe, convert(c)(qual)(tag) )
|
||||||
{
|
|
||||||
case ApplyTree(TypeApply(fun, t :: Nil), qual :: Nil) if isWrapper(fun) =>
|
|
||||||
val tag = c.WeakTypeTag(t.tpe)
|
|
||||||
addType(t.tpe, convert(c)(qual)(tag) )
|
|
||||||
case _ => super.transform(tree)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// collects all definitions in the tree. used for finding illegal references
|
|
||||||
tree.foreach(defSearch)
|
|
||||||
|
|
||||||
// applies the transformation
|
// applies the transformation
|
||||||
// resetting attributes: a) must be local b) must be done
|
// resetting attributes must be: a) local b) done here and not wider or else there are obscure errors
|
||||||
// on the transformed tree and not the wrapped tree or else there are obscure errors
|
val tr = makeApp( c.resetLocalAttrs( util.transformWrappers(tree, isWrapper, (tpe, tr) => sub(tpe, tr)) ) )
|
||||||
val tr = makeApp( c.resetLocalAttrs(appTransformer.transform(tree)) )
|
|
||||||
c.Expr[i.M[T]](tr)
|
c.Expr[i.M[T]](tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue