Skip to content

Commit

Permalink
Start Def.declareOutput
Browse files Browse the repository at this point in the history
  • Loading branch information
eed3si9n committed Oct 30, 2023
1 parent 1e8da3d commit eed7553
Show file tree
Hide file tree
Showing 14 changed files with 280 additions and 112 deletions.
80 changes: 54 additions & 26 deletions core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import scala.collection.mutable.ListBuffer
import scala.reflect.{ ClassTag, TypeTest }
import scala.quoted.*
import sjsonnew.{ BasicJsonProtocol, HashWriter, JsonFormat }
import sbt.util.{ ActionCache, ActionCacheStore }
import sbt.util.{ ActionCache, ActionCacheStore, CacheConfiguration }
import sbt.util.Applicative
import sbt.util.Monad
import xsbti.VirtualFile
import Types.Id

/**
Expand All @@ -27,12 +28,12 @@ trait Cont:
def contMapN[A: Type, F[_], Effect[_]: Type](
tree: Expr[A],
applicativeExpr: Expr[Applicative[F]],
storeExpr: Option[Expr[ActionCacheStore]],
cacheConfigExpr: Option[Expr[CacheConfiguration]],
)(using
iftpe: Type[F],
eatpe: Type[Effect[A]],
): Expr[F[Effect[A]]] =
contMapN[A, F, Effect](tree, applicativeExpr, storeExpr, conv.idTransform)
contMapN[A, F, Effect](tree, applicativeExpr, cacheConfigExpr, conv.idTransform)

/**
* Implementation of a macro that provides a direct syntax for applicative functors. It is
Expand All @@ -41,13 +42,13 @@ trait Cont:
def contMapN[A: Type, F[_], Effect[_]: Type](
tree: Expr[A],
applicativeExpr: Expr[Applicative[F]],
storeExpr: Option[Expr[ActionCacheStore]],
cacheConfigExpr: Option[Expr[CacheConfiguration]],
inner: conv.TermTransform[Effect]
)(using
iftpe: Type[F],
eatpe: Type[Effect[A]],
): Expr[F[Effect[A]]] =
contImpl[A, F, Effect](Left(tree), applicativeExpr, storeExpr, inner)
contImpl[A, F, Effect](Left(tree), applicativeExpr, cacheConfigExpr, inner)

/**
* Implementation of a macro that provides a direct syntax for applicative functors. It is
Expand All @@ -56,12 +57,12 @@ trait Cont:
def contFlatMap[A: Type, F[_], Effect[_]: Type](
tree: Expr[F[A]],
applicativeExpr: Expr[Applicative[F]],
storeExpr: Option[Expr[ActionCacheStore]],
cacheConfigExpr: Option[Expr[CacheConfiguration]],
)(using
iftpe: Type[F],
eatpe: Type[Effect[A]],
): Expr[F[Effect[A]]] =
contFlatMap[A, F, Effect](tree, applicativeExpr, storeExpr, conv.idTransform)
contFlatMap[A, F, Effect](tree, applicativeExpr, cacheConfigExpr, conv.idTransform)

/**
* Implementation of a macro that provides a direct syntax for applicative functors. It is
Expand All @@ -70,13 +71,13 @@ trait Cont:
def contFlatMap[A: Type, F[_], Effect[_]: Type](
tree: Expr[F[A]],
applicativeExpr: Expr[Applicative[F]],
storeExpr: Option[Expr[ActionCacheStore]],
cacheConfigExpr: Option[Expr[CacheConfiguration]],
inner: conv.TermTransform[Effect]
)(using
iftpe: Type[F],
eatpe: Type[Effect[A]],
): Expr[F[Effect[A]]] =
contImpl[A, F, Effect](Right(tree), applicativeExpr, storeExpr, inner)
contImpl[A, F, Effect](Right(tree), applicativeExpr, cacheConfigExpr, inner)

def summonAppExpr[F[_]: Type]: Expr[Applicative[F]] =
import conv.qctx
Expand Down Expand Up @@ -150,7 +151,7 @@ trait Cont:
def contImpl[A: Type, F[_], Effect[_]: Type](
eitherTree: Either[Expr[A], Expr[F[A]]],
applicativeExpr: Expr[Applicative[F]],
storeExprOpt: Option[Expr[ActionCacheStore]],
cacheConfigExprOpt: Option[Expr[CacheConfiguration]],
inner: conv.TermTransform[Effect]
)(using
iftpe: Type[F],
Expand All @@ -167,29 +168,46 @@ trait Cont:
case Right(r) => (r, faTpe)

val inputBuf = ListBuffer[Input]()
val outputBuf = ListBuffer[Output]()

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)

// wrap body in between output var declarations and var references
def letOutput[A1: Type](outputs: List[Output])(body: Expr[A1]): Expr[(A1, Seq[VirtualFile])] =
Block(
outputs.map(_.toVarDef),
'{
(
$body,
List(${ Varargs[VirtualFile](outputs.map(_.toRef.asExprOf[VirtualFile])) }: _*)
)
}.asTerm
).asExprOf[(A1, Seq[VirtualFile])]

// 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]] =
storeExprOpt match
case Some(storeExpr) =>
cacheConfigExprOpt match
case Some(cacheConfigExpr) =>
val codeContentHash = Expr[Long](body.show.##)
val aJsonFormat = summonJsonFormat[A1]
val aClassTag = summonClassTag[A1]
val outputs = outputBuf.toList
val block = letOutput(outputBuf.toList)(body)
'{
import BasicJsonProtocol.given
// given HashWriter[Unit] = $inputHashWriter
given JsonFormat[A1] = $aJsonFormat
given ClassTag[A1] = $aClassTag
val x = ActionCache.cache((), $codeContentHash)({ _ =>
($body, Nil)
})($storeExpr)
$applicativeExpr.pure[A1] { () => x.value }
$applicativeExpr.pure[A1] { () =>
val x = ActionCache.cache((), $codeContentHash)({ _ =>
$block
})($cacheConfigExpr)
x.value
}
}
case None =>
'{
Expand Down Expand Up @@ -234,21 +252,22 @@ trait Cont:
}
val modifiedBody =
transformWrappers(body.asTerm.changeOwner(sym), substitute, sym).asExprOf[A1]
storeExprOpt match
case Some(storesExpr) =>
cacheConfigExprOpt match
case Some(cacheConfigExpr) =>
val codeContentHash = Expr[Long](modifiedBody.##)
val paramRef = Ref(param.symbol).asExprOf[a]
val inputHashWriter = summonHashWriter[a]
val aJsonFormat = summonJsonFormat[A1]
val aClassTag = summonClassTag[A1]
val block = letOutput(outputBuf.toList)(modifiedBody)
'{
given HashWriter[a] = $inputHashWriter
given JsonFormat[A1] = $aJsonFormat
given ClassTag[A1] = $aClassTag
ActionCache
.cache($paramRef, $codeContentHash)({ _ =>
($modifiedBody, Nil)
})($storesExpr)
$block
})($cacheConfigExpr)
.value
}.asTerm.changeOwner(sym)
case None => modifiedBody.asTerm
Expand Down Expand Up @@ -295,21 +314,22 @@ trait Cont:
}
val modifiedBody =
transformWrappers(body.asTerm.changeOwner(sym), substitute, sym).asExprOf[A1]
storeExprOpt match
case Some(storeExpr) =>
cacheConfigExprOpt match
case Some(cacheConfigExpr) =>
val codeContentHash = Expr[Long](modifiedBody.##)
val p0Ref = Ref(p0.symbol).asExprOf[inputTypeTpe]
val inputHashWriter = summonHashWriter[inputTypeTpe]
val aJsonFormat = summonJsonFormat[A1]
val aClassTag = summonClassTag[A1]
val block = letOutput(outputBuf.toList)(modifiedBody)
'{
given HashWriter[inputTypeTpe] = $inputHashWriter
given JsonFormat[A1] = $aJsonFormat
given ClassTag[A1] = $aClassTag
ActionCache
.cache($p0Ref, $codeContentHash)({ _ =>
($modifiedBody, Nil)
})($storeExpr)
$block
})($cacheConfigExpr)
.value
}.asTerm.changeOwner(sym)
case None =>
Expand Down Expand Up @@ -338,14 +358,22 @@ trait Cont:
case Right(_) =>
flatten(genMapN0[F[Effect[A]]](inner(body).asExprOf[F[Effect[A]]]))

val WrapOutputName = "wrapOutput_\u2603\u2603"
// Called when transforming the tree to add an input.
// For `qual` of type F[A], and a `selection` qual.value.
val record = [a] =>
(name: String, tpe: Type[a], qual: Term, oldTree: Term) =>
given t: Type[a] = tpe
convert[a](name, qual) transform { (replacement: Term) =>
inputBuf += Input(TypeRepr.of[a], qual, replacement, freshName("q"))
oldTree
if name != WrapOutputName then
inputBuf += Input(TypeRepr.of[a], qual, replacement, freshName("q"))
oldTree
else
val output = Output(TypeRepr.of[a], qual, freshName("o"), Symbol.spliceOwner)
outputBuf += output
if cacheConfigExprOpt.isDefined then output.toAssign
else oldTree
end if
}
val tx = transformWrappers(expr.asTerm, record, Symbol.spliceOwner)
val tr = makeApp(tx, inputBuf.toList)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val qctx: C, val valStart: Int)
counter = counter + 1
s"$$${prefix}${counter}"

/**
* Constructs a new, synthetic, local var with type `tpe`, a unique name, initialized to
* zero-equivalent (Zero[A]), and owned by `parent`.
*/
def freshValDef(parent: Symbol, tpe: TypeRepr, rhs: Term): ValDef =
tpe.asType match
case '[a] =>
Expand Down Expand Up @@ -61,6 +57,33 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val qctx: C, val valStart: Int)
override def toString: String =
s"Input($tpe, $qual, $term, $name)"

