diff --git a/util/appmacro/ContextUtil.scala b/util/appmacro/ContextUtil.scala index 3fd45207a..a62846e78 100644 --- a/util/appmacro/ContextUtil.scala +++ b/util/appmacro/ContextUtil.scala @@ -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()`. */ + 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() returns true. + * Typically, `f` is a `Select` or `Ident`. + * The wrapper is replaced with the result of `subWrapper(, )` */ + 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) + } } \ No newline at end of file diff --git a/util/appmacro/Instance.scala b/util/appmacro/Instance.scala index 1dd51e26b..f70941dd0 100644 --- a/util/appmacro/Instance.scala +++ b/util/appmacro/Instance.scala @@ -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.[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) }