From 2a6385fd941c0b95ace351648522ac641aa64874 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 6 Oct 2017 15:47:46 -0400 Subject: [PATCH] Remove thunk for slash syntax Ref #3606, #3611, and #3613 This removes unnecessary thunk for slash syntax. The semantics using this approach is strictly better than the previous `in (ref, config, task)`. By removing the thunk, we retain `(a / b) / c == a / b / c`. See the following example: ```scala scala> import sbt._, Keys._ scala> val t: TaskKey[Unit] = (test in Test) t: sbt.TaskKey[Unit] = TaskKey(This / Select(ConfigKey(test)) / This / test) scala> ThisBuild / t ThisBuild / t res1: sbt.TaskKey[Unit] = TaskKey(Select(ThisBuild) / Select(ConfigKey(test)) / This / test) scala> ThisBuild / t / name ThisBuild / t / name res2: sbt.SettingKey[String] = SettingKey(Select(ThisBuild) / Select(ConfigKey(test)) / Select(test) / name) ``` so far so good? Now look at this: ``` scala> scala> name in (ThisBuild, t) name in (ThisBuild, t) res3: sbt.SettingKey[String] = SettingKey(Select(ThisBuild) / This / Select(test) / name) ``` `Test` configuration knowledge is lost! For `in (..)` maybe it was ok because mostly we don't use unscoped keys, but that's the difference between `in (..)` and `/`. Fixes #3605 --- .../src/main/scala/sbt/SlashSyntax.scala | 70 +++---- .../src/test/scala/sbt/SlashSyntaxSpec.scala | 193 +++++++++--------- sbt/src/sbt-test/project/unified/build.sbt | 21 ++ 3 files changed, 147 insertions(+), 137 deletions(-) diff --git a/main-settings/src/main/scala/sbt/SlashSyntax.scala b/main-settings/src/main/scala/sbt/SlashSyntax.scala index fc98cfc39..9b1898a05 100644 --- a/main-settings/src/main/scala/sbt/SlashSyntax.scala +++ b/main-settings/src/main/scala/sbt/SlashSyntax.scala @@ -39,23 +39,31 @@ trait SlashSyntax { implicit def sbtSlashSyntaxRichConfigKey(c: ConfigKey): RichConfiguration = new RichConfiguration(Scope(This, Select(c), This, This)) - - implicit def sbtSlashSyntaxRichConfiguration(c: Configuration): RichConfiguration = (c: ConfigKey) - - implicit def sbtSlashSyntaxRichScopeFromScoped(t: Scoped): RichScope = - new RichScope(Scope(This, This, Select(t.key), This)) + implicit def sbtSlashSyntaxRichConfiguration(c: Configuration): RichConfiguration = + sbtSlashSyntaxRichConfigKey(c: ConfigKey) implicit def sbtSlashSyntaxRichScope(s: Scope): RichScope = new RichScope(s) - implicit def sbtSlashSyntaxScopeAndKeyRescope(scopeAndKey: ScopeAndKey[_]): TerminalScope = - scopeAndKey.rescope + /** + * This handles task scoping an existing scoped key (such as `Compile / test`) + * into a task scoping in `(Compile / test) / name`. + */ + implicit def sbtSlashSyntaxRichScopeFromScoped(t: Scoped): RichScope = + new RichScope(t.scope.copy(task = Select(t.key))) - implicit def sbtSlashSyntaxScopeAndKeyMaterialize[K <: Key[K]](scopeAndKey: ScopeAndKey[K]): K = - scopeAndKey.materialize + implicit val sbtSlashSyntaxSettingKeyCanScope: CanScope[SettingKey] = new SettingKeyCanScope() + implicit val sbtSlashSyntaxTaskKeyCanScope: CanScope[TaskKey] = new TaskKeyCanScope() + implicit val sbtSlashSyntaxInputKeyCanScope: CanScope[InputKey] = new InputKeyCanScope() } object SlashSyntax { + /** RichScopeLike wraps a general scope to provide the `/` operator for key scoping. */ + sealed trait RichScopeLike { + protected def scope: Scope + def /[A, F[_]: CanScope](key: F[A]): F[A] = implicitly[CanScope[F]].inScope(key, scope) + } + /** RichReference wraps a reference to provide the `/` operator for scoping. */ final class RichReference(protected val scope: Scope) extends RichScopeLike { def /(c: ConfigKey): RichConfiguration = new RichConfiguration(scope in c) @@ -68,46 +76,28 @@ object SlashSyntax { /** RichConfiguration wraps a configuration to provide the `/` operator for scoping. */ final class RichConfiguration(protected val scope: Scope) extends RichScopeLike { - // This is for handling `Zero / Zero / Zero / name`. def /(taskAxis: ScopeAxis[AttributeKey[_]]): RichScope = new RichScope(scope.copy(task = taskAxis)) } - /** Both `Scoped.ScopingSetting` and `Scoped` are parents of `SettingKey`, `TaskKey` and - * `InputKey`. We'll need both, so this is a convenient type alias. */ - type Key[K] = Scoped.ScopingSetting[K] with Scoped - - sealed trait RichScopeLike { - protected def scope: Scope - - // We don't know what the key is for yet, so just capture for now. - def /[K <: Key[K]](key: K): ScopeAndKey[K] = new ScopeAndKey(scope, key) - } - /** RichScope wraps a general scope to provide the `/` operator for scoping. */ - final class RichScope(protected val scope: Scope) extends RichScopeLike - - /** TerminalScope provides the last `/` for scoping. */ - final class TerminalScope(scope: Scope) { - def /[K <: Key[K]](key: K): K = key in scope - } + final class RichScope(protected val scope: Scope) extends RichScopeLike {} /** - * ScopeAndKey is a synthetic DSL construct necessary to capture both the built-up scope with a - * given key, while we're not sure if the given key is terminal or task-scoping. The "materialize" - * method will be used if it's terminal, returning the scoped key, while "rescope" will be used - * if we're task-scoping. - * - * @param scope the built-up scope - * @param key a given key - * @tparam K the type of the given key, necessary to type "materialize" + * A typeclass that represents scoping. */ - final class ScopeAndKey[K <: Key[K]](scope: Scope, key: K) { - private[sbt] def materialize: K = key in scope - private[sbt] def rescope: TerminalScope = new TerminalScope(scope in key.key) - - override def toString: String = s"$scope / ${key.key}" + sealed trait CanScope[F[A]] { + def inScope[A](key: F[A], scope: Scope): F[A] } + final class SettingKeyCanScope extends CanScope[SettingKey] { + def inScope[A](key: SettingKey[A], scope: Scope): SettingKey[A] = key in scope + } + final class TaskKeyCanScope extends CanScope[TaskKey] { + def inScope[A](key: TaskKey[A], scope: Scope): TaskKey[A] = key in scope + } + final class InputKeyCanScope extends CanScope[InputKey] { + def inScope[A](key: InputKey[A], scope: Scope): InputKey[A] = key in scope + } } diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index 8432a3f54..34f6b55d2 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -11,7 +11,7 @@ import org.scalacheck.{ Test => _, _ }, Arbitrary.arbitrary, Gen._, Prop._ import java.io.File import sbt.io.IO -import sbt.SlashSyntax, SlashSyntax.Key +import sbt.SlashSyntax import sbt.{ Scope, ScopeAxis, Scoped, Select, This, Zero }, Scope.{ Global, ThisScope } import sbt.{ BuildRef, LocalProject, LocalRootProject, ProjectRef, Reference, RootProject, ThisBuild, ThisProject } import sbt.ConfigKey @@ -63,21 +63,26 @@ object BuildDSLInstances { 1 -> (for (key <- keyGen; scope <- arbitrary[Scope]) yield key in scope) )) - implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = - withScope(Gen.identifier map (InputKey[A](_))) + object WithScope { + implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = + withScope(Gen.identifier map (InputKey[A](_))) - implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = - withScope(Gen.identifier map (SettingKey[A](_))) + implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = + withScope(Gen.identifier map (SettingKey[A](_))) - implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = - withScope(Gen.identifier map (TaskKey[A](_))) + implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = + withScope(Gen.identifier map (TaskKey[A](_))) + } - implicit def arbKey[A: Manifest]: Arbitrary[Key[_]] = Arbitrary { - Gen.frequency[Key[_]]( - 15 -> arbitrary[InputKey[A]], // 15,431 - 20 -> arbitrary[SettingKey[A]], // 19,645 - 23 -> arbitrary[TaskKey[A]], // 22,867 - ) + object WithoutScope { + implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = + Arbitrary(Gen.identifier map (InputKey[A](_))) + + implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = + Arbitrary(Gen.identifier map (SettingKey[A](_))) + + implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = + Arbitrary(Gen.identifier map (TaskKey[A](_))) } implicit def arbScopeAxis[A: Arbitrary]: Arbitrary[ScopeAxis[A]] = @@ -134,111 +139,105 @@ import CustomEquality._ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { property("Global / key == key in Global") = { - def check[K <: Key[K]: Arbitrary] = forAll((k: K) => expectValue(k in Global)(Global / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + import WithScope._ + (forAll { (k: SettingKey[String]) => expectValue(k in Global)(Global / k) } + && forAll { (k: TaskKey[String]) => expectValue(k in Global)(Global / k) } + && forAll { (k: InputKey[String]) => expectValue(k in Global)(Global / k) }) } + property("Reference / key == key in Reference") = { - def check[K <: Key[K]: Arbitrary] = forAll((r: Reference, k: K) => expectValue(k in r)(r / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + import WithScope._ + (forAll { (r: Reference, k: SettingKey[String]) => expectValue(k in r)(r / k) } + && forAll { (r: Reference, k: TaskKey[String]) => expectValue(k in r)(r / k) } + && forAll { (r: Reference, k: InputKey[String]) => expectValue(k in r)(r / k) }) } + property("Reference / Config / key == key in Reference in Config") = { - def check[K <: Key[K]: Arbitrary] = - forAll((r: Reference, c: ConfigKey, k: K) => expectValue(k in r in c)(r / c / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + import WithScope._ + (forAll { (r: Reference, c: ConfigKey, k: SettingKey[String]) => expectValue(k in r in c)(r / c / k) } + && forAll { (r: Reference, c: ConfigKey, k: TaskKey[String]) => expectValue(k in r in c)(r / c / k) } + && forAll { (r: Reference, c: ConfigKey, k: InputKey[String]) => expectValue(k in r in c)(r / c / k) }) } + property("Reference / task / key == key in Reference in task") = { - def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((r: Reference, t: K, k: K) => expectValue(k in (r, t))(r / t / k)) - (true - && check[InputKey[String], InputKey[String]] - && check[InputKey[String], SettingKey[String]] - && check[InputKey[String], TaskKey[String]] - && check[SettingKey[String], InputKey[String]] - && check[SettingKey[String], SettingKey[String]] - && check[SettingKey[String], TaskKey[String]] - && check[TaskKey[String], InputKey[String]] - && check[TaskKey[String], SettingKey[String]] - && check[TaskKey[String], TaskKey[String]] - ) + import WithoutScope._ + (forAll { (r: Reference, t: TaskKey[String], k: SettingKey[String]) => expectValue(k in (r, t))(r / t / k) } + && forAll { (r: Reference, t: TaskKey[String], k: TaskKey[String]) => expectValue(k in (r, t))(r / t / k) } + && forAll { (r: Reference, t: TaskKey[String], k: InputKey[String]) => expectValue(k in (r, t))(r / t / k) }) } + property("Reference / Config / task / key == key in Reference in Config in task") = { - def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((r: Reference, c: ConfigKey, t: K, k: K) => expectValue(k in (r, c, t))(r / c / t / k)) - (true - && check[InputKey[String], InputKey[String]] - && check[InputKey[String], SettingKey[String]] - && check[InputKey[String], TaskKey[String]] - && check[SettingKey[String], InputKey[String]] - && check[SettingKey[String], SettingKey[String]] - && check[SettingKey[String], TaskKey[String]] - && check[TaskKey[String], InputKey[String]] - && check[TaskKey[String], SettingKey[String]] - && check[TaskKey[String], TaskKey[String]] - ) + import WithoutScope._ + (forAll { (r: Reference, c: ConfigKey, t: TaskKey[String], k: SettingKey[String]) => expectValue(k in (r, c, t))(r / c / t / k) } + && forAll { (r: Reference, c: ConfigKey, t: TaskKey[String], k: TaskKey[String]) => expectValue(k in (r, c, t))(r / c / t / k) } + && forAll { (r: Reference, c: ConfigKey, t: TaskKey[String], k: InputKey[String]) => expectValue(k in (r, c, t))(r / c / t / k) }) } + property("Config / key == key in Config") = { - def check[K <: Key[K]: Arbitrary] = - forAll((c: ConfigKey, k: K) => expectValue(k in c)(c / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + import WithScope._ + (forAll { (c: ConfigKey, k: SettingKey[String]) => expectValue(k in c)(c / k) } + && forAll { (c: ConfigKey, k: TaskKey[String]) => expectValue(k in c)(c / k) } + && forAll { (c: ConfigKey, k: InputKey[String]) => expectValue(k in c)(c / k) }) } + property("Config / task / key == key in Config in task") = { - def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((c: ConfigKey, t: K, k: K) => expectValue(k in c in t)(c / t / k)) - (true - && check[InputKey[String], InputKey[String]] - && check[InputKey[String], SettingKey[String]] - && check[InputKey[String], TaskKey[String]] - && check[SettingKey[String], InputKey[String]] - && check[SettingKey[String], SettingKey[String]] - && check[SettingKey[String], TaskKey[String]] - && check[TaskKey[String], InputKey[String]] - && check[TaskKey[String], SettingKey[String]] - && check[TaskKey[String], TaskKey[String]] - ) + import WithoutScope._ + (forAll { (c: ConfigKey, t: TaskKey[String], k: SettingKey[String]) => expectValue(k in c in t)(c / t / k) } + && forAll { (c: ConfigKey, t: TaskKey[String], k: TaskKey[String]) => expectValue(k in c in t)(c / t / k) } + && forAll { (c: ConfigKey, t: TaskKey[String], k: InputKey[String]) => expectValue(k in c in t)(c / t / k) }) } + property("task / key == key in task") = { - def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((t: K, k: K) => expectValue(k in t)(t / k)) - (true - && check[InputKey[String], InputKey[String]] - && check[InputKey[String], SettingKey[String]] - && check[InputKey[String], TaskKey[String]] - && check[SettingKey[String], InputKey[String]] - && check[SettingKey[String], SettingKey[String]] - && check[SettingKey[String], TaskKey[String]] - && check[TaskKey[String], InputKey[String]] - && check[TaskKey[String], SettingKey[String]] - && check[TaskKey[String], TaskKey[String]] - ) + import WithoutScope._ + (forAll { (t: TaskKey[String], k: SettingKey[String]) => expectValue(k in t)(t / k) } + && forAll { (t: TaskKey[String], k: TaskKey[String]) => expectValue(k in t)(t / k) } + && forAll { (t: TaskKey[String], k: InputKey[String]) => expectValue(k in t)(t / k) }) } + property("Scope / key == key in Scope") = { - def check[K <: Key[K]: Arbitrary] = forAll((s: Scope, k: K) => expectValue(k in s)(s / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + import WithScope._ + (forAll { (s: Scope, k: SettingKey[String]) => expectValue(k in s)(s / k) } + && forAll { (s: Scope, k: TaskKey[String]) => expectValue(k in s)(s / k) } + && forAll { (s: Scope, k: InputKey[String]) => expectValue(k in s)(s / k) }) } + property("Reference? / key == key in ThisScope.copy(..)") = { - def check[K <: Key[K]: Arbitrary] = - forAll((r: ScopeAxis[Reference], k: K) => - expectValue(k in ThisScope.copy(project = r))(r / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + import WithScope._ + (forAll { (r: ScopeAxis[Reference], k: SettingKey[String]) => + expectValue(k in ThisScope.copy(project = r))(r / k) } && + forAll { (r: ScopeAxis[Reference], k: TaskKey[String]) => + expectValue(k in ThisScope.copy(project = r))(r / k) } && + forAll { (r: ScopeAxis[Reference], k: InputKey[String]) => + expectValue(k in ThisScope.copy(project = r))(r / k) }) } + property("Reference? / ConfigKey? / key == key in ThisScope.copy(..)") = { - def check[K <: Key[K]: Arbitrary] = - forAll((r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: K) => - expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + import WithScope._ + (forAll { (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: SettingKey[String]) => + expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k) } && + forAll { (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: TaskKey[String]) => + expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k) } && + forAll { (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: InputKey[String]) => + expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k) }) } -// property("Reference? / AttributeKey? / key == key in ThisScope.copy(..)") = { -// def check[K <: Key[K]: Arbitrary] = -// forAll( -// (r: ScopeAxis[Reference], t: ScopeAxis[AttributeKey[_]], k: K) => -// expectValue(k in ThisScope.copy(project = r, task = t))(r / t / k)) -// check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] -// } + + // property("Reference? / AttributeKey? / key == key in ThisScope.copy(..)") = { + // import WithScope._ + // (forAll { (r: ScopeAxis[Reference], t: ScopeAxis[AttributeKey[_]], k: SettingKey[String]) => + // expectValue(k in ThisScope.copy(project = r, task = t))(r / t / k) } && + // forAll { (r: ScopeAxis[Reference], t: ScopeAxis[AttributeKey[_]], k: TaskKey[String]) => + // expectValue(k in ThisScope.copy(project = r, task = t))(r / t / k) } && + // forAll { (r: ScopeAxis[Reference], t: ScopeAxis[AttributeKey[_]], k: InputKey[String]) => + // expectValue(k in ThisScope.copy(project = r, task = t))(r / t / k) } + // } + property("Reference? / ConfigKey? / AttributeKey? / key == key in ThisScope.copy(..)") = { - def check[K <: Key[K]: Arbitrary] = - forAll( - (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]], k: K) => - expectValue(k in ThisScope.copy(project = r, config = c, task = t))(r / c / t / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + import WithScope._ + (forAll { (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]], k: SettingKey[String]) => + expectValue(k in ThisScope.copy(project = r, config = c, task = t))(r / c / t / k) } && + forAll { (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]], k: TaskKey[String]) => + expectValue(k in ThisScope.copy(project = r, config = c, task = t))(r / c / t / k) } && + forAll { (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]], k: InputKey[String]) => + expectValue(k in ThisScope.copy(project = r, config = c, task = t))(r / c / t / k) }) } } diff --git a/sbt/src/sbt-test/project/unified/build.sbt b/sbt/src/sbt-test/project/unified/build.sbt index 91f772197..7f456b66c 100644 --- a/sbt/src/sbt-test/project/unified/build.sbt +++ b/sbt/src/sbt-test/project/unified/build.sbt @@ -1,8 +1,14 @@ import sbt.internal.CommandStrings.{ inspectBrief, inspectDetailed } import sbt.internal.Inspect +import sjsonnew._, BasicJsonProtocol._ val uTest = "com.lihaoyi" %% "utest" % "0.5.3" +val foo = taskKey[Int]("") +val bar = taskKey[Int]("") +val baz = inputKey[Unit]("") +val buildInfo = taskKey[Seq[File]]("The task that generates the build info.") + lazy val root = (project in file(".")) .settings( Global / cancelable := true, @@ -10,9 +16,24 @@ lazy val root = (project in file(".")) console / scalacOptions += "-deprecation", Compile / console / scalacOptions += "-Ywarn-numeric-widen", projA / Compile / console / scalacOptions += "-feature", + Zero / name := "foo", Zero / Zero / name := "foo", Zero / Zero / Zero / name := "foo", + Test / bar := 1, + Test / foo := (Test / bar).value + 1, + Compile / foo := { + (Compile / bar).previous.getOrElse(1) + }, + Compile / bar := { + (Compile / foo).previous.getOrElse(2) + }, + Test / buildInfo := Nil, + baz := { + val x = (Test / buildInfo).taskValue + (Compile / run).evaluated + }, + libraryDependencies += uTest % Test, testFrameworks += new TestFramework("utest.runner.Framework"),