Skip to content

Commit 061a9ba

Browse files
committed
Part 4 - reimplement Applicative and Target macros
1 parent bc5e525 commit 061a9ba

File tree

8 files changed

+555
-416
lines changed

8 files changed

+555
-416
lines changed

main/api/src/mill/api/Ctx.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import scala.language.implicitConversions
77
* Provides access to various resources in the context of a currently execution Target.
88
*/
99
object Ctx {
10-
// @compileTimeOnly("Target.ctx() / T.ctx() / T.* APIs can only be used with a T{...} block")
10+
@compileTimeOnly("Target.ctx() / T.ctx() / T.* APIs can only be used with a T{...} block")
1111
@ImplicitStub
1212
implicit def taskCtx: Ctx = ???
1313

main/define/src/mill/define/Applicative.scala

Lines changed: 98 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import mill.api.internal
55
import scala.annotation.compileTimeOnly
66
import scala.reflect.macros.blackbox.Context
77

8+
import scala.quoted.*
9+
810
/**
911
* A generic Applicative-functor macro: translates calls to
1012
*
@@ -24,7 +26,7 @@ object Applicative {
2426
def apply[T](t: M[T]): T
2527
}
2628
object ApplyHandler {
27-
// @compileTimeOnly("Target#apply() can only be used with a T{...} block")
29+
@compileTimeOnly("Target#apply() can only be used with a T{...} block")
2830
implicit def defaultApplyHandler[M[+_]]: ApplyHandler[M] = ???
2931
}
3032
trait Applyable[M[+_], +T] {
@@ -39,73 +41,102 @@ object Applicative {
3941
def traverseCtx[I, R](xs: Seq[W[I]])(f: (IndexedSeq[I], Ctx) => Z[R]): T[R]
4042
}
4143

42-
def impl[M[_], T: c.WeakTypeTag, Ctx: c.WeakTypeTag](c: Context)(t: c.Expr[T]): c.Expr[M[T]] = {
43-
// impl0(c)(t.tree)(implicitly[c.WeakTypeTag[T]], implicitly[c.WeakTypeTag[Ctx]])
44-
???
44+
def impl[M[_]: Type, Q[_]: Type, Z[_]: Type, T: Type, Ctx: Type](using Quotes)(caller: Expr[Applyer[Q, M, Z, Ctx]], t: Expr[Z[T]]): Expr[M[T]] = {
45+
import quotes.reflect.*
46+
impl0(using quotes)(caller.asTerm, t.asTerm)(using Type.of[M], Type.of[Q], Type.of[Z], Type.of[T], Type.of[Ctx])
4547
}
46-
def impl0[M[_], T: c.WeakTypeTag, Ctx: c.WeakTypeTag](c: Context)(t: c.Tree): c.Expr[M[T]] = {
47-
???
48-
// import c.universe._
49-
// def rec(t: Tree): Iterator[c.Tree] = Iterator(t) ++ t.children.flatMap(rec(_))
50-
51-
// val exprs = collection.mutable.Buffer.empty[c.Tree]
52-
// val targetApplySym = typeOf[Applyable[Nothing, _]].member(TermName("apply"))
53-
54-
// val itemsName = c.freshName(TermName("items"))
55-
// val itemsSym = c.internal.newTermSymbol(c.internal.enclosingOwner, itemsName)
56-
// c.internal.setFlag(itemsSym, (1L << 44).asInstanceOf[c.universe.FlagSet])
57-
// c.internal.setInfo(itemsSym, typeOf[Seq[Any]])
58-
// // Derived from @olafurpg's
59-
// // https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608
60-
// val defs = rec(t).filter(_.isDef).map(_.symbol).toSet
61-
62-
// val ctxName = TermName(c.freshName("ctx"))
63-
// val ctxSym = c.internal.newTermSymbol(c.internal.enclosingOwner, ctxName)
64-
// c.internal.setInfo(ctxSym, weakTypeOf[Ctx])
65-
66-
// val transformed = c.internal.typingTransform(t) {
67-
// case (t @ q"$fun.apply()($handler)", api) if t.symbol == targetApplySym =>
68-
// val localDefs = rec(fun).filter(_.isDef).map(_.symbol).toSet
69-
// val banned = rec(t).filter(x => defs(x.symbol) && !localDefs(x.symbol))
70-
71-
// if (banned.hasNext) {
72-
// val banned0 = banned.next()
73-
// c.abort(
74-
// banned0.pos,
75-
// "Target#apply() call cannot use `" + banned0.symbol + "` defined within the T{...} block"
76-
// )
77-
// }
78-
// val tempName = c.freshName(TermName("tmp"))
79-
// val tempSym = c.internal.newTermSymbol(c.internal.enclosingOwner, tempName)
80-
// c.internal.setInfo(tempSym, t.tpe)
81-
// val tempIdent = Ident(tempSym)
82-
// c.internal.setType(tempIdent, t.tpe)
83-
// c.internal.setFlag(tempSym, (1L << 44).asInstanceOf[c.universe.FlagSet])
84-
// val itemsIdent = Ident(itemsSym)
85-
// exprs.append(q"$fun")
86-
// c.typecheck(q"$itemsIdent(${exprs.size - 1}).asInstanceOf[${t.tpe}]")
87-
// case (t, api)
88-
// if t.symbol != null
89-
// && t.symbol.annotations.exists(_.tree.tpe =:= typeOf[mill.api.Ctx.ImplicitStub]) =>
90-
// val tempIdent = Ident(ctxSym)
91-
// c.internal.setType(tempIdent, t.tpe)
92-
// c.internal.setFlag(ctxSym, (1L << 44).asInstanceOf[c.universe.FlagSet])
93-
// tempIdent
94-
95-
// case (t, api) => api.default(t)
96-
// }
97-
98-
// val ctxBinding = c.internal.valDef(ctxSym)
99-
100-
// val itemsBinding = c.internal.valDef(itemsSym)
101-
// val callback = c.typecheck(q"{(${itemsBinding}, ${ctxBinding}) => $transformed}")
102-
103-
// val res =
104-
// q"${c.prefix}.traverseCtx[_root_.scala.Any, ${weakTypeOf[T]}](${exprs.toList}){ $callback }"
105-
106-
// c.internal.changeOwner(transformed, c.internal.enclosingOwner, callback.symbol)
107-
108-
// c.Expr[M[T]](res)
48+
def impl0[M[_]: Type, Q[_]: Type, Z[_]: Type, T: Type, Ctx: Type](using Quotes)(caller: quotes.reflect.Tree, t: quotes.reflect.Tree): Expr[M[T]] = {
49+
import quotes.reflect.*
50+
51+
val targetApplySym = TypeRepr.of[Applyable[Nothing, ?]].typeSymbol.methodMember("apply").head
52+
53+
// Derived from @olafurpg's
54+
// https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608
55+
56+
def extractDefs(tree: Tree): Set[Symbol] =
57+
new TreeAccumulator[Set[Symbol]] {
58+
override def foldTree(x: Set[Symbol], tree: Tree)(owner: Symbol): Set[Symbol] = tree match
59+
case tree: Definition => foldOverTree(x + tree.symbol, tree)(owner)
60+
case tree => foldOverTree(x, tree)(owner)
61+
}.foldTree(Set.empty, tree)(Symbol.spliceOwner)
62+
63+
def visitAllTrees(tree: Tree)(f: Tree => Unit): Unit =
64+
new TreeTraverser {
65+
override def traverseTree(tree: Tree)(owner: Symbol): Unit =
66+
f(tree)
67+
traverseTreeChildren(tree)(owner)
68+
}.traverseTree(tree)(Symbol.spliceOwner)
69+
70+
val defs = extractDefs(t)
71+
72+
var hasErrors = false
73+
74+
def transformed(itemsRef: Expr[IndexedSeq[Any]], ctxRef: Expr[Ctx]): (Expr[Z[T]], Expr[List[Q[Any]]]) = {
75+
val exprs = collection.mutable.Buffer.empty[Tree]
76+
val treeMap = new TreeMap {
77+
78+
override def transformTerm(tree: Term)(owner: Symbol): Term = tree match
79+
// case t @ '{$fun.apply()($handler)}
80+
case t @ Apply(Apply(sel @ Select(fun, "apply"), Nil), List(handler)) if sel.symbol == targetApplySym =>
81+
val localDefs = extractDefs(fun)
82+
visitAllTrees(t){ x =>
83+
val sym = x.symbol
84+
if (sym != Symbol.noSymbol && defs(sym) && !localDefs(sym)) {
85+
hasErrors = true
86+
report.error(
87+
"Target#apply() call cannot use `" + x.symbol + "` defined within the T{...} block",
88+
x.pos
89+
)
90+
}
91+
}
92+
93+
t.tpe.asType match
94+
case '[tt] =>
95+
// val tempName = c.freshName(TermName("tmp"))
96+
// val tempSym = c.internal.newTermSymbol(c.internal.enclosingOwner, tempName)
97+
// c.internal.setInfo(tempSym, t.tpe)
98+
// val tempIdent = Ident(tempSym)
99+
// c.internal.setType(tempIdent, t.tpe)
100+
// c.internal.setFlag(tempSym, (1L << 44).asInstanceOf[c.universe.FlagSet])
101+
// val itemsIdent = Ident(itemsSym)
102+
// exprs.append(q"$fun")
103+
exprs += fun
104+
'{$itemsRef(${Expr(exprs.size - 1)}).asInstanceOf[tt]}.asTerm
105+
case t if t.symbol.exists
106+
&& t.symbol.annotations.exists(_.tpe =:= TypeRepr.of[mill.api.Ctx.ImplicitStub]) =>
107+
// val tempIdent = Ident(ctxSym)
108+
// c.internal.setType(tempIdent, t.tpe)
109+
// c.internal.setFlag(ctxSym, (1L << 44).asInstanceOf[c.universe.FlagSet])
110+
// tempIdent
111+
ctxRef.asTerm
112+
113+
case t => super.transformTerm(t)(owner)
114+
end transformTerm
115+
}
116+
117+
val newBody = treeMap.transformTree(t)(Symbol.spliceOwner).asExprOf[Z[T]]
118+
val exprsList = Expr.ofList(exprs.toList.map(_.asExprOf[Q[Any]]))
119+
(newBody, exprsList)
120+
}
121+
122+
val callerExpr = caller.asExprOf[Applyer[Q, M, Z, Ctx]]
123+
124+
val (callback, exprsList) = {
125+
var exprsExpr: Expr[List[Q[Any]]] | Null = null
126+
val cb = '{ (items: IndexedSeq[Any], ctx: Ctx) =>
127+
${
128+
val (body, exprs) = transformed('items, 'ctx)
129+
exprsExpr = exprs
130+
body
131+
}
132+
}
133+
(cb, exprsExpr.nn)
134+
}
135+
136+
if hasErrors then
137+
'{throw new RuntimeException("stub implementation - macro expansion had errors")}
138+
else
139+
'{${callerExpr}.traverseCtx[Any, T]($exprsList)($callback)}
109140
}
110141

111142
}

0 commit comments

Comments
 (0)