mirror of https://github.com/sbt/sbt.git
Port Applicative-do macro to Scala 3
See https://eed3si9n.com/sudori-part3/ for details
This commit is contained in:
parent
1ed94bff16
commit
8d5355d274
|
|
@ -1,231 +0,0 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package appmacro
|
||||
|
||||
import sbt.internal.util.Classes.Applicative
|
||||
import sbt.internal.util.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.macros._
|
||||
|
||||
object Instance {
|
||||
type Aux[M0[_]] = Instance { type M[x] = M0[x] }
|
||||
type Aux2[M0[_], N[_]] = Instance { type M[x] = M0[N[x]] }
|
||||
|
||||
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] = in => 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,
|
||||
linter: LinterDSL
|
||||
)(
|
||||
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, mttpe.erasure)
|
||||
|
||||
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): Tree) :: 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
|
||||
linter.runLinter(c)(tree)
|
||||
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))
|
||||
val noWarn = q"""($tr: @_root_.scala.annotation.nowarn("cat=other-pure-statement"))"""
|
||||
c.Expr[i.M[N[T]]](noWarn)
|
||||
}
|
||||
|
||||
import Types._
|
||||
|
||||
implicit def applicativeInstance[A[_]](implicit ap: Applicative[A]): Instance.Aux[A] =
|
||||
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())
|
||||
}
|
||||
|
||||
def compose[A[_], B[_]](implicit a: Aux[A], b: Aux[B]): Instance.Aux2[A, B] =
|
||||
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: Aux[A], b: Aux[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)
|
||||
a.app[AList.SplitK[K, B]#l, B[Z]](in, g)(AList.asplit(alist))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
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.foldRight(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))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
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: TupleBuilder)
|
||||
else (TupleNBuilder: TupleBuilder)
|
||||
delegate.make(c)(mt, inputs)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
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]
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
package sbt
|
||||
package internal
|
||||
package util
|
||||
package appmacro
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.reflect.TypeTest
|
||||
import scala.quoted.*
|
||||
import sbt.util.Applicative
|
||||
import sbt.util.Monad
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
trait Cont:
|
||||
final val InstanceTCName = "F"
|
||||
|
||||
extension [C <: Quotes & Singleton](conv: Convert[C])
|
||||
/**
|
||||
* Implementation of a macro that provides a direct syntax for applicative functors. It is
|
||||
* intended to be used in conjunction with another macro that conditions the inputs.
|
||||
*/
|
||||
def contMapN[A: Type, F[_], Effect[_]: Type](
|
||||
tree: Expr[A],
|
||||
inner: conv.TermTransform[Effect]
|
||||
)(using
|
||||
iftpe: Type[F],
|
||||
eatpe: Type[Effect[A]],
|
||||
): Expr[F[Effect[A]]] =
|
||||
contImpl[A, F, Effect](Left(tree), inner)
|
||||
|
||||
/**
|
||||
* Implementation of a macro that provides a direct syntax for applicative functors. It is
|
||||
* intended to be used in conjunction with another macro that conditions the inputs.
|
||||
*/
|
||||
def contFlatMap[A: Type, F[_], Effect[_]: Type](
|
||||
tree: Expr[F[A]],
|
||||
inner: conv.TermTransform[Effect]
|
||||
)(using
|
||||
iftpe: Type[F],
|
||||
eatpe: Type[Effect[A]],
|
||||
): Expr[F[Effect[A]]] =
|
||||
contImpl[A, F, Effect](Right(tree), inner)
|
||||
|
||||
/**
|
||||
* 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 Term `t` to find inputs of the form `wrap[A]( input )` This form is
|
||||
* typically constructed by another macro that pretends to be able to get a value of type `A`
|
||||
* from a value convertible to `F[A]`. 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 `F[A]` 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 `Term` and `Type` to be hidden behind the raw `A` 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[A]( input )` is transformed by `convert`.
|
||||
* This transformation converts the input Term to a Term of type `F[A]`. The original wrapped
|
||||
* expression `wrap(input)` is replaced by a reference to a new local `val x: A`, 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 `eitherTree` 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),
|
||||
* `in` 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[A: Type, F[_], Effect[_]: Type](
|
||||
eitherTree: Either[Expr[A], Expr[F[A]]],
|
||||
inner: conv.TermTransform[Effect]
|
||||
)(using
|
||||
iftpe: Type[F],
|
||||
eatpe: Type[Effect[A]],
|
||||
): Expr[F[Effect[A]]] =
|
||||
import conv.*
|
||||
import qctx.reflect.*
|
||||
given qctx.type = qctx
|
||||
|
||||
val fTypeCon = TypeRepr.of[F]
|
||||
val faTpe = fTypeCon.appliedTo(TypeRepr.of[Effect[A]])
|
||||
val (expr, treeType) = eitherTree match
|
||||
case Left(l) => (l, TypeRepr.of[Effect[A]])
|
||||
case Right(r) => (r, faTpe)
|
||||
|
||||
// we can extract i out of i.type
|
||||
val instanceExpr = Expr.summon[Applicative[F]].get
|
||||
val inputBuf = ListBuffer[Input]()
|
||||
|
||||
def makeApp(body: Term, inputs: List[Input]): Expr[F[Effect[A]]] = inputs match
|
||||
case Nil => pure(body)
|
||||
case x :: Nil => genMap(body, x)
|
||||
case xs => genMapN(body, xs)
|
||||
|
||||
// no inputs, so construct F[A] via Instance.pure or pure+flatten
|
||||
def pure(body: Term): Expr[F[Effect[A]]] =
|
||||
def pure0[A1: Type](body: Expr[A1]): Expr[F[A1]] =
|
||||
'{
|
||||
$instanceExpr.pure[A1] { $body }
|
||||
}
|
||||
eitherTree match
|
||||
case Left(_) => pure0[Effect[A]](body.asExprOf[Effect[A]])
|
||||
case Right(_) =>
|
||||
flatten(pure0[F[Effect[A]]](body.asExprOf[F[Effect[A]]]))
|
||||
|
||||
// m should have type F[F[A]]
|
||||
// the returned Tree will have type F[A]
|
||||
def flatten(m: Expr[F[F[Effect[A]]]]): Expr[F[Effect[A]]] =
|
||||
'{
|
||||
{
|
||||
val i1 = $instanceExpr.asInstanceOf[Monad[F]]
|
||||
i1.flatten[Effect[A]]($m.asInstanceOf[F[F[Effect[A]]]])
|
||||
}
|
||||
}
|
||||
|
||||
def genMap(body: Term, input: Input): Expr[F[Effect[A]]] =
|
||||
def genMap0[A1: Type](body: Expr[A1]): Expr[F[A1]] =
|
||||
input.tpe.asType match
|
||||
case '[a] =>
|
||||
val tpe =
|
||||
MethodType(List(input.name))(_ => List(TypeRepr.of[a]), _ => TypeRepr.of[A1])
|
||||
val lambda = Lambda(
|
||||
owner = Symbol.spliceOwner,
|
||||
tpe = tpe,
|
||||
rhsFn = (sym, params) => {
|
||||
val param = params.head.asInstanceOf[Term]
|
||||
// Called when transforming the tree to add an input.
|
||||
// For `qual` of type F[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 substitute(name: String, tpe: TypeRepr, qual: Term, replace: Term) =
|
||||
convert[A](name, qual) transform { (tree: Term) =>
|
||||
typed[a](Ref(param.symbol))
|
||||
}
|
||||
transformWrappers(body.asTerm.changeOwner(sym), substitute, sym)
|
||||
}
|
||||
).asExprOf[a => A1]
|
||||
val expr = input.term.asExprOf[F[a]]
|
||||
typed[F[A1]](
|
||||
'{
|
||||
$instanceExpr.map[a, A1]($expr.asInstanceOf[F[a]])($lambda)
|
||||
}.asTerm
|
||||
).asExprOf[F[A1]]
|
||||
eitherTree match
|
||||
case Left(_) =>
|
||||
genMap0[Effect[A]](body.asExprOf[Effect[A]])
|
||||
case Right(_) =>
|
||||
flatten(genMap0[F[Effect[A]]](body.asExprOf[F[Effect[A]]]))
|
||||
|
||||
def genMapN(body: Term, inputs: List[Input]): Expr[F[Effect[A]]] =
|
||||
def genMapN0[A1: Type](body: Expr[A1]): Expr[F[A1]] =
|
||||
val br = makeTuple(inputs)
|
||||
val lambdaTpe =
|
||||
MethodType(List("$p0"))(_ => List(br.inputTupleTypeRepr), _ => TypeRepr.of[A1])
|
||||
val lambda = Lambda(
|
||||
owner = Symbol.spliceOwner,
|
||||
tpe = lambdaTpe,
|
||||
rhsFn = (sym, params) => {
|
||||
val p0 = params.head.asInstanceOf[Term]
|
||||
// Called when transforming the tree to add an input.
|
||||
// For `qual` of type F[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 substitute(name: String, tpe: TypeRepr, qual: Term, replace: Term) =
|
||||
convert[A](name, qual) transform { (tree: Term) =>
|
||||
val idx = inputs.indexWhere(input => input.term == qual)
|
||||
Select
|
||||
.unique(Ref(p0.symbol), "apply")
|
||||
.appliedToTypes(List(br.inputTupleTypeRepr))
|
||||
.appliedToArgs(List(Literal(IntConstant(idx))))
|
||||
}
|
||||
transformWrappers(body.asTerm.changeOwner(sym), substitute, sym)
|
||||
}
|
||||
)
|
||||
val tupleMapRepr = TypeRepr
|
||||
.of[Tuple.Map]
|
||||
.appliedTo(List(br.inputTupleTypeRepr, TypeRepr.of[F]))
|
||||
tupleMapRepr.asType match
|
||||
case '[tupleMap] =>
|
||||
br.inputTupleTypeRepr.asType match
|
||||
case '[inputTypeTpe] =>
|
||||
'{
|
||||
given Applicative[F] = $instanceExpr
|
||||
AList.tuple.mapN[F, A1, inputTypeTpe & Tuple](${
|
||||
br.tupleExpr.asInstanceOf[Expr[Tuple.Map[inputTypeTpe & Tuple, F]]]
|
||||
})(
|
||||
${ lambda.asExprOf[inputTypeTpe => A1] }
|
||||
)
|
||||
}
|
||||
|
||||
eitherTree match
|
||||
case Left(_) =>
|
||||
genMapN0[Effect[A]](body.asExprOf[Effect[A]])
|
||||
case Right(_) =>
|
||||
flatten(genMapN0[F[Effect[A]]](body.asExprOf[F[Effect[A]]]))
|
||||
|
||||
// Called when transforming the tree to add an input.
|
||||
// For `qual` of type F[A], and a `selection` qual.value.
|
||||
def record(name: String, tpe: TypeRepr, qual: Term, replace: Term) =
|
||||
convert[A](name, qual) transform { (tree: Term) =>
|
||||
inputBuf += Input(tpe, qual, freshName("q"))
|
||||
replace
|
||||
}
|
||||
val tx = transformWrappers(expr.asTerm, record, Symbol.spliceOwner)
|
||||
val tr = makeApp(inner(tx), inputBuf.toList)
|
||||
tr
|
||||
end Cont
|
||||
|
|
@ -9,30 +9,6 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val qctx: C):
|
|||
import qctx.reflect.*
|
||||
given qctx.type = qctx
|
||||
|
||||
/**
|
||||
* Returns a Type representing the type constructor tcp.<name>. For example, given `object Demo {
|
||||
* type M[x] = List[x] }`, the call `extractTypeCon(Demo, "M")` will return a type representing
|
||||
* the type constructor `[x] List[x]`.
|
||||
*/
|
||||
def extractTypeCon(tcp: AnyRef & scala.Singleton, name: String)(using
|
||||
tcpt: Type[tcp.type]
|
||||
): TypeRepr =
|
||||
val tcpTpe = TypeRepr.of[tcp.type]
|
||||
val fSym = tcpTpe.typeSymbol.declaredType(name).head
|
||||
val typeConTpe: TypeRepr = tcpTpe.memberType(fSym)
|
||||
val hiRepr = typeConTpe match
|
||||
case TypeBounds(low, TypeLambda(_, _, AppliedType(tc, _))) => tc
|
||||
hiRepr
|
||||
|
||||
/**
|
||||
* Returns a reference given a singleton/termref
|
||||
*/
|
||||
def extractSingleton[A: Type]: Expr[A] =
|
||||
def termRef(r: TypeRepr)(using rtt: TypeTest[TypeRepr, TermRef]): Ref = r match
|
||||
case rtt(ref) => Ref.term(ref)
|
||||
case _ => sys.error(s"expected termRef but got $r")
|
||||
termRef(TypeRepr.of[A]).asExprOf[A]
|
||||
|
||||
private var counter: Int = -1
|
||||
def freshName(prefix: String): String =
|
||||
counter = counter + 1
|
||||
|
|
@ -58,6 +34,18 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val qctx: C):
|
|||
def typed[A: Type](value: Term): Term =
|
||||
Typed(value, TypeTree.of[A])
|
||||
|
||||
def makeTuple(inputs: List[Input]): BuilderResult =
|
||||
new BuilderResult:
|
||||
override def inputTupleTypeRepr: TypeRepr =
|
||||
tupleTypeRepr(inputs.map(_.tpe))
|
||||
override def tupleExpr: Expr[Tuple] =
|
||||
Expr.ofTupleFromSeq(inputs.map(_.term.asExpr))
|
||||
|
||||
trait BuilderResult:
|
||||
def inputTupleTypeRepr: TypeRepr
|
||||
def tupleExpr: Expr[Tuple]
|
||||
end BuilderResult
|
||||
|
||||
def tupleTypeRepr(param: List[TypeRepr]): TypeRepr =
|
||||
param match
|
||||
case x :: xs => TypeRepr.of[scala.*:].appliedTo(List(x, tupleTypeRepr(xs)))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package sbt.internal
|
||||
|
||||
import sbt.internal.util.appmacro.*
|
||||
import verify.*
|
||||
import ContTestMacro.*
|
||||
import sbt.util.Applicative
|
||||
|
||||
object ContTest extends BasicTestSuite:
|
||||
test("pure") {
|
||||
given Applicative[List] = sbt.util.ListInstances.listMonad
|
||||
val actual = contMapNMacro[List, Int](12)
|
||||
assert(actual == List(12))
|
||||
}
|
||||
|
||||
test("getMap") {
|
||||
given Applicative[List] = sbt.util.ListInstances.listMonad
|
||||
val actual = contMapNMacro[List, Int](ContTest.wrapInit(List(1)) + 2)
|
||||
assert(actual == List(3))
|
||||
}
|
||||
|
||||
test("getMapN") {
|
||||
given Applicative[List] = sbt.util.ListInstances.listMonad
|
||||
val actual = contMapNMacro[List, Int](
|
||||
ContTest.wrapInit(List(1))
|
||||
+ ContTest.wrapInit(List(2)) + 3
|
||||
)
|
||||
assert(actual == List(6))
|
||||
}
|
||||
|
||||
test("getMapN2") {
|
||||
given Applicative[List] = sbt.util.ListInstances.listMonad
|
||||
val actual = contMapNMacro[List, Int]({
|
||||
val x = ContTest.wrapInit(List(1))
|
||||
val y = ContTest.wrapInit(List(2))
|
||||
x + y + 3
|
||||
})
|
||||
assert(actual == List(6))
|
||||
}
|
||||
|
||||
// This compiles away
|
||||
def wrapInit[A](a: List[A]): A = ???
|
||||
end ContTest
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package sbt.internal
|
||||
|
||||
import sbt.internal.util.Types.Id
|
||||
import sbt.internal.util.appmacro.*
|
||||
import sbt.util.Applicative
|
||||
import scala.quoted.*
|
||||
import ConvertTestMacro.InputInitConvert
|
||||
|
||||
object ContTestMacro:
|
||||
inline def contMapNMacro[F[_]: Applicative, A](inline expr: A): List[A] =
|
||||
${ contMapNMacroImpl[F, A]('expr) }
|
||||
|
||||
def contMapNMacroImpl[F[_]: Type, A: Type](expr: Expr[A])(using
|
||||
qctx: Quotes
|
||||
): Expr[List[A]] =
|
||||
object ContSyntax extends Cont
|
||||
import ContSyntax.*
|
||||
val convert1: Convert[qctx.type] = new InputInitConvert(qctx)
|
||||
convert1.contMapN[A, List, Id](expr, convert1.idTransform)
|
||||
|
||||
end ContTestMacro
|
||||
|
|
@ -16,4 +16,5 @@ end Applicative
|
|||
|
||||
object Applicative:
|
||||
given Applicative[Option] = OptionInstances.optionMonad
|
||||
given Applicative[List] = ListInstances.listMonad
|
||||
end Applicative
|
||||
|
|
|
|||
|
|
@ -16,4 +16,5 @@ end Apply
|
|||
|
||||
object Apply:
|
||||
given Apply[Option] = OptionInstances.optionMonad
|
||||
given Apply[List] = ListInstances.listMonad
|
||||
end Apply
|
||||
|
|
|
|||
|
|
@ -19,4 +19,5 @@ end FlatMap
|
|||
|
||||
object FlatMap:
|
||||
given FlatMap[Option] = OptionInstances.optionMonad
|
||||
given FlatMap[List] = ListInstances.listMonad
|
||||
end FlatMap
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@ end Functor
|
|||
|
||||
object Functor:
|
||||
given Functor[Option] = OptionInstances.optionMonad
|
||||
given Functor[List] = ListInstances.listMonad
|
||||
end Functor
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package sbt.util
|
|||
private[sbt] object ListInstances:
|
||||
lazy val listMonad: Monad[List] =
|
||||
new Monad[List]:
|
||||
type F[a] = List[a]
|
||||
def pure[A](x: A): List[A] = List(x)
|
||||
def ap[A, B](ff: List[A => B])(fa: List[A]): List[B] =
|
||||
for
|
||||
|
|
|
|||
|
|
@ -16,4 +16,5 @@ end Monad
|
|||
|
||||
object Monad:
|
||||
given Monad[Option] = OptionInstances.optionMonad
|
||||
given Monad[List] = ListInstances.listMonad
|
||||
end Monad
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ package sbt.util
|
|||
private[sbt] object OptionInstances:
|
||||
lazy val optionMonad: Monad[Option] =
|
||||
new Monad[Option]:
|
||||
type F[a] = Option[a]
|
||||
|
||||
def pure[A](x: A): Option[A] = Some(x)
|
||||
def ap[A, B](ff: Option[A => B])(fa: Option[A]): Option[B] =
|
||||
if ff.isDefined && fa.isDefined then Some(ff.get(fa.get))
|
||||
|
|
|
|||
Loading…
Reference in New Issue