/**
* Represents an output expression via Def.declareOutput
*/
final class Output(
val tpe: TypeRepr,
val term: Term,
val name: String,
val parent: Symbol,
):
override def toString: String =
s"Output($tpe, $term, $name)"
val placeholder: Symbol =
tpe.asType match
case '[a] =>
Symbol.newVal(
parent,
name,
tpe,
Flags.Mutable,
Symbol.noSymbol
)
def toVarDef: ValDef =
ValDef(placeholder, rhs = Some('{ null }.asTerm))
def toAssign: Term = Assign(toRef, term)
def toRef: Ref = Ref(placeholder)
end Output

trait TermTransform[F[_]]:
def apply(in: Term): Term
end TermTransform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object ContTestMacro:
convert1.contMapN[A, List, Id](
expr,
convert1.summonAppExpr[List],
'{ Seq(inMemoryCache: ActionCacheStore) },
None,
convert1.idTransform
)

Expand Down
10 changes: 10 additions & 0 deletions main-settings/src/main/scala/sbt/Def.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package sbt

import java.io.File
import java.nio.file.Path
import java.net.URI

import scala.annotation.compileTimeOnly
Expand Down Expand Up @@ -230,8 +231,13 @@ object Def extends Init[Scope] with TaskMacroExtra with InitializeImplicits:

import language.experimental.macros

// These are here, as opposed to RemoteCahe, since we need them from TaskMacro etc
private[sbt] var _cacheStore: ActionCacheStore = AggregateActionCacheStore.empty
def cacheStore: ActionCacheStore = _cacheStore
private[sbt] var _outputDirectory: Option[Path] = None
def outputDirectoryForCache: Path = _outputDirectory match
case Some(dir) => dir
case None => sys.error("outputDirectoryForCache has not been set")

inline def cachedTask[A1: JsonFormat](inline a1: A1): Def.Initialize[Task[A1]] =
${ TaskMacro.taskMacroImpl[A1]('a1, cached = true) }
Expand Down Expand Up @@ -296,6 +302,10 @@ object Def extends Init[Scope] with TaskMacroExtra with InitializeImplicits:
*/
def promise[A]: PromiseWrap[A] = new PromiseWrap[A]()

inline def declareOutput(inline vf: VirtualFile): Unit =
InputWrapper.`wrapOutput_\u2603\u2603`[VirtualFile](vf)


// The following conversions enable the types Initialize[T], Initialize[Task[T]], and Task[T] to
// be used in task and setting macros as inputs with an ultimate result of type T

Expand Down
1 change: 1 addition & 0 deletions main-settings/src/main/scala/sbt/std/InputConvert.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class FullConvert[C <: Quotes & scala.Singleton](override val qctx: C, valStart:
case InputWrapper.WrapPreviousName => Converted.success(in)
case InputWrapper.WrapInitName => wrapInit[A](in)
case InputWrapper.WrapTaskName => wrapTask[A](in)
case InputWrapper.WrapOutputName => Converted.success(in)
case _ => Converted.NotApplicable()

private def wrapInit[A: Type](tree: Term): Converted =
Expand Down
6 changes: 6 additions & 0 deletions main-settings/src/main/scala/sbt/std/InputWrapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ object InputWrapper:

private[std] final val WrapTaskName = "wrapTask_\u2603\u2603"
private[std] final val WrapInitName = "wrapInit_\u2603\u2603"
private[std] final val WrapOutputName = "wrapOutput_\u2603\u2603"
private[std] final val WrapInitTaskName = "wrapInitTask_\u2603\u2603"
private[std] final val WrapInitInputName = "wrapInitInputTask_\u2603\u2603"
private[std] final val WrapInputName = "wrapInputTask_\u2603\u2603"
Expand All @@ -41,6 +42,11 @@ object InputWrapper:
)
def `wrapInit_\u2603\u2603`[T](@deprecated("unused", "") in: Any): T = implDetailError

@compileTimeOnly(
"`declareOutput` can only be used within a task macro, such as Def.cachedTask."
)
def `wrapOutput_\u2603\u2603`[A](@deprecated("unused", "") in: Any): A = implDetailError

@compileTimeOnly(
"`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task."
)
Expand Down
7 changes: 4 additions & 3 deletions main-settings/src/main/scala/sbt/std/TaskMacro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import scala.annotation.tailrec
import scala.reflect.internal.util.UndefinedPosition
import scala.quoted.*
import sjsonnew.{ BasicJsonProtocol, JsonFormat }
import sbt.util.CacheConfiguration

object TaskMacro:
final val AssignInitName = "set"
Expand Down Expand Up @@ -60,10 +61,10 @@ object TaskMacro:
case '{ if ($cond) then $thenp else $elsep } => taskIfImpl[A1](t, cached)
case _ =>
val convert1 = new FullConvert(qctx, 0)
val storeExpr =
if cached then Some('{ Def.cacheStore })
val cacheConfigExpr =
if cached then Some('{ CacheConfiguration(Def.cacheStore, Def.outputDirectoryForCache) })
else None
convert1.contMapN[A1, F, Id](t, convert1.appExpr, storeExpr)
convert1.contMapN[A1, F, Id](t, convert1.appExpr, cacheConfigExpr)

def taskIfImpl[A1: Type](expr: Expr[A1], cached: Boolean)(using
qctx: Quotes
Expand Down
Loading

0 comments on commit eed7553

Please sign in to comment.