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:
Mark Harrah 2012-11-02 14:20:17 -04:00
parent c2760ecbdd
commit 40a034c3a7
2 changed files with 138 additions and 59 deletions

View File

@ -4,11 +4,32 @@ package appmacro
import scala.reflect._
import macros._
import scala.tools.nsc.Global
import ContextUtil.{DynamicDependencyError, DynamicReferenceError}
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`.
* 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)
/** 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`).
@ -42,6 +63,50 @@ 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] =
{
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. */
def localValDef(tpt: Tree, rhs: Tree): ValDef =
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
}
/** >: Nothing <: Any */
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`. */
def singleton[T <: AnyRef with Singleton](i: T)(implicit it: ctx.TypeTag[i.type]): Symbol =
it.tpe match {
@ -105,4 +180,27 @@ final class ContextUtil[C <: Context](val ctx: C)
assert(tc != NoType && tc.takesTypeArgs, "Invalid type constructor: " + 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)
}
}

View File

@ -24,24 +24,45 @@ trait MonadInstance extends Instance
{
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 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
{
final val DynamicDependencyError = "Illegal dynamic dependency."
final val DynamicReferenceError = "Illegal dynamic reference."
final val ApplyName = "app"
final val FlattenName = "flatten"
final val PureName = "pure"
final val MapName = "map"
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)
@ -99,41 +120,14 @@ object Instance
// A Tree that references the statically accessible Instance that provides the actual implementations of map, flatMap, ...
val instance = Ident(instanceSym)
val parameterModifiers = Modifiers(Flag.PARAM)
val wrapperSym = util.singleton(InputWrapper)
val wrapMethodSymbol = util.method(wrapperSym, WrapName)
def isWrapper(fun: Tree) = fun.symbol == wrapMethodSymbol
val isWrapper: Tree => Boolean = util.isWrapper(InputWrapper.WrapName)
type In = Input[c.universe.type]
var inputs = List[In]()
// 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)
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;
}
// Local definitions in the macro. This is used to ensure references are to M instances defined outside of the macro call.
val defs = util.collectDefs(tree, isWrapper)
val checkQual: Tree => Unit = util.checkReferences(defs, isWrapper)
// transforms the original tree into calls to the Instance functions pure, map, ...,
// resulting in a value of type M[T]
@ -163,7 +157,7 @@ object Instance
def single(body: Tree, input: In): Tree =
{
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 mapped = ApplyTree(typeApplied, input.expr :: Function(param :: Nil, body) :: Nil)
if(t.isLeft) mapped else flatten(mapped)
@ -173,7 +167,7 @@ object Instance
def arbArity(body: Tree, inputs: List[In]): Tree =
{
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 f = Function(param :: Nil, Block(bindings, body))
val ttt = TypeTree(treeType)
@ -192,30 +186,17 @@ object Instance
qual.foreach(checkQual)
val vd = util.freshValDef(tpe, qual.symbol)
inputs ::= new Input(tpe, qual, vd)
Ident(vd.name)
util.refVal(vd)
}
// the main tree transformer that replaces calls to InputWrapper.wrap(x) with
// plain Idents that reference the actual input value
object appTransformer extends Transformer
def sub(tpe: Type, qual: Tree): Tree =
{
override def transform(tree: Tree): Tree =
tree match
{
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)
}
val tag = c.WeakTypeTag(tpe)
addType(tpe, convert(c)(qual)(tag) )
}
// collects all definitions in the tree. used for finding illegal references
tree.foreach(defSearch)
// applies the transformation
// resetting attributes: a) must be local b) must be done
// on the transformed tree and not the wrapped tree or else there are obscure errors
val tr = makeApp( c.resetLocalAttrs(appTransformer.transform(tree)) )
// 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)
}