mirror of https://github.com/sbt/sbt.git
task setting macros for :=, +=, ++=
also, bump to 2.10.0-M6
This commit is contained in:
parent
15fec197c3
commit
c95df4681b
|
|
@ -0,0 +1,104 @@
|
||||||
|
package sbt
|
||||||
|
package appmacro
|
||||||
|
|
||||||
|
import scala.reflect._
|
||||||
|
import makro._
|
||||||
|
import scala.tools.nsc.Global
|
||||||
|
|
||||||
|
object ContextUtil {
|
||||||
|
/** 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Utility methods for macros. Several methods assume that the context's universe is a full compiler (`scala.tools.nsc.Global`).
|
||||||
|
* This is not thread safe due to the underlying Context and related data structures not being thread safe.
|
||||||
|
* Use `ContextUtil[c.type](c)` to construct. */
|
||||||
|
final class ContextUtil[C <: Context with Singleton](val ctx: C)
|
||||||
|
{
|
||||||
|
import ctx.universe.{Apply=>ApplyTree,_}
|
||||||
|
|
||||||
|
val alistType = ctx.typeOf[AList[KList]]
|
||||||
|
val alist: Symbol = alistType.typeSymbol.companionSymbol
|
||||||
|
val alistTC: Type = alistType.typeConstructor
|
||||||
|
|
||||||
|
/** Modifiers for a local val.*/
|
||||||
|
val localModifiers = Modifiers(NoFlags)
|
||||||
|
|
||||||
|
def getPos(sym: Symbol) = if(sym eq null) NoPosition else sym.pos
|
||||||
|
|
||||||
|
/** Constructs a unique term name with the given prefix within this Context.
|
||||||
|
* (The current implementation uses Context.fresh, which increments*/
|
||||||
|
def freshTermName(prefix: String) = newTermName(ctx.fresh("$" + prefix))
|
||||||
|
|
||||||
|
def typeTree(tpe: Type) = TypeTree().setType(tpe)
|
||||||
|
|
||||||
|
/** Constructs a new, local ValDef with the given Type, a unique name,
|
||||||
|
* the same position as `sym`, and an empty implementation (no rhs). */
|
||||||
|
def freshValDef(tpe: Type, sym: Symbol): ValDef =
|
||||||
|
{
|
||||||
|
val vd = localValDef(typeTree(tpe), EmptyTree)
|
||||||
|
vd setPos getPos(sym)
|
||||||
|
vd
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructs a ValDef with local modifiers and a unique name. */
|
||||||
|
def localValDef(tpt: Tree, rhs: Tree): ValDef =
|
||||||
|
ValDef(localModifiers, freshTermName("q"), tpt, rhs)
|
||||||
|
|
||||||
|
/** Constructs a tuple value of the right TupleN type from the provided inputs.*/
|
||||||
|
def mkTuple(args: List[Tree]): Tree =
|
||||||
|
{
|
||||||
|
val global: Global = ctx.universe.asInstanceOf[Global]
|
||||||
|
global.gen.mkTuple(args.asInstanceOf[List[global.Tree]]).asInstanceOf[ctx.universe.Tree]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a new, synthetic type variable with the specified `owner`. */
|
||||||
|
def newTypeVariable(owner: Symbol): Symbol =
|
||||||
|
{
|
||||||
|
val global: Global = ctx.universe.asInstanceOf[Global]
|
||||||
|
owner.asInstanceOf[global.Symbol].newSyntheticTypeParam().asInstanceOf[ctx.universe.Symbol]
|
||||||
|
}
|
||||||
|
/** The type representing the type constructor `[X] X` */
|
||||||
|
val idTC: Type =
|
||||||
|
{
|
||||||
|
val tvar = newTypeVariable(NoSymbol)
|
||||||
|
polyType(tvar :: Nil, refVar(tvar))
|
||||||
|
}
|
||||||
|
/** Constructs a new, synthetic type variable that is a type constructor. For example, in type Y[L[x]], L is such a type variable. */
|
||||||
|
def newTCVariable(owner: Symbol): Symbol =
|
||||||
|
{
|
||||||
|
val global: Global = ctx.universe.asInstanceOf[Global]
|
||||||
|
val tc = owner.asInstanceOf[global.Symbol].newSyntheticTypeParam()
|
||||||
|
val arg = tc.newSyntheticTypeParam("x", 0L)
|
||||||
|
tc.setInfo(global.PolyType(arg :: Nil, global.TypeBounds.empty)).asInstanceOf[ctx.universe.Symbol]
|
||||||
|
}
|
||||||
|
/** 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 {
|
||||||
|
case SingleType(_, sym) if !sym.isFreeTerm && sym.isStatic => sym
|
||||||
|
case x => error("Instance must be static (was " + x + ").")
|
||||||
|
}
|
||||||
|
/** Constructs a Type that references the given type variable. */
|
||||||
|
def refVar(variable: Symbol): Type = typeRef(NoPrefix, variable, Nil)
|
||||||
|
|
||||||
|
/** Returns the symbol for the non-private method named `name` for the class/module `obj`. */
|
||||||
|
def method(obj: Symbol, name: String): Symbol = {
|
||||||
|
val global: Global = ctx.universe.asInstanceOf[Global]
|
||||||
|
obj.asInstanceOf[global.Symbol].info.nonPrivateMember(global.newTermName(name)).asInstanceOf[ctx.universe.Symbol]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a Type representing the type constructor tcp.<name>. For example, given
|
||||||
|
* `object Demo { type M[x] = List[x] }`, the call `extractTC(Demo, "M")` will return a type representing
|
||||||
|
* the type constructor `[x] List[x]`.
|
||||||
|
**/
|
||||||
|
def extractTC(tcp: AnyRef with Singleton, name: String)(implicit it: ctx.TypeTag[tcp.type]): ctx.Type =
|
||||||
|
{
|
||||||
|
val global: Global = ctx.universe.asInstanceOf[Global]
|
||||||
|
val itTpe = it.tpe.asInstanceOf[global.Type]
|
||||||
|
val m = itTpe.nonPrivateMember(global.newTypeName(name))
|
||||||
|
val tc = itTpe.memberInfo(m).asInstanceOf[ctx.universe.Type]
|
||||||
|
assert(tc != NoType && tc.isHigherKinded, "Invalid type constructor: " + tc)
|
||||||
|
tc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,260 @@
|
||||||
|
package sbt
|
||||||
|
package appmacro
|
||||||
|
|
||||||
|
import Classes.Applicative
|
||||||
|
import Types.Id
|
||||||
|
|
||||||
|
/** The separate hierarchy from Applicative/Monad is for two reasons.
|
||||||
|
*
|
||||||
|
* 1. The type constructor is represented as an abstract type because a TypeTag cannot represent a type constructor directly.
|
||||||
|
* 2. The applicative interface is uncurried.
|
||||||
|
*/
|
||||||
|
trait Instance
|
||||||
|
{
|
||||||
|
type M[x]
|
||||||
|
def app[K[L[x]], Z](in: K[M], f: K[Id] => Z)(implicit a: AList[K]): M[Z]
|
||||||
|
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.TypeTag](c: scala.reflect.makro.Context)(in: c.Tree): c.Tree
|
||||||
|
}
|
||||||
|
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 makro._
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
/** 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 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.
|
||||||
|
* Second, it allows the input trees to be wrapped for later conversion into the appropriate `M[T]` type by `convert`.
|
||||||
|
* This wrapping is necessary because applying the first macro must preserve the original type,
|
||||||
|
* but it is useful to delay conversion until the outer, second macro is called. The `wrap` method accomplishes this by
|
||||||
|
* 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`.
|
||||||
|
* 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.
|
||||||
|
* The `TupleBuilder` instance constructs a tuple (Tree) from the inputs and defines the right hand side of the vals
|
||||||
|
* that unpacks the tuple containing the results of the inputs.
|
||||||
|
*
|
||||||
|
* The constructed tuple of inputs and the code that unpacks the results of the inputs are then passed to the `i`,
|
||||||
|
* which is an implementation of `Instance` that is statically accessible.
|
||||||
|
* An Instance defines a applicative functor associated with a specific type constructor and, if it implements MonadInstance as well, a monad.
|
||||||
|
* Typically, it will be either a top-level module or a stable member of a top-level module (such as a val or a nested module).
|
||||||
|
* The `with Singleton` part of the type verifies some cases at macro compilation time,
|
||||||
|
* while the full check for static accessibility is done at macro expansion time.
|
||||||
|
* Note: Ideally, the types would verify that `i: MonadInstance` when `t.isRight`.
|
||||||
|
* With the various dependent types involved, this is not worth it.
|
||||||
|
*
|
||||||
|
* The `t` argument is the argument of the macro that will be transformed as described above.
|
||||||
|
* If the macro that calls this method is for a multi-input map (app followed by map),
|
||||||
|
* `t` should be the argument wrapped in Left.
|
||||||
|
* If this is for multi-input flatMap (app followed by flatMap),
|
||||||
|
* this should be the argument wrapped in Right.
|
||||||
|
*/
|
||||||
|
def contImpl[T: c.TypeTag](c: Context, i: Instance with Singleton, convert: Convert, builder: TupleBuilder)(t: Either[c.Expr[T], c.Expr[i.M[T]]])(
|
||||||
|
implicit tt: c.TypeTag[T], mt: c.TypeTag[i.M[T]], it: c.TypeTag[i.type]): c.Expr[i.M[T]] =
|
||||||
|
{
|
||||||
|
import c.universe.{Apply=>ApplyTree,_}
|
||||||
|
|
||||||
|
import scala.tools.nsc.Global
|
||||||
|
// Used to access compiler methods not yet exposed via the reflection/macro APIs
|
||||||
|
val global: Global = c.universe.asInstanceOf[Global]
|
||||||
|
|
||||||
|
val util = ContextUtil[c.type](c)
|
||||||
|
val mTC: Type = util.extractTC(i, InstanceTCName)
|
||||||
|
|
||||||
|
// the tree for the macro argument
|
||||||
|
val (tree, treeType) = t match {
|
||||||
|
case Left(l) => (l.tree, tt.tpe.normalize)
|
||||||
|
case Right(r) => (r.tree, mt.tpe.normalize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val instanceSym = util.singleton(i)
|
||||||
|
// 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
|
||||||
|
|
||||||
|
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))
|
||||||
|
def typeTree(tpe: Type) = TypeTree().setType(tpe)
|
||||||
|
|
||||||
|
// constructs a function that applies f to each subtree of the input tree
|
||||||
|
def visitor(f: Tree => Unit): Tree => Unit =
|
||||||
|
{
|
||||||
|
val v: Transformer = new Transformer {
|
||||||
|
override def transform(tree: Tree): Tree = { f(tree); super.transform(tree) }
|
||||||
|
}
|
||||||
|
(tree: Tree) => v.transform(tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 = visitor {
|
||||||
|
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 = visitor {
|
||||||
|
case _: Ident => ()
|
||||||
|
case tree => if(tree.symbol ne null) defs += tree.symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
// transforms the original tree into calls to the Instance functions pure, map, ...,
|
||||||
|
// resulting in a value of type M[T]
|
||||||
|
def makeApp(body: Tree): Tree =
|
||||||
|
inputs match {
|
||||||
|
case Nil => pure(body)
|
||||||
|
case x :: Nil => single(body, x)
|
||||||
|
case xs => arbArity(body, xs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// no inputs, so construct M[T] via Instance.pure or pure+flatten
|
||||||
|
def pure(body: Tree): Tree =
|
||||||
|
{
|
||||||
|
val typeApplied = TypeApply(Select(instance, PureName), typeTree(treeType) :: Nil)
|
||||||
|
val p = ApplyTree(typeApplied, Function(Nil, body) :: Nil)
|
||||||
|
if(t.isLeft) p else flatten(p)
|
||||||
|
}
|
||||||
|
// m should have type M[M[T]]
|
||||||
|
// the returned Tree will have type M[T]
|
||||||
|
def flatten(m: Tree): Tree =
|
||||||
|
{
|
||||||
|
val typedFlatten = TypeApply(Select(instance, FlattenName), typeTree(tt.tpe) :: Nil)
|
||||||
|
ApplyTree(typedFlatten, m :: Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// calls Instance.map or flatmap directly, skipping the intermediate Instance.app that is unnecessary for a single input
|
||||||
|
def single(body: Tree, input: In): Tree =
|
||||||
|
{
|
||||||
|
val variable = input.local
|
||||||
|
val param = ValDef(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// calls Instance.app to get the values for all inputs and then calls Instance.map or flatMap to evaluate the body
|
||||||
|
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 bindings = result.extract(param)
|
||||||
|
val f = Function(param :: Nil, Block(bindings, body))
|
||||||
|
val ttt = typeTree(treeType)
|
||||||
|
val typedApp = TypeApply(Select(instance, ApplyName), typeTree(result.representationC) :: ttt :: Nil)
|
||||||
|
val app = ApplyTree(ApplyTree(typedApp, result.input :: f :: Nil), result.alistInstance :: Nil)
|
||||||
|
if(t.isLeft) app else flatten(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
// called when transforming the tree to add an input
|
||||||
|
// for `qual` of type M[A], and a selection qual.value,
|
||||||
|
// the call is addType(Type A, Tree qual)
|
||||||
|
// the result is a Tree representing a reference to
|
||||||
|
// the bound value of the input
|
||||||
|
def addType(tpe: Type, qual: Tree): Tree =
|
||||||
|
{
|
||||||
|
checkQual(qual)
|
||||||
|
val vd = util.freshValDef(tpe, qual.symbol)
|
||||||
|
inputs ::= new Input(tpe, qual, vd)
|
||||||
|
Ident(vd.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, t :: Nil), qual :: Nil) if isWrapper(fun) =>
|
||||||
|
val tag = c.TypeTag(t.tpe)
|
||||||
|
addType(t.tpe, convert(c)(qual)(tag) )
|
||||||
|
case _ => super.transform(tree)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collects all definitions in the tree. used for finding illegal references
|
||||||
|
defSearch(tree)
|
||||||
|
|
||||||
|
// 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)) )
|
||||||
|
c.Expr[i.M[T]](tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
import Types._
|
||||||
|
|
||||||
|
implicit def applicativeInstance[A[_]](implicit ap: Applicative[A]): Instance { type M[x] = A[x] } = new Instance
|
||||||
|
{
|
||||||
|
type M[x] = A[x]
|
||||||
|
def app[ K[L[x]], Z ](in: K[A], f: K[Id] => Z)(implicit a: AList[K]) = a.apply[A,Z](in, f)
|
||||||
|
def map[S,T](in: A[S], f: S => T) = ap.map(f, in)
|
||||||
|
def pure[S](s: () => S): M[S] = ap.pure(s())
|
||||||
|
}
|
||||||
|
|
||||||
|
type AI[A[_]] = Instance { type M[x] = A[x] }
|
||||||
|
def compose[A[_], B[_]](implicit a: AI[A], b: AI[B]): Instance { type M[x] = A[B[x]] } = new Composed[A,B](a,b)
|
||||||
|
// made a public, named, unsealed class because of trouble with macros and inference when the Instance is not an object
|
||||||
|
class Composed[A[_], B[_]](a: AI[A], b: AI[B]) extends Instance
|
||||||
|
{
|
||||||
|
type M[x] = A[B[x]]
|
||||||
|
def pure[S](s: () => S): A[B[S]] = a.pure(() => b.pure(s))
|
||||||
|
def map[S,T](in: M[S], f: S => T): M[T] = a.map(in, (bv: B[S]) => b.map(bv, f))
|
||||||
|
def app[ K[L[x]], Z ](in: K[M], f: K[Id] => Z)(implicit alist: AList[K]): A[B[Z]] =
|
||||||
|
{
|
||||||
|
val g: K[B] => B[Z] = in => b.app[K, Z](in, f)
|
||||||
|
type Split[ L[x] ] = K[ (L ∙ B)#l ]
|
||||||
|
a.app[Split, B[Z]](in, g)(AList.asplit(alist))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package sbt
|
||||||
|
package appmacro
|
||||||
|
|
||||||
|
import Types.Id
|
||||||
|
import scala.tools.nsc.Global
|
||||||
|
import scala.reflect._
|
||||||
|
import makro._
|
||||||
|
|
||||||
|
/** A `TupleBuilder` that uses a KList as the tuple representation.*/
|
||||||
|
object KListBuilder extends TupleBuilder
|
||||||
|
{
|
||||||
|
def make(c: Context)(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] = new BuilderResult[c.type]
|
||||||
|
{
|
||||||
|
val ctx: c.type = c
|
||||||
|
val util = ContextUtil[c.type](c)
|
||||||
|
import c.universe.{Apply=>ApplyTree,_}
|
||||||
|
import util._
|
||||||
|
|
||||||
|
val knilType = c.typeOf[KNil]
|
||||||
|
val knil = Ident(knilType.typeSymbol.companionSymbol)
|
||||||
|
val kconsTpe = c.typeOf[KCons[Int,KNil,List]]
|
||||||
|
val kcons = kconsTpe.typeSymbol.companionSymbol
|
||||||
|
val mTC: Type = mt.asInstanceOf[c.universe.Type]
|
||||||
|
val kconsTC: Type = kconsTpe.typeConstructor
|
||||||
|
|
||||||
|
/** This is the L in the type function [L[x]] ... */
|
||||||
|
val tcVariable: Symbol = newTCVariable(NoSymbol)
|
||||||
|
|
||||||
|
/** Instantiates KCons[h, t <: KList[L], L], where L is the type constructor variable */
|
||||||
|
def kconsType(h: Type, t: Type): Type =
|
||||||
|
appliedType(kconsTC, h :: t :: refVar(tcVariable) :: Nil)
|
||||||
|
|
||||||
|
def bindKList(prev: ValDef, revBindings: List[ValDef], params: List[ValDef]): List[ValDef] =
|
||||||
|
params match
|
||||||
|
{
|
||||||
|
case ValDef(mods, name, tpt, _) :: xs =>
|
||||||
|
val head = ValDef(mods, name, tpt, Select(Ident(prev.name), "head"))
|
||||||
|
val tail = localValDef(TypeTree(), Select(Ident(prev.name), "tail"))
|
||||||
|
val base = head :: revBindings
|
||||||
|
bindKList(tail, if(xs.isEmpty) base else tail :: base, xs)
|
||||||
|
case Nil => revBindings.reverse
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The input trees combined in a KList */
|
||||||
|
val klist = (inputs :\ (knil: Tree))( (in, klist) => ApplyTree(kcons, in.expr, klist) )
|
||||||
|
|
||||||
|
/** The input types combined in a KList type. The main concern is tracking the heterogeneous types.
|
||||||
|
* The type constructor is tcVariable, so that it can be applied to [X] X or M later.
|
||||||
|
* When applied to `M`, this type gives the type of the `input` KList. */
|
||||||
|
val klistType: Type = (inputs :\ knilType)( (in, klist) => kconsType(in.tpe, klist) )
|
||||||
|
|
||||||
|
val representationC = PolyType(tcVariable :: Nil, klistType)
|
||||||
|
val resultType = appliedType(representationC, idTC :: Nil)
|
||||||
|
val input = klist
|
||||||
|
val alistInstance = TypeApply(Select(Ident(alist), "klist"), typeTree(representationC) :: Nil)
|
||||||
|
def extract(param: ValDef) = bindKList(param, Nil, inputs.map(_.local))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package sbt
|
||||||
|
package appmacro
|
||||||
|
|
||||||
|
import scala.reflect._
|
||||||
|
import makro._
|
||||||
|
|
||||||
|
/** A builder that uses `TupleN` as the representation for small numbers of inputs (up to `TupleNBuilder.MaxInputs`)
|
||||||
|
* and `KList` for larger numbers of inputs. This builder cannot handle fewer than 2 inputs.*/
|
||||||
|
object MixedBuilder extends TupleBuilder
|
||||||
|
{
|
||||||
|
def make(c: Context)(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] =
|
||||||
|
{
|
||||||
|
val delegate = if(inputs.size > TupleNBuilder.MaxInputs) KListBuilder else TupleNBuilder
|
||||||
|
delegate.make(c)(mt, inputs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package sbt
|
||||||
|
package appmacro
|
||||||
|
|
||||||
|
import Types.Id
|
||||||
|
import scala.tools.nsc.Global
|
||||||
|
import scala.reflect._
|
||||||
|
import makro._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `TupleBuilder` abstracts the work of constructing a tuple data structure such as a `TupleN` or `KList`
|
||||||
|
* and extracting values from it. The `Instance` macro implementation will (roughly) traverse the tree of its argument
|
||||||
|
* and ultimately obtain a list of expressions with type `M[T]` for different types `T`.
|
||||||
|
* The macro constructs an `Input` value for each of these expressions that contains the `Type` for `T`,
|
||||||
|
* the `Tree` for the expression, and a `ValDef` that will hold the value for the input.
|
||||||
|
*
|
||||||
|
* `TupleBuilder.apply` is provided with the list of `Input`s and is expected to provide three values in the returned BuilderResult.
|
||||||
|
* First, it returns the constructed tuple data structure Tree in `input`.
|
||||||
|
* Next, it provides the type constructor `representationC` that, when applied to M, gives the type of tuple data structure.
|
||||||
|
* For example, a builder that constructs a `Tuple3` for inputs `M[Int]`, `M[Boolean]`, and `M[String]`
|
||||||
|
* would provide a Type representing `[L[x]] (L[Int], L[Boolean], L[String])`. The `input` method
|
||||||
|
* would return a value whose type is that type constructor applied to M, or `(M[Int], M[Boolean], M[String])`.
|
||||||
|
*
|
||||||
|
* Finally, the `extract` method provides a list of vals that extract information from the applied input.
|
||||||
|
* The type of the applied input is the type constructor applied to `Id` (`[X] X`).
|
||||||
|
* The returned list of ValDefs should be the ValDefs from `inputs`, but with non-empty right-hand sides.
|
||||||
|
*/
|
||||||
|
trait TupleBuilder {
|
||||||
|
/** A convenience alias for a list of inputs (associated with a Universe of type U). */
|
||||||
|
type Inputs[U <: Universe with Singleton] = List[Instance.Input[U]]
|
||||||
|
|
||||||
|
/** Constructs a one-time use Builder for Context `c` and type constructor `tcType`. */
|
||||||
|
def make(c: Context)(tcType: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type]
|
||||||
|
}
|
||||||
|
|
||||||
|
trait BuilderResult[C <: Context with Singleton]
|
||||||
|
{
|
||||||
|
val ctx: C
|
||||||
|
import ctx.universe._
|
||||||
|
|
||||||
|
/** Represents the higher-order type constructor `[L[x]] ...` where `...` is the
|
||||||
|
* type of the data structure containing the added expressions,
|
||||||
|
* except that it is abstracted over the type constructor applied to each heterogeneous part of the type . */
|
||||||
|
def representationC: PolyType
|
||||||
|
|
||||||
|
/** The instance of AList for the input. For a `representationC` of `[L[x]]`, this `Tree` should have a `Type` of `AList[L]`*/
|
||||||
|
def alistInstance: Tree
|
||||||
|
|
||||||
|
/** Returns the completed value containing all expressions added to the builder. */
|
||||||
|
def input: Tree
|
||||||
|
|
||||||
|
/* The list of definitions that extract values from a value of type `$representationC[Id]`.
|
||||||
|
* The returned value should be identical to the `ValDef`s provided to the `TupleBuilder.make` method but with
|
||||||
|
* non-empty right hand sides. Each `ValDef` may refer to `param` and previous `ValDef`s in the list.*/
|
||||||
|
def extract(param: ValDef): List[ValDef]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package sbt
|
||||||
|
package appmacro
|
||||||
|
|
||||||
|
import Types.Id
|
||||||
|
import scala.tools.nsc.Global
|
||||||
|
import scala.reflect._
|
||||||
|
import makro._
|
||||||
|
|
||||||
|
/** A builder that uses a TupleN as the tuple representation.
|
||||||
|
* It is limited to tuples of size 2 to `MaxInputs`. */
|
||||||
|
object TupleNBuilder extends TupleBuilder
|
||||||
|
{
|
||||||
|
/** The largest number of inputs that this builder can handle. */
|
||||||
|
final val MaxInputs = 11
|
||||||
|
final val TupleMethodName = "tuple"
|
||||||
|
|
||||||
|
def make(c: Context)(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] = new BuilderResult[c.type]
|
||||||
|
{
|
||||||
|
val util = ContextUtil[c.type](c)
|
||||||
|
import c.universe.{Apply=>ApplyTree,_}
|
||||||
|
import util._
|
||||||
|
|
||||||
|
val global: Global = c.universe.asInstanceOf[Global]
|
||||||
|
val mTC: Type = mt.asInstanceOf[c.universe.Type]
|
||||||
|
|
||||||
|
val ctx: c.type = c
|
||||||
|
val representationC: PolyType = {
|
||||||
|
val tcVariable: Symbol = newTCVariable(NoSymbol)
|
||||||
|
val tupleTypeArgs = inputs.map(in => typeRef(NoPrefix, tcVariable, in.tpe :: Nil).asInstanceOf[global.Type])
|
||||||
|
val tuple = global.definitions.tupleType(tupleTypeArgs)
|
||||||
|
PolyType(tcVariable :: Nil, tuple.asInstanceOf[Type] )
|
||||||
|
}
|
||||||
|
val resultType = appliedType(representationC, idTC :: Nil)
|
||||||
|
|
||||||
|
val input: Tree = mkTuple(inputs.map(_.expr))
|
||||||
|
val alistInstance: Tree = {
|
||||||
|
val select = Select(Ident(alist), TupleMethodName + inputs.size.toString)
|
||||||
|
TypeApply(select, inputs.map(in => typeTree(in.tpe)))
|
||||||
|
}
|
||||||
|
def extract(param: ValDef): List[ValDef] = bindTuple(param, Nil, inputs.map(_.local), 1)
|
||||||
|
|
||||||
|
def bindTuple(param: ValDef, revBindings: List[ValDef], params: List[ValDef], i: Int): List[ValDef] =
|
||||||
|
params match
|
||||||
|
{
|
||||||
|
case ValDef(mods, name, tpt, _) :: xs =>
|
||||||
|
val x = ValDef(mods, name, tpt, Select(Ident(param.name), "_" + i.toString))
|
||||||
|
bindTuple(param, x :: revBindings, xs, i+1)
|
||||||
|
case Nil => revBindings.reverse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,9 @@ package sbt
|
||||||
import Classes.Applicative
|
import Classes.Applicative
|
||||||
import Types._
|
import Types._
|
||||||
|
|
||||||
/** An abstraction over (* -> *) -> * with the purpose of abstracting over arity abstractions like KList and TupleN
|
/** An abstraction over a higher-order type constructor `K[x[y]]` with the purpose of abstracting
|
||||||
* as well as homogeneous sequences Seq[M[T]]. */
|
* over heterogeneous sequences like `KList` and `TupleN` with elements with a common type
|
||||||
|
* constructor as well as homogeneous sequences `Seq[M[T]]`. */
|
||||||
trait AList[K[L[x]] ]
|
trait AList[K[L[x]] ]
|
||||||
{
|
{
|
||||||
def transform[M[_], N[_]](value: K[M], f: M ~> N): K[N]
|
def transform[M[_], N[_]](value: K[M], f: M ~> N): K[N]
|
||||||
|
|
@ -18,6 +19,7 @@ trait AList[K[L[x]] ]
|
||||||
object AList
|
object AList
|
||||||
{
|
{
|
||||||
type Empty = AList[({ type l[L[x]] = Unit})#l]
|
type Empty = AList[({ type l[L[x]] = Unit})#l]
|
||||||
|
/** AList for Unit, which represents a sequence that is always empty.*/
|
||||||
val empty: Empty = new Empty {
|
val empty: Empty = new Empty {
|
||||||
def transform[M[_], N[_]](in: Unit, f: M ~> N) = ()
|
def transform[M[_], N[_]](in: Unit, f: M ~> N) = ()
|
||||||
def foldr[M[_], T](in: Unit, f: (M[_], T) => T, init: T) = init
|
def foldr[M[_], T](in: Unit, f: (M[_], T) => T, init: T) = init
|
||||||
|
|
@ -26,6 +28,7 @@ object AList
|
||||||
}
|
}
|
||||||
|
|
||||||
type SeqList[T] = AList[({ type l[L[x]] = List[L[T]] })#l]
|
type SeqList[T] = AList[({ type l[L[x]] = List[L[T]] })#l]
|
||||||
|
/** AList for a homogeneous sequence. */
|
||||||
def seq[T]: SeqList[T] = new SeqList[T]
|
def seq[T]: SeqList[T] = new SeqList[T]
|
||||||
{
|
{
|
||||||
def transform[M[_], N[_]](s: List[M[T]], f: M ~> N) = s.map(f.fn[T])
|
def transform[M[_], N[_]](s: List[M[T]], f: M ~> N) = s.map(f.fn[T])
|
||||||
|
|
@ -44,6 +47,7 @@ object AList
|
||||||
def traverse[M[_], N[_], P[_]](s: List[M[T]], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[List[P[T]]] = ???
|
def traverse[M[_], N[_], P[_]](s: List[M[T]], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[List[P[T]]] = ???
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** AList for the abitrary arity data structure KList. */
|
||||||
def klist[KL[M[_]] <: KList[M] { type Transform[N[_]] = KL[N] }]: AList[KL] = new AList[KL] {
|
def klist[KL[M[_]] <: KList[M] { type Transform[N[_]] = KL[N] }]: AList[KL] = new AList[KL] {
|
||||||
def transform[M[_], N[_]](k: KL[M], f: M ~> N) = k.transform(f)
|
def transform[M[_], N[_]](k: KL[M], f: M ~> N) = k.transform(f)
|
||||||
def foldr[M[_], T](k: KL[M], f: (M[_], T) => T, init: T): T = k.foldr(f, init)
|
def foldr[M[_], T](k: KL[M], f: (M[_], T) => T, init: T): T = k.foldr(f, init)
|
||||||
|
|
@ -51,6 +55,7 @@ object AList
|
||||||
def traverse[M[_], N[_], P[_]](k: KL[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[KL[P]] = k.traverse[N,P](f)(np)
|
def traverse[M[_], N[_], P[_]](k: KL[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[KL[P]] = k.traverse[N,P](f)(np)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** AList for a single value. */
|
||||||
type Single[A] = AList[({ type l[L[x]] = L[A]})#l]
|
type Single[A] = AList[({ type l[L[x]] = L[A]})#l]
|
||||||
def single[A]: Single[A] = new Single[A] {
|
def single[A]: Single[A] = new Single[A] {
|
||||||
def transform[M[_], N[_]](a: M[A], f: M ~> N) = f(a)
|
def transform[M[_], N[_]](a: M[A], f: M ~> N) = f(a)
|
||||||
|
|
@ -58,8 +63,8 @@ object AList
|
||||||
def traverse[M[_], N[_], P[_]](a: M[A], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[P[A]] = f(a)
|
def traverse[M[_], N[_], P[_]](a: M[A], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[P[A]] = f(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type ASplit[K[L[x]], B[x]] = AList[ ({ type l[L[x]] = K[ (L ∙ B)#l] })#l ]
|
type ASplit[K[L[x]], B[x]] = AList[ ({ type l[L[x]] = K[ (L ∙ B)#l] })#l ]
|
||||||
|
/** AList that operates on the outer type constructor `A` of a composition `[x] A[B[x]]` for type constructors `A` and `B`*/
|
||||||
def asplit[ K[L[x]], B[x] ](base: AList[K]): ASplit[K,B] = new ASplit[K, B]
|
def asplit[ K[L[x]], B[x] ](base: AList[K]): ASplit[K,B] = new ASplit[K, B]
|
||||||
{
|
{
|
||||||
type Split[ L[x] ] = K[ (L ∙ B)#l ]
|
type Split[ L[x] ] = K[ (L ∙ B)#l ]
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,16 @@ sealed trait KList[+M[_]]
|
||||||
/** Apply the natural transformation `f` to each element. */
|
/** Apply the natural transformation `f` to each element. */
|
||||||
def transform[N[_]](f: M ~> N): Transform[N]
|
def transform[N[_]](f: M ~> N): Transform[N]
|
||||||
|
|
||||||
|
/** Folds this list using a function that operates on the homogeneous type of the elements of this list. */
|
||||||
def foldr[T](f: (M[_], T) => T, init: T): T = init // had trouble defining it in KNil
|
def foldr[T](f: (M[_], T) => T, init: T): T = init // had trouble defining it in KNil
|
||||||
|
|
||||||
|
/** Applies `f` to the elements of this list in the applicative functor defined by `ap`. */
|
||||||
def apply[N[x] >: M[x], Z](f: Transform[Id] => Z)(implicit ap: Applicative[N]): N[Z]
|
def apply[N[x] >: M[x], Z](f: Transform[Id] => Z)(implicit ap: Applicative[N]): N[Z]
|
||||||
|
|
||||||
|
/** Equivalent to `transform(f) . apply(x => x)`, this is the essence of the iterator at the level of natural transformations.*/
|
||||||
def traverse[N[_], P[_]](f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[Transform[P]]
|
def traverse[N[_], P[_]](f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[Transform[P]]
|
||||||
|
|
||||||
|
/** Discards the heterogeneous type information and constructs a plain List from this KList's elements. */
|
||||||
def toList: List[M[_]]
|
def toList: List[M[_]]
|
||||||
}
|
}
|
||||||
final case class KCons[H, +T <: KList[M], +M[_]](head: M[H], tail: T) extends KList[M]
|
final case class KCons[H, +T <: KList[M], +M[_]](head: M[H], tail: T) extends KList[M]
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,9 @@ trait Init[Scope]
|
||||||
type MapConstant = ScopedKey ~> Option
|
type MapConstant = ScopedKey ~> Option
|
||||||
|
|
||||||
def setting[T](key: ScopedKey[T], init: Initialize[T], pos: SourcePosition = NoPosition): Setting[T] = new Setting[T](key, init, pos)
|
def setting[T](key: ScopedKey[T], init: Initialize[T], pos: SourcePosition = NoPosition): Setting[T] = new Setting[T](key, init, pos)
|
||||||
def value[T](value: => T): Initialize[T] = new Value(value _)
|
def valueStrict[T](value: T): Initialize[T] = pure(() => value)
|
||||||
|
def value[T](value: => T): Initialize[T] = pure(value _)
|
||||||
|
def pure[T](value: () => T): Initialize[T] = new Value(value)
|
||||||
def optional[T,U](i: Initialize[T])(f: Option[T] => U): Initialize[U] = new Optional(Some(i), f)
|
def optional[T,U](i: Initialize[T])(f: Option[T] => U): Initialize[U] = new Optional(Some(i), f)
|
||||||
def update[T](key: ScopedKey[T])(f: T => T): Setting[T] = new Setting[T](key, map(key)(f), NoPosition)
|
def update[T](key: ScopedKey[T])(f: T => T): Setting[T] = new Setting[T](key, map(key)(f), NoPosition)
|
||||||
def bind[S,T](in: Initialize[S])(f: S => Initialize[T]): Initialize[T] = new Bind(f, in)
|
def bind[S,T](in: Initialize[S])(f: S => Initialize[T]): Initialize[T] = new Bind(f, in)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue