diff --git a/build.sbt b/build.sbt index a884e5ef7..33a4cde61 100644 --- a/build.sbt +++ b/build.sbt @@ -249,16 +249,25 @@ lazy val commandProj = (project in file("main-command")) addSbtCompilerClasspath, addSbtLm) +// The core macro project defines the main logic of the DSL, abstracted +// away from several sbt implementators (tasks, settings, et cetera). +lazy val coreMacrosProj = (project in file("core-macros")) + .settings( + commonSettings, + name := "Core Macros", + libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value + ) + .configure(addSbtUtilCollection) + // Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions lazy val mainSettingsProj = (project in file("main-settings")) - .dependsOn(commandProj, stdTaskProj) + .dependsOn(commandProj, stdTaskProj, coreMacrosProj) .settings( testedBaseSettings, name := "Main Settings" ) .configure( addSbtUtilCache, - addSbtUtilApplyMacro, addSbtCompilerInterface, addSbtUtilRelation, addSbtUtilLogging, diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala new file mode 100644 index 000000000..f6dbf5894 --- /dev/null +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala @@ -0,0 +1,275 @@ +package sbt.internal.util +package appmacro + +import scala.reflect._ +import macros._ +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 <: blackbox.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](c: blackbox.Context)( + f: (c.Expr[Any], c.Position) => c.Expr[T]): c.Expr[T] = { + import c.universe._ + c.macroApplication match { + case s @ Select(Apply(_, t :: Nil), tp) => f(c.Expr[Any](t), s.pos) + case x => unexpectedTree(x) + } + } + + def unexpectedTree[C <: blackbox.Context](tree: C#Tree): Nothing = + sys.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`). + * 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 <: blackbox.Context](val ctx: C) { + import ctx.universe.{ Apply => ApplyTree, _ } + import internal.decorators._ + + val powerContext = ctx.asInstanceOf[reflect.macros.runtime.Context] + val global: powerContext.universe.type = powerContext.universe + def callsiteTyper: global.analyzer.Typer = powerContext.callsiteTyper + val initialOwner: Symbol = callsiteTyper.context.owner.asInstanceOf[ctx.universe.Symbol] + + lazy val alistType = ctx.typeOf[AList[KList]] + lazy val alist: Symbol = alistType.typeSymbol.companion + lazy val alistTC: Type = alistType.typeConstructor + + /** Modifiers for a local val.*/ + lazy 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.freshName, which increments + */ + def freshTermName(prefix: String) = TermName(ctx.freshName("$" + prefix)) + + /** + * Constructs a new, synthetic, local ValDef Type `tpe`, a unique name, + * Position `pos`, an empty implementation (no rhs), and owned by `owner`. + */ + def freshValDef(tpe: Type, pos: Position, owner: Symbol): ValDef = { + val SYNTHETIC = (1 << 21).toLong.asInstanceOf[FlagSet] + val sym = owner.newTermSymbol(freshTermName("q"), pos, SYNTHETIC) + setInfo(sym, tpe) + val vd = internal.valDef(sym, EmptyTree) + vd.setPos(pos) + vd + } + + 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: (String, Type, 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(Select(_, nme), tpe :: Nil), qual :: Nil) + if isWrapper(nme.decodedName.toString, tpe.tpe, qual) => + () + 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: (String, Type, Tree) => Boolean): Tree => Unit = { + case s @ ApplyTree(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil) => + if (isWrapper(nme.decodedName.toString, tpe.tpe, qual)) + 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) + + /** Constructs a tuple value of the right TupleN type from the provided inputs.*/ + def mkTuple(args: List[Tree]): Tree = + global.gen.mkTuple(args.asInstanceOf[List[global.Tree]]).asInstanceOf[ctx.universe.Tree] + + def setSymbol[_Tree](t: _Tree, sym: Symbol): Unit = { + t.asInstanceOf[global.Tree].setSymbol(sym.asInstanceOf[global.Symbol]) + () + } + def setInfo(sym: Symbol, tpe: Type): Unit = { + sym.asInstanceOf[global.Symbol].setInfo(tpe.asInstanceOf[global.Type]) + () + } + + /** Creates a new, synthetic type variable with the specified `owner`. */ + def newTypeVariable(owner: Symbol, prefix: String = "T0"): TypeSymbol = + owner + .asInstanceOf[global.Symbol] + .newSyntheticTypeParam(prefix, 0L) + .asInstanceOf[ctx.universe.TypeSymbol] + + /** The type representing the type constructor `[X] X` */ + lazy val idTC: Type = { + val tvar = newTypeVariable(NoSymbol) + internal.polyType(tvar :: Nil, refVar(tvar)) + } + + /** A Type that references the given type variable. */ + def refVar(variable: TypeSymbol): Type = variable.toTypeConstructor + + /** 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): TypeSymbol = { + val tc = newTypeVariable(owner) + val arg = newTypeVariable(tc, "x"); + tc.setInfo(internal.polyType(arg :: Nil, emptyTypeBounds)) + tc + } + + /** >: Nothing <: Any */ + def emptyTypeBounds: TypeBounds = + internal.typeBounds(definitions.NothingClass.toType, definitions.AnyClass.toType) + + /** Creates a new anonymous function symbol with Position `pos`. */ + def functionSymbol(pos: Position): Symbol = + callsiteTyper.context.owner + .newAnonymousFunctionValue(pos.asInstanceOf[global.Position]) + .asInstanceOf[ctx.universe.Symbol] + + def functionType(args: List[Type], result: Type): Type = { + val tpe = global.definitions + .functionType(args.asInstanceOf[List[global.Type]], result.asInstanceOf[global.Type]) + tpe.asInstanceOf[Type] + } + + /** Create a Tree that references the `val` represented by `vd`, copying attributes from `replaced`. */ + def refVal(replaced: Tree, vd: ValDef): Tree = + treeCopy.Ident(replaced, vd.name).setSymbol(vd.symbol) + + /** Creates a Function tree using `functionSym` as the Symbol and changing `initialOwner` to `functionSym` in `body`.*/ + def createFunction(params: List[ValDef], body: Tree, functionSym: Symbol): Tree = { + changeOwner(body, initialOwner, functionSym) + val f = Function(params, body) + setSymbol(f, functionSym) + f + } + + def changeOwner(tree: Tree, prev: Symbol, next: Symbol): Unit = + new ChangeOwnerAndModuleClassTraverser( + prev.asInstanceOf[global.Symbol], + next.asInstanceOf[global.Symbol]).traverse(tree.asInstanceOf[global.Tree]) + + // Workaround copied from scala/async:can be removed once https://github.com/scala/scala/pull/3179 is merged. + private[this] class ChangeOwnerAndModuleClassTraverser(oldowner: global.Symbol, + newowner: global.Symbol) + extends global.ChangeOwnerTraverser(oldowner, newowner) { + override def traverse(tree: global.Tree): Unit = { + tree match { + case _: global.DefTree => change(tree.symbol.moduleClass) + case _ => + } + super.traverse(tree) + } + } + + /** 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 => sys.error("Instance must be static (was " + x + ").") + } + + def select(t: Tree, name: String): Tree = Select(t, TermName(name)) + + /** Returns the symbol for the non-private method named `name` for the class/module `obj`. */ + def method(obj: Symbol, name: String): Symbol = { + val ts: Type = obj.typeSignature + val m: global.Symbol = ts.asInstanceOf[global.Type].nonPrivateMember(global.newTermName(name)) + m.asInstanceOf[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 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.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(, , .target) returns true. + * Typically, `f` is a `Select` or `Ident`. + * The wrapper is replaced with the result of `subWrapper(, , )` + */ + def transformWrappers(t: Tree, + subWrapper: (String, Type, Tree, Tree) => Converted[ctx.type]): 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(Select(_, nme), targ :: Nil), qual :: Nil) => + subWrapper(nme.decodedName.toString, targ.tpe, qual, tree) match { + case Converted.Success(t, finalTx) => + changeOwner(qual, currentOwner, initialOwner) // Fixes https://github.com/sbt/sbt/issues/1150 + finalTx(t) + case Converted.Failure(p, m) => ctx.abort(p, m) + case _: Converted.NotApplicable[_] => super.transform(tree) + } + case _ => super.transform(tree) + } + } + appTransformer.atOwner(initialOwner) { + appTransformer.transform(t) + } + } +} diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala new file mode 100644 index 000000000..edb13f54c --- /dev/null +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala @@ -0,0 +1,42 @@ +package sbt.internal.util +package appmacro + +import scala.reflect._ +import macros._ +import Types.idFun + +abstract class Convert { + def apply[T: c.WeakTypeTag](c: blackbox.Context)(nme: String, in: c.Tree): Converted[c.type] + def asPredicate(c: blackbox.Context): (String, c.Type, c.Tree) => Boolean = + (n, tpe, tree) => { + val tag = c.WeakTypeTag(tpe) + apply(c)(n, tree)(tag).isSuccess + } +} +sealed trait Converted[C <: blackbox.Context with Singleton] { + def isSuccess: Boolean + def transform(f: C#Tree => C#Tree): Converted[C] +} +object Converted { + def NotApplicable[C <: blackbox.Context with Singleton] = new NotApplicable[C] + final case class Failure[C <: blackbox.Context with Singleton](position: C#Position, + message: String) + extends Converted[C] { + def isSuccess = false + def transform(f: C#Tree => C#Tree): Converted[C] = new Failure(position, message) + } + final class NotApplicable[C <: blackbox.Context with Singleton] extends Converted[C] { + def isSuccess = false + def transform(f: C#Tree => C#Tree): Converted[C] = this + } + final case class Success[C <: blackbox.Context with Singleton](tree: C#Tree, + finalTransform: C#Tree => C#Tree) + extends Converted[C] { + def isSuccess = true + def transform(f: C#Tree => C#Tree): Converted[C] = Success(f(tree), finalTransform) + } + object Success { + def apply[C <: blackbox.Context with Singleton](tree: C#Tree): Success[C] = + Success(tree, idFun) + } +} diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala new file mode 100644 index 000000000..e5a2fcbf3 --- /dev/null +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala @@ -0,0 +1,216 @@ +package sbt.internal.util +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 MonadInstance extends Instance { + def flatten[T](in: M[M[T]]): M[T] +} + +import scala.reflect._ +import macros._ + +object Instance { + final val ApplyName = "app" + final val FlattenName = "flatten" + final val PureName = "pure" + final val MapName = "map" + final val InstanceTCName = "M" + + final class Input[U <: Universe with Singleton](val tpe: U#Type, + val expr: U#Tree, + val local: U#ValDef) + trait Transform[C <: blackbox.Context with Singleton, N[_]] { + def apply(in: C#Tree): C#Tree + } + def idTransform[C <: blackbox.Context with Singleton]: Transform[C, Id] = new Transform[C, Id] { + def apply(in: C#Tree): C#Tree = in + } + + /** + * 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 `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 `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, N[_]]( + c: blackbox.Context, + i: Instance with Singleton, + convert: Convert, + builder: TupleBuilder)(t: Either[c.Expr[T], c.Expr[i.M[T]]], inner: Transform[c.type, N])( + implicit tt: c.WeakTypeTag[T], + nt: c.WeakTypeTag[N[T]], + it: c.TypeTag[i.type] + ): c.Expr[i.M[N[T]]] = { + import c.universe.{ Apply => ApplyTree, _ } + + val util = ContextUtil[c.type](c) + val mTC: Type = util.extractTC(i, InstanceTCName) + val mttpe: Type = appliedType(mTC, nt.tpe :: Nil).dealias + + // the tree for the macro argument + val (tree, treeType) = t match { + case Left(l) => (l.tree, nt.tpe.dealias) + case Right(r) => (r.tree, mttpe) + } + // the Symbol for the anonymous function passed to the appropriate Instance.map/flatMap/pure method + // this Symbol needs to be known up front so that it can be used as the owner of synthetic vals + val functionSym = util.functionSymbol(tree.pos) + + 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 isWrapper: (String, Type, Tree) => Boolean = convert.asPredicate(c) + + // Local definitions `defs` in the macro. This is used to ensure references are to M instances defined outside of the macro call. + // Also `refCount` is the number of references, which is used to create the private, synthetic method containing the body + val defs = util.collectDefs(tree, isWrapper) + val checkQual: Tree => Unit = util.checkReferences(defs, isWrapper) + + type In = Input[c.universe.type] + var inputs = List[In]() + + // 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(util.select(instance, PureName), TypeTree(treeType) :: Nil) + val f = util.createFunction(Nil, body, functionSym) + val p = ApplyTree(typeApplied, f :: 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(util.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 = + treeCopy.ValDef(variable, util.parameterModifiers, variable.name, variable.tpt, EmptyTree) + val typeApplied = + TypeApply(util.select(instance, MapName), variable.tpt :: TypeTree(treeType) :: Nil) + val f = util.createFunction(param :: Nil, body, functionSym) + val mapped = ApplyTree(typeApplied, input.expr :: f :: 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 = util.freshMethodParameter(appliedType(result.representationC, util.idTC :: Nil)) + val bindings = result.extract(param) + val f = util.createFunction(param :: Nil, Block(bindings, body), functionSym) + val ttt = TypeTree(treeType) + val typedApp = + TypeApply(util.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, selection: Tree): Tree = { + qual.foreach(checkQual) + val vd = util.freshValDef(tpe, qual.pos, functionSym) + inputs ::= new Input(tpe, qual, vd) + util.refVal(selection, vd) + } + def sub(name: String, tpe: Type, qual: Tree, replace: Tree): Converted[c.type] = { + val tag = c.WeakTypeTag[T](tpe) + convert[T](c)(name, qual)(tag) transform { tree => + addType(tpe, tree, replace) + } + } + + // applies the transformation + val tx = util.transformWrappers(tree, (n, tpe, t, replace) => sub(n, tpe, t, replace)) + // resetting attributes must be: a) local b) done here and not wider or else there are obscure errors + val tr = makeApp(inner(tx)) + c.Expr[i.M[N[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/core-macros/src/main/scala/sbt/internal/util/appmacro/KListBuilder.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/KListBuilder.scala new file mode 100644 index 000000000..4a2428912 --- /dev/null +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/KListBuilder.scala @@ -0,0 +1,72 @@ +package sbt.internal.util +package appmacro + +import scala.reflect._ +import macros._ + +/** A `TupleBuilder` that uses a KList as the tuple representation.*/ +object KListBuilder extends TupleBuilder { + def make(c: blackbox.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.companion) + val kconsTpe = c.typeOf[KCons[Int, KNil, List]] + val kcons = kconsTpe.typeSymbol.companion + 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: TypeSymbol = newTCVariable(util.initialOwner) + + /** 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 (x @ ValDef(mods, name, tpt, _)) :: xs => + val rhs = select(Ident(prev.name), "head") + val head = treeCopy.ValDef(x, mods, name, tpt, rhs) + util.setSymbol(head, x.symbol) + 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 + } + + private[this] def makeKList(revInputs: Inputs[c.universe.type], + klist: Tree, + klistType: Type): Tree = + revInputs match { + case in :: tail => + val next = ApplyTree( + TypeApply(Ident(kcons), + TypeTree(in.tpe) :: TypeTree(klistType) :: TypeTree(mTC) :: Nil), + in.expr :: klist :: Nil) + makeKList(tail, next, appliedType(kconsTC, in.tpe :: klistType :: mTC :: Nil)) + case Nil => klist + } + + /** The input trees combined in a KList */ + val klist = makeKList(inputs.reverse, knil, knilType) + + /** + * 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 = internal.polyType(tcVariable :: Nil, klistType) + val input = klist + val alistInstance: ctx.universe.Tree = + TypeApply(select(Ident(alist), "klist"), TypeTree(representationC) :: Nil) + def extract(param: ValDef) = bindKList(param, Nil, inputs.map(_.local)) + } +} diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/MixedBuilder.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/MixedBuilder.scala new file mode 100644 index 000000000..8688d7376 --- /dev/null +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/MixedBuilder.scala @@ -0,0 +1,17 @@ +package sbt.internal.util +package appmacro + +import scala.reflect._ +import macros._ + +/** + * 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: blackbox.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) + } +} diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleBuilder.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleBuilder.scala new file mode 100644 index 000000000..0ca8b3ab3 --- /dev/null +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleBuilder.scala @@ -0,0 +1,56 @@ +package sbt.internal.util +package appmacro + +import scala.reflect._ +import macros._ + +/** + * 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: blackbox.Context)(tcType: c.Type, + inputs: Inputs[c.universe.type]): BuilderResult[c.type] +} + +trait BuilderResult[C <: blackbox.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/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleNBuilder.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleNBuilder.scala new file mode 100644 index 000000000..bb005a537 --- /dev/null +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleNBuilder.scala @@ -0,0 +1,56 @@ +package sbt.internal.util +package appmacro + +import scala.tools.nsc.Global +import scala.reflect._ +import macros._ + +/** + * 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: blackbox.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._ + import util._ + + val global: Global = c.universe.asInstanceOf[Global] + + val ctx: c.type = c + val representationC: PolyType = { + val tcVariable: Symbol = newTCVariable(util.initialOwner) + val tupleTypeArgs = inputs.map(in => + internal.typeRef(NoPrefix, tcVariable, in.tpe :: Nil).asInstanceOf[global.Type]) + val tuple = global.definitions.tupleType(tupleTypeArgs) + internal.polyType(tcVariable :: Nil, tuple.asInstanceOf[Type]) + } + + val input: Tree = mkTuple(inputs.map(_.expr)) + val alistInstance: Tree = { + val selectTree = select(Ident(alist), TupleMethodName + inputs.size.toString) + TypeApply(selectTree, 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 (x @ ValDef(mods, name, tpt, _)) :: xs => + val rhs = select(Ident(param.name), "_" + i.toString) + val newVal = treeCopy.ValDef(x, mods, name, tpt, rhs) + util.setSymbol(newVal, x.symbol) + bindTuple(param, newVal :: revBindings, xs, i + 1) + case Nil => revBindings.reverse + } + } +}