From 087e386c9955b16fba3d32ec0f94d7a19ef97aea Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Tue, 31 Jul 2012 11:52:10 -0400 Subject: [PATCH] task setting macros for :=, +=, ++= also, bump to 2.10.0-M6 --- util/appmacro/ContextUtil.scala | 104 ++++++++++++ util/appmacro/Instance.scala | 260 ++++++++++++++++++++++++++++++ util/appmacro/KListBuilder.scala | 58 +++++++ util/appmacro/MixedBuilder.scala | 16 ++ util/appmacro/TupleBuilder.scala | 56 +++++++ util/appmacro/TupleNBuilder.scala | 51 ++++++ 6 files changed, 545 insertions(+) create mode 100644 util/appmacro/ContextUtil.scala create mode 100644 util/appmacro/Instance.scala create mode 100644 util/appmacro/KListBuilder.scala create mode 100644 util/appmacro/MixedBuilder.scala create mode 100644 util/appmacro/TupleBuilder.scala create mode 100644 util/appmacro/TupleNBuilder.scala diff --git a/util/appmacro/ContextUtil.scala b/util/appmacro/ContextUtil.scala new file mode 100644 index 000000000..31f61c356 --- /dev/null +++ b/util/appmacro/ContextUtil.scala @@ -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.. 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 + } +} \ No newline at end of file diff --git a/util/appmacro/Instance.scala b/util/appmacro/Instance.scala new file mode 100644 index 000000000..05a80b4e8 --- /dev/null +++ b/util/appmacro/Instance.scala @@ -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)) + } + } +} diff --git a/util/appmacro/KListBuilder.scala b/util/appmacro/KListBuilder.scala new file mode 100644 index 000000000..5b658ea69 --- /dev/null +++ b/util/appmacro/KListBuilder.scala @@ -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)) + } +} \ No newline at end of file diff --git a/util/appmacro/MixedBuilder.scala b/util/appmacro/MixedBuilder.scala new file mode 100644 index 000000000..593f60382 --- /dev/null +++ b/util/appmacro/MixedBuilder.scala @@ -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) + } +} \ No newline at end of file diff --git a/util/appmacro/TupleBuilder.scala b/util/appmacro/TupleBuilder.scala new file mode 100644 index 000000000..f91d3c91c --- /dev/null +++ b/util/appmacro/TupleBuilder.scala @@ -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] +} + diff --git a/util/appmacro/TupleNBuilder.scala b/util/appmacro/TupleNBuilder.scala new file mode 100644 index 000000000..ddf312f1b --- /dev/null +++ b/util/appmacro/TupleNBuilder.scala @@ -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 + } + } +}