mirror of https://github.com/sbt/sbt.git
**problem**
.previous was implemented as an inline expansion to wrapInitTask(Previous.runtime(in)(<instance of JsonFormat[A1]))
For example, the .previous on taskKey[String] gets inlined like
this, and then sbt's macro moves `Previous.runtime(...)(using StringJsonFormat)` into task input.
lazy val fingerprints = taskKey[String]("...")
val previousFingerprints = fingerprints.previous
// inlined
val previousFingerprints: Option[String] = {
InputWrapper.wrapInitTask(
Previous.runtime(...)(using StringJsonFormat)
)
}
- 6c8ee6ea37/main-settings/src/main/scala/sbt/Def.scala (L410-L412)
- 6c8ee6ea37/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala (L468)
However, if the return type of task is a bit more complicated like
Seq[String], it doesn't work:
Scala 3 Compiler's inliner creates a synthetic proxy symbol if the
inlining tree is an application, whose arguments are unstable path.
For example,
lazy val fingerprints = taskKey[Seq[String]]("...")
val previousFingerprints = fingerprints.previous
// inline to
val previousFingerprints: Option[Seq[String]] = {
val x$2$proxy1 = immSeqFormat(StringJsonFormat)
InputWrapper.wrapInitTask(
Previous.runtime(...)(using x$2$proxy1)
)
}
cc7d6db700/compiler/src/dotty/tools/dotc/inlines/Inliner.scala (L324-L329)
However, sbt2's Cont macro captures only `Previous.runtime(...)(using x$2$proxy1)`, while it doesn't capture the proxy definition. Consequently, while sbt macro moves the `Previous.runtime(...)` application as a task input, the proxy definition is left in the task body.
mapN(
(
link / fingerprints,
Previous.runtime(...)(using x$2$proxy1) // here x$2$proxy1 can't be found
)
) {
...
val x$2$proxy1 = ...
}
Then we get:
-- Error: /.../build.sbt:14:59
14 | val previousFingerprints = (link / fingerprints).previous
| ^
|While expanding a macro, a reference to value x$2$proxy1 was used outside the scope where it was defined
**How this PR fixed**
This commit fixes the problem by defining a dedicated Scala3 macro for .previous that summon JsonFormat[A1] inside the macro before constructing the wrapped previous.runtime(...)(using ...) by inliner. The macro insert the found given value tree directly into the previous.runtime(...)(using $found).
This way, Cont macro always moves the Previous.runtime tree along with it's given argument, without leaking compiler-generated inline proxies across scopes.
Co-authored-by: Rikito Taniguchi <rikitotaniguchi@proton.me>
This commit is contained in:
parent
879ef86567
commit
53b36840bb
|
|
@ -404,8 +404,16 @@ object Def extends BuildSyntax with Init with InitializeImplicits:
|
|||
|
||||
extension [A1](inline in: TaskKey[A1])
|
||||
// implicit def macroPrevious[T](@deprecated("unused", "") in: TaskKey[T]): MacroPrevious[T] = ???
|
||||
inline def previous(using JsonFormat[A1]): Option[A1] =
|
||||
InputWrapper.`wrapInitTask_\u2603\u2603`[Option[A1]](Previous.runtime[A1](in))
|
||||
// Previously, we implemented `.previous` as a plain inline expansion of
|
||||
// `wrapInitTask(Previous.runtime(in))`. For composite types such as `Seq[String]`, the
|
||||
// compiler can rewrite the format argument into a synthetic local like
|
||||
// `{ val x$2$proxy1 = immSeqFormat(StringJsonFormat); Previous.runtime(in)(using x$2$proxy1) }`.
|
||||
// The outer task macro later hoists only `Previous.runtime(...)` as an input dependency,
|
||||
// leaving `x$2$proxy1` behind and causing "used outside the scope where it was defined".
|
||||
// Now we implement `.previous` as a macro so the wrapped tree is already self-contained.
|
||||
// See https://github.com/sbt/sbt/issues/9037.
|
||||
inline def previous(using @scala.annotation.unused format: JsonFormat[A1]): Option[A1] =
|
||||
${ TaskMacro.previousImpl[A1]('in) }
|
||||
|
||||
// The following conversions enable the types Parser[T], Initialize[Parser[T]], and
|
||||
// Initialize[State => Parser[T]] to be used in the inputTask macro as an input with an ultimate
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import sbt.internal.util.{ SourcePosition, SourcePositionImpl }
|
|||
import language.experimental.macros
|
||||
import scala.quoted.*
|
||||
import sbt.util.BuildWideCacheConfiguration
|
||||
import sjsonnew.JsonFormat
|
||||
|
||||
object TaskMacro:
|
||||
final val AssignInitName = "set"
|
||||
|
|
@ -120,6 +121,15 @@ object TaskMacro:
|
|||
val convert1 = new FullConvert(qctx, 1000)
|
||||
convert1.contFlatMap[A1, F, Id](t, convert1.appExpr, None)
|
||||
|
||||
def previousImpl[A1: Type](t: Expr[TaskKey[A1]])(using qctx: Quotes): Expr[Option[A1]] =
|
||||
import qctx.reflect.*
|
||||
Expr.summon[JsonFormat[A1]] match
|
||||
case Some(ev) =>
|
||||
'{
|
||||
InputWrapper.`wrapInitTask_\u2603\u2603`[Option[A1]](Previous.runtime[A1]($t)(using $ev))
|
||||
}
|
||||
case _ => report.errorAndAbort(s"JsonFormat[${Type.show[A1]}] missing")
|
||||
|
||||
/** Implementation of := macro for settings. */
|
||||
def settingAssignMacroImpl[A1: Type](rec: Expr[Scoped.DefinableSetting[A1]], v: Expr[A1])(using
|
||||
qctx: Quotes
|
||||
|
|
|
|||
|
|
@ -106,6 +106,23 @@ class TaskPosSpec {
|
|||
}
|
||||
}
|
||||
|
||||
locally {
|
||||
// .previous should compile for task with complex return type like `Seq[String]`
|
||||
// https://github.com/sbt/sbt/issues/9037
|
||||
import sbt.*, Def.*
|
||||
import sjsonnew.BasicJsonProtocol.given
|
||||
val link = taskKey[Int]("")
|
||||
val fingerprints = taskKey[Seq[String]]("")
|
||||
Def.taskDyn[Int] {
|
||||
val currentFingerprints = (link / fingerprints).value
|
||||
val previousFingerprints = (link / fingerprints).previous
|
||||
Def.task {
|
||||
if previousFingerprints.exists(_ != currentFingerprints) then currentFingerprints.size
|
||||
else 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locally {
|
||||
// missing .value error should not happen inside task dyn
|
||||
import sbt.*, Def.*
|
||||
|
|
|
|||
Loading…
Reference in New Issue