From e5cedbe56b9811e09b9e43f37028f4a16e3504c6 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Tue, 12 Nov 2024 17:05:20 +0100 Subject: [PATCH 1/4] Fuse Info in Task to reduce instances --- .../sbt/internal/EvaluateConfigurations.scala | 2 +- main-actions/src/main/scala/sbt/Tests.scala | 5 +- main-settings/src/main/scala/sbt/Def.scala | 4 +- .../src/main/scala/sbt/Previous.scala | 2 +- .../src/main/scala/sbt/Structure.scala | 2 +- main/src/main/scala/sbt/EvaluateTask.scala | 4 +- main/src/main/scala/sbt/SessionVar.scala | 5 +- .../sbt/internal/AbstractTaskProgress.scala | 2 +- main/src/main/scala/sbt/internal/Load.scala | 2 +- .../main/scala/sbt/internal/TaskName.scala | 4 +- main/src/main/scala/sbt/nio/Settings.scala | 2 +- tasks-standard/src/main/scala/sbt/Task.scala | 78 +++++++------------ .../src/main/scala/sbt/std/TaskExtra.scala | 39 +++++----- .../src/main/scala/sbt/std/Transform.scala | 6 +- .../scala/sbt/internal/util/Attributes.scala | 8 ++ 15 files changed, 76 insertions(+), 89 deletions(-) diff --git a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 4aa859018..923c2ece5 100644 --- a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -404,7 +404,7 @@ object Index { case AttributeEntry(_, base: Task[?]) <- a.entries do def update(map: TriggerMap, key: AttributeKey[Seq[Task[?]]]): Unit = - base.info.attributes.get(key).getOrElse(Seq.empty).foreach { task => + base.getOrElse(key, Seq.empty).foreach { task => map(task) = base +: map.getOrElse(task, Nil) } update(runBefore, Def.runBefore) diff --git a/main-actions/src/main/scala/sbt/Tests.scala b/main-actions/src/main/scala/sbt/Tests.scala index 14e6c4ee1..d1051be52 100644 --- a/main-actions/src/main/scala/sbt/Tests.scala +++ b/main-actions/src/main/scala/sbt/Tests.scala @@ -463,10 +463,7 @@ object Tests { fun: TestFunction, tags: Seq[(Tag, Int)] ): Task[Map[String, SuiteResult]] = { - val base = Task[(String, (SuiteResult, Seq[TestTask]))]( - Info[(String, (SuiteResult, Seq[TestTask]))]().setName(name), - Action.Pure(() => (name, fun.apply()), `inline` = false) - ) + val base = Task(Action.Pure(() => (name, fun.apply()), `inline` = false)).setName(name) val taggedBase = base.tagw(tags*).tag(fun.tags.map(ConcurrentRestrictions.Tag(_))*) taggedBase flatMap { case (name, (result, nested)) => val nestedRunnables = createNestedRunnables(loader, fun, nested) diff --git a/main-settings/src/main/scala/sbt/Def.scala b/main-settings/src/main/scala/sbt/Def.scala index c4d48256b..e14f15e25 100644 --- a/main-settings/src/main/scala/sbt/Def.scala +++ b/main-settings/src/main/scala/sbt/Def.scala @@ -457,11 +457,11 @@ object Def extends BuildSyntax with Init[Scope] with InitializeImplicits: sys.error(s"Dummy task '$name' did not get converted to a full task.") ) .named(name) - base.copy(info = base.info.set(isDummyTask, true)) + base.set(isDummyTask, true) } private[sbt] def isDummy(t: Task[?]): Boolean = - t.info.attributes.get(isDummyTask) getOrElse false + t.get(isDummyTask).getOrElse(false) end Def sealed trait InitializeImplicits { self: Def.type => diff --git a/main-settings/src/main/scala/sbt/Previous.scala b/main-settings/src/main/scala/sbt/Previous.scala index 43256f6af..66bd30cbf 100644 --- a/main-settings/src/main/scala/sbt/Previous.scala +++ b/main-settings/src/main/scala/sbt/Previous.scala @@ -127,7 +127,7 @@ object Previous { val successfulTaskResults = ( for case results.TPair(task: Task[?], Result.Value(v)) <- results.toTypedSeq - key <- task.info.attributes.get(Def.taskDefinitionKey).asInstanceOf[Option[AnyTaskKey]] + key <- task.get(Def.taskDefinitionKey).asInstanceOf[Option[AnyTaskKey]] yield key -> v ).toMap // We then traverse the successful results and look up all of the referenced values for diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index 7986dc16d..49995dab8 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -385,7 +385,7 @@ object Scoped: ): Initialize[Task[A1]] = Initialize .joinAny[Task](coerceToAnyTaskSeq(tasks)) - .zipWith(init)((ts, i) => i.copy(info = i.info.set(key, ts))) + .zipWith(init)((ts, i) => i.set(key, ts)) extension [A1](init: Initialize[InputTask[A1]]) @targetName("onTaskInitializeInputTask") diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 326736b90..cd833ed2a 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -582,8 +582,8 @@ object EvaluateTask { Function.chain( results.toTypedSeq flatMap { case results.TPair(_, Result.Value(KeyValue(_, st: StateTransform))) => Some(st.transform) - case results.TPair(Task(info, _), Result.Value(v)) => info.post(v).get(transformState) - case _ => Nil + case results.TPair(Task(_, post, _), Result.Value(v)) => post(v).get(transformState) + case _ => Nil } ) diff --git a/main/src/main/scala/sbt/SessionVar.scala b/main/src/main/scala/sbt/SessionVar.scala index 40a36b587..b8d4f3af0 100644 --- a/main/src/main/scala/sbt/SessionVar.scala +++ b/main/src/main/scala/sbt/SessionVar.scala @@ -49,10 +49,9 @@ object SessionVar { def orEmpty(opt: Option[Map]) = opt.getOrElse(emptyMap) - def transform[S](task: Task[S], f: (State, S) => State): Task[S] = { + def transform[S](task: Task[S], f: (State, S) => State): Task[S] = val g = (s: S, map: AttributeMap) => map.put(Keys.transformState, (state: State) => f(state, s)) - task.copy(info = task.info.postTransform(g)) - } + task.postTransform(g) def resolveContext[T]( key: ScopedKey[Task[T]], diff --git a/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala b/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala index 626f041f1..b7542670a 100644 --- a/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala +++ b/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala @@ -112,7 +112,7 @@ private[sbt] abstract class AbstractTaskExecuteProgress extends ExecuteProgress } private def taskName0(t: TaskId[?]): String = { def definedName(node: Task[?]): Option[String] = - node.info.name.orElse(TaskName.transformNode(node).map(showScopedKey.show)) + node.name.orElse(TaskName.transformNode(node).map(showScopedKey.show)) def inferredName(t: Task[?]): Option[String] = nameDelegate(t) map taskName def nameDelegate(t: Task[?]): Option[TaskId[?]] = Option(anonOwners.get(t)).orElse(Option(calledBy.get(t))) diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 7878dd305..1d293c752 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -366,7 +366,7 @@ private[sbt] object Load { } def setDefinitionKey[T](tk: Task[T], key: ScopedKey[?]): Task[T] = - if (isDummy(tk)) tk else Task(tk.info.set(Keys.taskDefinitionKey, key), tk.work) + if (isDummy(tk)) tk else tk.set(Keys.taskDefinitionKey, key) def structureIndex( data: Settings[Scope], diff --git a/main/src/main/scala/sbt/internal/TaskName.scala b/main/src/main/scala/sbt/internal/TaskName.scala index 45199bc66..840c0bcca 100644 --- a/main/src/main/scala/sbt/internal/TaskName.scala +++ b/main/src/main/scala/sbt/internal/TaskName.scala @@ -15,9 +15,9 @@ import Keys.taskDefinitionKey private[sbt] object TaskName { def name(node: Task[?]): String = definedName(node).getOrElse(anonymousName(node)) def definedName(node: Task[?]): Option[String] = - node.info.name.orElse(transformNode(node).map(displayFull)) + node.name.orElse(transformNode(node).map(displayFull)) def anonymousName(node: TaskId[?]): String = "" def transformNode(node: Task[?]): Option[ScopedKey[?]] = - node.info.attributes.get(taskDefinitionKey) + node.get(taskDefinitionKey) } diff --git a/main/src/main/scala/sbt/nio/Settings.scala b/main/src/main/scala/sbt/nio/Settings.scala index e7f7462a0..eff12b903 100644 --- a/main/src/main/scala/sbt/nio/Settings.scala +++ b/main/src/main/scala/sbt/nio/Settings.scala @@ -145,7 +145,7 @@ private[sbt] object Settings { * @return the setting with the task definition */ private def addTaskDefinition[T](setting: Def.Setting[Task[T]]): Def.Setting[Task[T]] = - setting.mapInit((sk, task) => Task(task.info.set(taskDefinitionKey, sk), task.work)) + setting.mapInit((sk, task) => task.set(taskDefinitionKey, sk)) /** * Returns all of the paths described by a glob along with their basic file attributes. diff --git a/tasks-standard/src/main/scala/sbt/Task.scala b/tasks-standard/src/main/scala/sbt/Task.scala index 099d4d10c..daa865132 100644 --- a/tasks-standard/src/main/scala/sbt/Task.scala +++ b/tasks-standard/src/main/scala/sbt/Task.scala @@ -14,32 +14,45 @@ import ConcurrentRestrictions.{ Tag, TagMap, tagsKey } import sbt.util.Monad /** - * Combines metadata `info` and a computation `work` to define a task. + * Combines metadata `attributes` and a computation `work` to define a task. */ -final case class Task[A](info: Info[A], work: Action[A]) extends TaskId[A]: - override def toString = info.name getOrElse ("Task(" + info + ")") - override def hashCode = info.hashCode +final case class Task[A](attributes: AttributeMap, post: A => AttributeMap, work: Action[A]) + extends TaskId[A]: + override def toString = name.getOrElse(s"Task($attributes)") + override def hashCode = (attributes, post).hashCode + + def name = attributes.get(Task.Name) + def description = attributes.get(Task.Description) + def get[B](key: AttributeKey[B]): Option[B] = attributes.get(key) + def getOrElse[B](key: AttributeKey[B], default: => B): B = attributes.getOrElse(key, default) + + def setName(name: String): Task[A] = set(Task.Name, name) + def setDescription(description: String): Task[A] = set(Task.Description, description) + def set[A](key: AttributeKey[A], value: A) = copy(attributes = this.attributes.put(key, value)) + + def postTransform(f: (A, AttributeMap) => AttributeMap): Task[A] = copy(post = a => f(a, post(a))) def tag(tags: Tag*): Task[A] = tagw(tags.map(t => (t, 1))*) - def tagw(tags: (Tag, Int)*): Task[A] = { - val tgs: TagMap = info.get(tagsKey).getOrElse(TagMap.empty) + def tagw(tags: (Tag, Int)*): Task[A] = + val tgs: TagMap = get(tagsKey).getOrElse(TagMap.empty) val value = tags.foldLeft(tgs)((acc, tag) => acc + tag) - val nextInfo = info.set(tagsKey, value) - withInfo(info = nextInfo) - } - - def tags: TagMap = info.get(tagsKey).getOrElse(TagMap.empty) - def name: Option[String] = info.name - - def attributes: AttributeMap = info.attributes - - private[sbt] def withInfo(info: Info[A]): Task[A] = - Task(info = info, work = this.work) + set(tagsKey, value) + def tags: TagMap = get(tagsKey).getOrElse(TagMap.empty) end Task object Task: import sbt.std.TaskExtra.* + def apply[A](work: Action[A]): Task[A] = + new Task[A](AttributeMap.empty, defaultAttributeMap, work) + + def apply[A](attributes: AttributeMap, work: Action[A]): Task[A] = + new Task[A](attributes, defaultAttributeMap, work) + + val Name = AttributeKey[String]("name") + val Description = AttributeKey[String]("description") + val defaultAttributeMap = const(AttributeMap.empty) + given taskMonad: Monad[Task] with type F[a] = Task[a] override def pure[A1](a: () => A1): Task[A1] = toTask(a) @@ -54,34 +67,3 @@ object Task: override def flatMap[A1, A2](in: F[A1])(f: A1 => F[A2]): F[A2] = in.flatMap(f) override def flatten[A1](in: Task[Task[A1]]): Task[A1] = in.flatMap(identity) end Task - -/** - * Used to provide information about a task, such as the name, description, and tags for controlling - * concurrent execution. - * @param attributes - * Arbitrary user-defined key/value pairs describing this task - * @param post - * a transformation that takes the result of evaluating this task and produces user-defined - * key/value pairs. - */ -final case class Info[T]( - attributes: AttributeMap = AttributeMap.empty, - post: T => AttributeMap = Info.defaultAttributeMap -) { - import Info._ - def name = attributes.get(Name) - def description = attributes.get(Description) - def setName(n: String) = set(Name, n) - def setDescription(d: String) = set(Description, d) - def set[A](key: AttributeKey[A], value: A) = copy(attributes = this.attributes.put(key, value)) - def get[A](key: AttributeKey[A]): Option[A] = attributes.get(key) - def postTransform(f: (T, AttributeMap) => AttributeMap) = copy(post = (t: T) => f(t, post(t))) - - override def toString = if (attributes.isEmpty) "_" else attributes.toString -} - -object Info: - val Name = AttributeKey[String]("name") - val Description = AttributeKey[String]("description") - val defaultAttributeMap = const(AttributeMap.empty) -end Info diff --git a/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala b/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala index 946d46b3e..c0c652bb5 100644 --- a/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala +++ b/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala @@ -103,7 +103,7 @@ trait TaskExtra0 { joinTasks0[Any](existToAny(in)) private[sbt] def joinTasks0[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { def join: Task[Seq[S]] = - Task[Seq[S]](Info(), Action.Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)))) + Task[Seq[S]](Action.Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)))) def reduced(f: (S, S) => S): Task[S] = TaskExtra.reduced(in.toIndexedSeq, f) } private[sbt] def existToAny(in: Seq[Task[?]]): Seq[Task[Any]] = in.asInstanceOf[Seq[Task[Any]]] @@ -114,8 +114,8 @@ trait TaskExtra extends TaskExtra0 { final def constant[T](t: T): Task[T] = task(t) final def task[T](f: => T): Task[T] = toTask(() => f) - final implicit def toTask[T](f: () => T): Task[T] = Task(Info(), Action.Pure(f, false)) - final def inlineTask[T](value: T): Task[T] = Task(Info(), Action.Pure(() => value, true)) + final implicit def toTask[T](f: () => T): Task[T] = Task(Action.Pure(f, false)) + final def inlineTask[T](value: T): Task[T] = Task(Action.Pure(() => value, true)) final implicit def upcastTask[A >: B, B](t: Task[B]): Task[A] = t mapN { x => x: A @@ -131,7 +131,7 @@ trait TaskExtra extends TaskExtra0 { final implicit def joinTasks[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { def join: Task[Seq[S]] = - Task[Seq[S]](Info(), Action.Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)))) + Task[Seq[S]](Action.Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)))) def reduced(f: (S, S) => S): Task[S] = TaskExtra.reduced(in.toIndexedSeq, f) } @@ -144,18 +144,18 @@ trait TaskExtra extends TaskExtra0 { final implicit def multInputTask[Tup <: Tuple](tasks: Tuple.Map[Tup, Task]): MultiInTask[Tup] = new MultiInTask[Tup]: override def flatMapN[A](f: Tup => Task[A]): Task[A] = - Task(Info(), Action.FlatMapped(tasks, f.compose(allM))) + Task(Action.FlatMapped(tasks, f.compose(allM))) override def flatMapR[A](f: Tuple.Map[Tup, Result] => Task[A]): Task[A] = - Task(Info(), Action.FlatMapped(tasks, f)) + Task(Action.FlatMapped(tasks, f)) override def mapN[A](f: Tup => A): Task[A] = - Task(Info(), Action.Mapped(tasks, f.compose(allM))) + Task(Action.Mapped(tasks, f.compose(allM))) override def mapR[A](f: Tuple.Map[Tup, Result] => A): Task[A] = - Task(Info(), Action.Mapped(tasks, f)) + Task(Action.Mapped(tasks, f)) override def flatFailure[A](f: Seq[Incomplete] => Task[A]): Task[A] = - Task(Info(), Action.FlatMapped(tasks, f.compose(anyFailM))) + Task(Action.FlatMapped(tasks, f.compose(anyFailM))) override def mapFailure[A](f: Seq[Incomplete] => A): Task[A] = - Task(Info(), Action.Mapped(tasks, f.compose(anyFailM))) + Task(Action.Mapped(tasks, f.compose(anyFailM))) final implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = new SingleInTask[S]: @@ -164,21 +164,22 @@ trait TaskExtra extends TaskExtra0 { def failure: Task[Incomplete] = mapFailure(identity) def result: Task[Result[S]] = mapR(identity) - private def newInfo[A]: Info[A] = TaskExtra.newInfo(in.info) + private def newAttributes: AttributeMap = TaskExtra.newAttributes(in.attributes) override def flatMapR[A](f: Result[S] => Task[A]): Task[A] = Task( - newInfo, + newAttributes, Action.FlatMapped[A, Tuple1[S]](Tuple1(in), { case Tuple1(a) => f(a) }) ) override def mapR[A](f: Result[S] => A): Task[A] = Task( - newInfo, + newAttributes, Action.Mapped[A, Tuple1[S]](Tuple1(in), { case Tuple1(a) => f(a) }) ) - override def dependsOn(tasks: Task[?]*): Task[S] = Task(newInfo, Action.DependsOn(in, tasks)) + override def dependsOn(tasks: Task[?]*): Task[S] = + Task(newAttributes, Action.DependsOn(in, tasks)) override def flatMapN[T](f: S => Task[T]): Task[T] = flatMapR(f.compose(successM)) @@ -206,8 +207,8 @@ trait TaskExtra extends TaskExtra0 { def &&[T](alt: Task[T]): Task[T] = flatMapN(_ => alt) final implicit def toTaskInfo[S](in: Task[S]): TaskInfo[S] = new TaskInfo[S] { - def describedAs(s: String): Task[S] = in.copy(info = in.info.setDescription(s)) - def named(s: String): Task[S] = in.copy(info = in.info.setName(s)) + def describedAs(s: String): Task[S] = in.setDescription(s) + def named(s: String): Task[S] = in.setName(s) } final implicit def pipeToProcess[Key]( @@ -327,10 +328,10 @@ object TaskExtra extends TaskExtra { def incompleteDeps(incs: Seq[Incomplete]): Incomplete = Incomplete(None, causes = incs) def select[A, B](fab: Task[Either[A, B]], f: Task[A => B]): Task[B] = - Task(newInfo(fab.info), Action.Selected[A, B](fab, f)) + Task(newAttributes(fab.attributes), Action.Selected[A, B](fab, f)) // The "taskDefinitionKey" is used, at least, by the ".previous" functionality. // But apparently it *cannot* survive a task map/flatMap/etc. See actions/depends-on. - private[sbt] def newInfo[A](info: Info[?]): Info[A] = - Info[A](AttributeMap(info.attributes.entries.filter(_.key.label != "taskDefinitionKey"))) + private[sbt] def newAttributes[A](attributes: AttributeMap): AttributeMap = + AttributeMap(attributes.entries.filter(_.key.label != "taskDefinitionKey")) } diff --git a/tasks-standard/src/main/scala/sbt/std/Transform.scala b/tasks-standard/src/main/scala/sbt/std/Transform.scala index 134344c9c..15b01de4d 100644 --- a/tasks-standard/src/main/scala/sbt/std/Transform.scala +++ b/tasks-standard/src/main/scala/sbt/std/Transform.scala @@ -17,7 +17,7 @@ import sbt.internal.util.Types.* object Transform: def fromDummy[A](original: Task[A])(action: => A): Task[A] = - Task(original.info, Action.Pure(() => action, false)) + original.copy(work = Action.Pure(() => action, false)) def fromDummyStrict[T](original: Task[T], value: T): Task[T] = fromDummy(original)(value) @@ -57,8 +57,8 @@ object Transform: case Join(in, f) => uniform(in)(f) def inline1[T](t: TaskId[T]): Option[() => T] = t match - case Task(_, Action.Pure(eval, true)) => Some(eval) - case _ => None + case Task(_, _, Action.Pure(eval, true)) => Some(eval) + case _ => None def uniform[A1, D](tasks: Seq[Task[D]])( f: Seq[Result[D]] => Either[Task[A1], A1] diff --git a/util-collection/src/main/scala/sbt/internal/util/Attributes.scala b/util-collection/src/main/scala/sbt/internal/util/Attributes.scala index f0f1412f5..b45e3146a 100644 --- a/util-collection/src/main/scala/sbt/internal/util/Attributes.scala +++ b/util-collection/src/main/scala/sbt/internal/util/Attributes.scala @@ -172,6 +172,12 @@ trait AttributeMap { */ def get[T](k: AttributeKey[T]): Option[T] + /** + * Gets the value of type `T` associated with the key `k` or `default` if no value is associated. If + * a key with the same label but a different type is defined, this method will return `default`. + */ + def getOrElse[T](k: AttributeKey[T], default: => T): T + /** * Returns this map without the mapping for `k`. This method will not remove a mapping for a key * with the same label but a different type. @@ -245,6 +251,8 @@ private class BasicAttributeMap(private val backing: Map[AttributeKey[?], Any]) def isEmpty: Boolean = backing.isEmpty def apply[T](k: AttributeKey[T]) = backing(k).asInstanceOf[T] def get[T](k: AttributeKey[T]) = backing.get(k).asInstanceOf[Option[T]] + def getOrElse[T](k: AttributeKey[T], default: => T): T = + backing.getOrElse(k, default).asInstanceOf[T] def remove[T](k: AttributeKey[T]): AttributeMap = new BasicAttributeMap(backing - k) def contains[T](k: AttributeKey[T]) = backing.contains(k) From d54647a46c295e03aa649a754ea179587e24f905 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Wed, 13 Nov 2024 10:55:29 +0100 Subject: [PATCH 2/4] Use referential equality on Task --- main/src/main/scala/sbt/EvaluateTask.scala | 4 ++-- tasks-standard/src/main/scala/sbt/Task.scala | 20 ++++++++++++------- .../src/main/scala/sbt/std/Transform.scala | 6 +++--- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index cd833ed2a..e16205e1a 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -582,8 +582,8 @@ object EvaluateTask { Function.chain( results.toTypedSeq flatMap { case results.TPair(_, Result.Value(KeyValue(_, st: StateTransform))) => Some(st.transform) - case results.TPair(Task(_, post, _), Result.Value(v)) => post(v).get(transformState) - case _ => Nil + case results.TPair(task: Task[?], Result.Value(v)) => task.post(v).get(transformState) + case _ => Nil } ) diff --git a/tasks-standard/src/main/scala/sbt/Task.scala b/tasks-standard/src/main/scala/sbt/Task.scala index daa865132..cbe2628d5 100644 --- a/tasks-standard/src/main/scala/sbt/Task.scala +++ b/tasks-standard/src/main/scala/sbt/Task.scala @@ -16,21 +16,25 @@ import sbt.util.Monad /** * Combines metadata `attributes` and a computation `work` to define a task. */ -final case class Task[A](attributes: AttributeMap, post: A => AttributeMap, work: Action[A]) - extends TaskId[A]: +final class Task[A]( + val attributes: AttributeMap, + val post: A => AttributeMap, + val work: Action[A] +) extends TaskId[A]: override def toString = name.getOrElse(s"Task($attributes)") - override def hashCode = (attributes, post).hashCode - def name = attributes.get(Task.Name) - def description = attributes.get(Task.Description) + def name: Option[String] = get(Task.Name) + def description: Option[String] = get(Task.Description) def get[B](key: AttributeKey[B]): Option[B] = attributes.get(key) def getOrElse[B](key: AttributeKey[B], default: => B): B = attributes.getOrElse(key, default) def setName(name: String): Task[A] = set(Task.Name, name) def setDescription(description: String): Task[A] = set(Task.Description, description) - def set[A](key: AttributeKey[A], value: A) = copy(attributes = this.attributes.put(key, value)) + def set[B](key: AttributeKey[B], value: B) = + new Task(attributes.put(key, value), post, work) - def postTransform(f: (A, AttributeMap) => AttributeMap): Task[A] = copy(post = a => f(a, post(a))) + def postTransform(f: (A, AttributeMap) => AttributeMap): Task[A] = + new Task(attributes, a => f(a, post(a)), work) def tag(tags: Tag*): Task[A] = tagw(tags.map(t => (t, 1))*) def tagw(tags: (Tag, Int)*): Task[A] = @@ -49,6 +53,8 @@ object Task: def apply[A](attributes: AttributeMap, work: Action[A]): Task[A] = new Task[A](attributes, defaultAttributeMap, work) + def unapply[A](task: Task[A]): Option[Action[A]] = Some(task.work) + val Name = AttributeKey[String]("name") val Description = AttributeKey[String]("description") val defaultAttributeMap = const(AttributeMap.empty) diff --git a/tasks-standard/src/main/scala/sbt/std/Transform.scala b/tasks-standard/src/main/scala/sbt/std/Transform.scala index 15b01de4d..f10d4735f 100644 --- a/tasks-standard/src/main/scala/sbt/std/Transform.scala +++ b/tasks-standard/src/main/scala/sbt/std/Transform.scala @@ -17,7 +17,7 @@ import sbt.internal.util.Types.* object Transform: def fromDummy[A](original: Task[A])(action: => A): Task[A] = - original.copy(work = Action.Pure(() => action, false)) + new Task(original.attributes, original.post, work = Action.Pure(() => action, false)) def fromDummyStrict[T](original: Task[T], value: T): Task[T] = fromDummy(original)(value) @@ -57,8 +57,8 @@ object Transform: case Join(in, f) => uniform(in)(f) def inline1[T](t: TaskId[T]): Option[() => T] = t match - case Task(_, _, Action.Pure(eval, true)) => Some(eval) - case _ => None + case Task(Action.Pure(eval, true)) => Some(eval) + case _ => None def uniform[A1, D](tasks: Seq[Task[D]])( f: Seq[Result[D]] => Either[Task[A1], A1] From 6fbe032517aee2a22fdbf58502035bf9986e2884 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Wed, 13 Nov 2024 11:09:23 +0100 Subject: [PATCH 3/4] AttributeMap as an opaque type --- .../scala/sbt/internal/util/Attributes.scala | 182 +++++++----------- 1 file changed, 73 insertions(+), 109 deletions(-) diff --git a/util-collection/src/main/scala/sbt/internal/util/Attributes.scala b/util-collection/src/main/scala/sbt/internal/util/Attributes.scala index b45e3146a..4d672a905 100644 --- a/util-collection/src/main/scala/sbt/internal/util/Attributes.scala +++ b/util-collection/src/main/scala/sbt/internal/util/Attributes.scala @@ -158,131 +158,95 @@ object AttributeKey { * keys with the same label but different types. Excluding this possibility is the responsibility of * the client if desired. */ -trait AttributeMap { - - /** - * Gets the value of type `T` associated with the key `k`. If a key with the same label but - * different type is defined, this method will fail. - */ - def apply[T](k: AttributeKey[T]): T - - /** - * Gets the value of type `T` associated with the key `k` or `None` if no value is associated. If - * a key with the same label but a different type is defined, this method will return `None`. - */ - def get[T](k: AttributeKey[T]): Option[T] - - /** - * Gets the value of type `T` associated with the key `k` or `default` if no value is associated. If - * a key with the same label but a different type is defined, this method will return `default`. - */ - def getOrElse[T](k: AttributeKey[T], default: => T): T - - /** - * Returns this map without the mapping for `k`. This method will not remove a mapping for a key - * with the same label but a different type. - */ - def remove[T](k: AttributeKey[T]): AttributeMap - - /** - * Returns true if this map contains a mapping for `k`. If a key with the same label but a - * different type is defined in this map, this method will return `false`. - */ - def contains[T](k: AttributeKey[T]): Boolean - - /** - * Adds the mapping `k -> value` to this map, replacing any existing mapping for `k`. Any mappings - * for keys with the same label but different types are unaffected. - */ - def put[T](k: AttributeKey[T], value: T): AttributeMap - - /** - * All keys with defined mappings. There may be multiple keys with the same `label`, but different - * types. - */ - def keys: Iterable[AttributeKey[?]] - - /** - * Adds the mappings in `o` to this map, with mappings in `o` taking precedence over existing - * mappings. - */ - def ++(o: Iterable[AttributeEntry[?]]): AttributeMap - - /** - * Combines the mappings in `o` with the mappings in this map, with mappings in `o` taking - * precedence over existing mappings. - */ - def ++(o: AttributeMap): AttributeMap - - /** - * All mappings in this map. The [[AttributeEntry]] type preserves the typesafety of mappings, - * although the specific types are unknown. - */ - def entries: Iterable[AttributeEntry[?]] - - /** `true` if there are no mappings in this map, `false` if there are. */ - def isEmpty: Boolean - - /** - * Adds the mapping `k -> opt.get` if opt is Some. Otherwise, it returns this map without the - * mapping for `k`. - */ - private[sbt] def setCond[T](k: AttributeKey[T], opt: Option[T]): AttributeMap -} - -object AttributeMap { +opaque type AttributeMap = Map[AttributeKey[?], Any] +object AttributeMap: /** An [[AttributeMap]] without any mappings. */ - val empty: AttributeMap = new BasicAttributeMap(Map.empty) + val empty: AttributeMap = Map.empty /** Constructs an [[AttributeMap]] containing the given `entries`. */ - def apply(entries: Iterable[AttributeEntry[?]]): AttributeMap = empty ++ entries + def apply(entries: Iterable[AttributeEntry[?]]): AttributeMap = ++(empty)(entries) /** Constructs an [[AttributeMap]] containing the given `entries`. */ - def apply(entries: AttributeEntry[?]*): AttributeMap = empty ++ entries + def apply(entries: AttributeEntry[?]*): AttributeMap = ++(empty)(entries) - /** Presents an `AttributeMap` as a natural transformation. */ - // implicit def toNatTrans(map: AttributeMap): AttributeKey ~> Id = λ[AttributeKey ~> Id](map(_)) -} + extension (self: AttributeMap) + /** + * Gets the value of type `T` associated with the key `k`. If a key with the same label but + * different type is defined, this method will fail. + */ + def apply[T](k: AttributeKey[T]): T = self(k).asInstanceOf[T] -private class BasicAttributeMap(private val backing: Map[AttributeKey[?], Any]) - extends AttributeMap { + /** + * Gets the value of type `T` associated with the key `k` or `None` if no value is associated. If + * a key with the same label but a different type is defined, this method will return `None`. + */ + def get[T](k: AttributeKey[T]): Option[T] = self.get(k).asInstanceOf[Option[T]] - def isEmpty: Boolean = backing.isEmpty - def apply[T](k: AttributeKey[T]) = backing(k).asInstanceOf[T] - def get[T](k: AttributeKey[T]) = backing.get(k).asInstanceOf[Option[T]] - def getOrElse[T](k: AttributeKey[T], default: => T): T = - backing.getOrElse(k, default).asInstanceOf[T] - def remove[T](k: AttributeKey[T]): AttributeMap = new BasicAttributeMap(backing - k) - def contains[T](k: AttributeKey[T]) = backing.contains(k) + /** + * Gets the value of type `T` associated with the key `k` or `default` if no value is associated. If + * a key with the same label but a different type is defined, this method will return `default`. + */ + def getOrElse[T](k: AttributeKey[T], default: => T): T = + self.getOrElse(k, default).asInstanceOf[T] - def put[T](k: AttributeKey[T], value: T): AttributeMap = - new BasicAttributeMap(backing.updated(k, value: Any)) + /** + * Returns this map without the mapping for `k`. This method will not remove a mapping for a key + * with the same label but a different type. + */ + def remove[T](k: AttributeKey[T]): AttributeMap = self.removed(k) - def keys: Iterable[AttributeKey[?]] = backing.keys + /** + * Returns true if this map contains a mapping for `k`. If a key with the same label but a + * different type is defined in this map, this method will return `false`. + */ + def contains[T](k: AttributeKey[T]): Boolean = self.contains(k) - def ++(o: Iterable[AttributeEntry[?]]): AttributeMap = - new BasicAttributeMap(o.foldLeft(backing)((b, e) => b.updated(e.key, e.value: Any))) + /** + * Adds the mapping `k -> value` to this map, replacing any existing mapping for `k`. Any mappings + * for keys with the same label but different types are unaffected. + */ + def put[T](k: AttributeKey[T], value: T): AttributeMap = self.updated(k, value) - def ++(o: AttributeMap): AttributeMap = o match { - case bam: BasicAttributeMap => - new BasicAttributeMap(Map(backing.toSeq ++ bam.backing.toSeq*)) - case _ => o ++ this - } + /** + * All keys with defined mappings. There may be multiple keys with the same `label`, but different + * types. + */ + def keys: Iterable[AttributeKey[?]] = self.keys - def entries: Iterable[AttributeEntry[?]] = - backing.collect { case (k: AttributeKey[kt], v) => - AttributeEntry(k, v.asInstanceOf[kt]) - } + /** + * Adds the mappings in `o` to this map, with mappings in `o` taking precedence over existing + * mappings. + */ + def ++(o: Iterable[AttributeEntry[?]]): AttributeMap = + o.foldLeft(self)((b, e) => b.updated(e.key, e.value)) - private[sbt] def setCond[T](k: AttributeKey[T], opt: Option[T]): AttributeMap = - opt match { - case Some(v) => put(k, v) - case None => remove(k) - } + /** + * Combines the mappings in `o` with the mappings in this map, with mappings in `o` taking + * precedence over existing mappings. + */ + def ++(o: AttributeMap): AttributeMap = self ++ o - override def toString = entries.mkString("(", ", ", ")") -} + /** + * All mappings in this map. The [[AttributeEntry]] type preserves the typesafety of mappings, + * although the specific types are unknown. + */ + def entries: Iterable[AttributeEntry[?]] = + self.collect { case (k: AttributeKey[x], v) => AttributeEntry(k, v.asInstanceOf[x]) } + + /** `true` if there are no mappings in this map, `false` if there are. */ + def isEmpty: Boolean = self.isEmpty + + /** + * Adds the mapping `k -> opt.get` if opt is Some. Otherwise, it returns this map without the + * mapping for `k`. + */ + private[sbt] def setCond[T](k: AttributeKey[T], opt: Option[T]): AttributeMap = + opt match + case Some(v) => self.updated(k, v) + case None => self.removed(k) + end extension +end AttributeMap /** * An immutable map where both key and value are String. From 327d05e28f44214a159a304b44b531029f5fcdd5 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Wed, 13 Nov 2024 11:22:53 +0100 Subject: [PATCH 4/4] Remove unused Structure.taskToKey --- .../scala/sbt/internal/EvaluateConfigurations.scala | 11 ----------- main/src/main/scala/sbt/internal/BuildStructure.scala | 1 - main/src/main/scala/sbt/internal/Load.scala | 1 - 3 files changed, 13 deletions(-) diff --git a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 923c2ece5..4f164e92a 100644 --- a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -351,17 +351,6 @@ object BuildUtilLite: end BuildUtilLite object Index { - def taskToKeyMap(data: Settings[Scope]): Map[Task[?], ScopedKey[Task[?]]] = { - - val pairs = data.scopes flatMap (scope => - data.data(scope).entries collect { case AttributeEntry(key, value: Task[_]) => - (value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[?]]])) - } - ) - - pairs.toMap[Task[?], ScopedKey[Task[?]]] - } - def allKeys(settings: Seq[Setting[?]]): Set[ScopedKey[?]] = { val result = new java.util.HashSet[ScopedKey[?]] settings.foreach { s => diff --git a/main/src/main/scala/sbt/internal/BuildStructure.scala b/main/src/main/scala/sbt/internal/BuildStructure.scala index eb52c31a9..e33fed4f0 100644 --- a/main/src/main/scala/sbt/internal/BuildStructure.scala +++ b/main/src/main/scala/sbt/internal/BuildStructure.scala @@ -71,7 +71,6 @@ final class BuildStructure( // information that is not original, but can be reconstructed from the rest of BuildStructure final class StructureIndex( val keyMap: Map[String, AttributeKey[?]], - val taskToKey: Map[Task[?], ScopedKey[Task[?]]], val triggers: Triggers, val keyIndex: KeyIndex, val aggregateKeyIndex: KeyIndex, diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 1d293c752..4e12246ec 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -384,7 +384,6 @@ private[sbt] object Load { val aggIndex = KeyIndex.aggregate(scopedKeys.toVector, extra(keyIndex), projectsMap, configsMap) new StructureIndex( Index.stringToKeyMap(attributeKeys), - Index.taskToKeyMap(data), Index.triggers(data), keyIndex, aggIndex