Port Applicative-do macro to Scala 3

See https://eed3si9n.com/sudori-part3/ for details
This commit is contained in:
Eugene Yokota 2021-12-12 22:59:18 -05:00
parent 1ed94bff16
commit 8d5355d274
16 changed files with 306 additions and 505 deletions

View File

@ -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))
}
}
}

View File

@ -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))
}
}

View File

@ -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)
}
}

View File

@ -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]
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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)))

View File

@ -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

View File

@ -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

View File

@ -16,4 +16,5 @@ end Applicative
object Applicative:
given Applicative[Option] = OptionInstances.optionMonad
given Applicative[List] = ListInstances.listMonad
end Applicative

View File

@ -16,4 +16,5 @@ end Apply
object Apply:
given Apply[Option] = OptionInstances.optionMonad
given Apply[List] = ListInstances.listMonad
end Apply

View File

@ -19,4 +19,5 @@ end FlatMap
object FlatMap:
given FlatMap[Option] = OptionInstances.optionMonad
given FlatMap[List] = ListInstances.listMonad
end FlatMap

View File

@ -13,4 +13,5 @@ end Functor
object Functor:
given Functor[Option] = OptionInstances.optionMonad
given Functor[List] = ListInstances.listMonad
end Functor

View File

@ -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

View File

@ -16,4 +16,5 @@ end Monad
object Monad:
given Monad[Option] = OptionInstances.optionMonad
given Monad[List] = ListInstances.listMonad
end Monad

View File

@ -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))