summaryrefslogtreecommitdiff
path: root/main/core/src/define/Applicative.scala
diff options
context:
space:
mode:
Diffstat (limited to 'main/core/src/define/Applicative.scala')
-rw-r--r--main/core/src/define/Applicative.scala108
1 files changed, 108 insertions, 0 deletions
diff --git a/main/core/src/define/Applicative.scala b/main/core/src/define/Applicative.scala
new file mode 100644
index 00000000..5e63b1cc
--- /dev/null
+++ b/main/core/src/define/Applicative.scala
@@ -0,0 +1,108 @@
+package mill.define
+
+import scala.annotation.{StaticAnnotation, compileTimeOnly}
+import scala.language.higherKinds
+import scala.reflect.macros.blackbox.Context
+
+/**
+ * A generic Applicative-functor macro: translates calls to
+ *
+ * Applier.apply{ ... applyable1.apply() ... applyable2.apply() ... }
+ *
+ * into
+ *
+ * Applier.zipMap(applyable1, applyable2){ (a1, a2, ctx) => ... a1 ... a2 ... }
+ */
+object Applicative {
+ trait ApplyHandler[M[+_]]{
+ def apply[T](t: M[T]): T
+ }
+ object ApplyHandler{
+ @compileTimeOnly("Target#apply() can only be used with a T{...} block")
+ implicit def defaultApplyHandler[M[+_]]: ApplyHandler[M] = ???
+ }
+ trait Applyable[M[+_], +T]{
+ def self: M[T]
+ def apply()(implicit handler: ApplyHandler[M]): T = handler(self)
+ }
+
+ type Id[+T] = T
+
+ trait Applyer[W[_], T[_], Z[_], Ctx] extends ApplyerGenerated[T, Z, Ctx] {
+ def ctx()(implicit c: Ctx) = c
+ def underlying[A](v: W[A]): T[_]
+
+ def zipMap[R]()(cb: Ctx => Z[R]) = mapCtx(zip()){ (_, ctx) => cb(ctx)}
+ def zipMap[A, R](a: T[A])(f: (A, Ctx) => Z[R]) = mapCtx(a)(f)
+ def zip(): T[Unit]
+ def zip[A](a: T[A]): T[Tuple1[A]]
+ }
+
+ def impl[M[_], T: c.WeakTypeTag, Ctx: c.WeakTypeTag](c: Context)
+ (t: c.Expr[T]): c.Expr[M[T]] = {
+ impl0(c)(t.tree)(implicitly[c.WeakTypeTag[T]], implicitly[c.WeakTypeTag[Ctx]])
+ }
+ def impl0[M[_], T: c.WeakTypeTag, Ctx: c.WeakTypeTag](c: Context)
+ (t: c.Tree): c.Expr[M[T]] = {
+ import c.universe._
+ def rec(t: Tree): Iterator[c.Tree] = Iterator(t) ++ t.children.flatMap(rec(_))
+
+ val bound = collection.mutable.Buffer.empty[(c.Tree, ValDef)]
+ val targetApplySym = typeOf[Applyable[Nothing, _]].member(TermName("apply"))
+
+ // Derived from @olafurpg's
+ // https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608
+ val defs = rec(t).filter(_.isDef).map(_.symbol).toSet
+
+ val ctxName = TermName(c.freshName("ctx"))
+ val ctxSym = c.internal.newTermSymbol(c.internal.enclosingOwner, ctxName)
+ c.internal.setInfo(ctxSym, weakTypeOf[Ctx])
+
+ val transformed = c.internal.typingTransform(t) {
+ case (t @ q"$fun.apply()($handler)", api) if t.symbol == targetApplySym =>
+
+ val localDefs = rec(fun).filter(_.isDef).map(_.symbol).toSet
+ val banned = rec(t).filter(x => defs(x.symbol) && !localDefs(x.symbol))
+
+ if (banned.hasNext){
+ val banned0 = banned.next()
+ c.abort(
+ banned0.pos,
+ "Target#apply() call cannot use `" + banned0.symbol + "` defined within the T{...} block"
+ )
+ }
+ val tempName = c.freshName(TermName("tmp"))
+ val tempSym = c.internal.newTermSymbol(c.internal.enclosingOwner, tempName)
+ c.internal.setInfo(tempSym, t.tpe)
+ val tempIdent = Ident(tempSym)
+ c.internal.setType(tempIdent, t.tpe)
+ c.internal.setFlag(tempSym, (1L << 44).asInstanceOf[c.universe.FlagSet])
+ bound.append((q"${c.prefix}.underlying($fun)", c.internal.valDef(tempSym)))
+ tempIdent
+ case (t, api)
+ if t.symbol != null
+ && t.symbol.annotations.exists(_.tree.tpe =:= typeOf[mill.api.Ctx.ImplicitStub]) =>
+
+ val tempIdent = Ident(ctxSym)
+ c.internal.setType(tempIdent, t.tpe)
+ c.internal.setFlag(ctxSym, (1L << 44).asInstanceOf[c.universe.FlagSet])
+ tempIdent
+
+ case (t, api) => api.default(t)
+ }
+
+ val (exprs, bindings) = bound.unzip
+
+
+ val ctxBinding = c.internal.valDef(ctxSym)
+
+ val callback = c.typecheck(q"(..$bindings, $ctxBinding) => $transformed ")
+
+ val res = q"${c.prefix}.zipMap(..$exprs){ $callback }"
+
+ c.internal.changeOwner(transformed, c.internal.enclosingOwner, callback.symbol)
+
+ c.Expr[M[T]](res)
+ }
+
+}