diff --git a/core/resolve/src/mill/resolve/Resolve.scala b/core/resolve/src/mill/resolve/Resolve.scala index 9bada228f39..7f40f30d89c 100644 --- a/core/resolve/src/mill/resolve/Resolve.scala +++ b/core/resolve/src/mill/resolve/Resolve.scala @@ -8,6 +8,7 @@ import mill.define.{ Discover, Module, NamedTask, + Segment, Segments, TaskModule, SelectMode @@ -27,7 +28,10 @@ private[mill] object Resolve { resolveToModuleTasks: Boolean, cache: ResolveCore.Cache ) = { - Result.Success(resolved.map(_.segments)) + // For qualified super tasks (super.Something), return the original selector + // Otherwise, return the resolved segments + val hasQualifiedSuper = selector.render.contains(".super.") + Result.Success(if (hasQualifiedSuper) Seq(selector) else resolved.map(_.segments)) } private[mill] override def deduplicate(items: List[Segments]): List[Segments] = items.distinct @@ -58,10 +62,33 @@ private[mill] object Resolve { val taskList: Seq[Result[Option[NamedTask[?]]]] = resolved.map { case r: Resolved.NamedTask => - val instantiated = ResolveCore - .instantiateModule(rootModule, r.segments.init, cache) - .flatMap(instantiateNamedTask(r, _, cache)) - instantiated.map(Some(_)) + val superIdx = r.segments.value.indexWhere { + case Segment.Label("super") => true + case _ => false + } + + if (superIdx >= 0) { + // For tasks with super, create a task with segments before the super segment + val segmentsWithoutSuper = mill.define.Segments(r.segments.value.take(superIdx)) + Result.Success(Some(new NamedTask[Any] { + override def ctx0 = mill.define.Ctx.makeRoot( + millModuleEnclosing0 = rootModule.moduleCtx.enclosing, + millModuleLine0 = rootModule.moduleCtx.lineNum, + millSourcePath = rootModule.moduleCtx.millSourcePath, + segments0 = segmentsWithoutSuper, + external0 = rootModule.moduleCtx.external, + fileName = rootModule.moduleCtx.fileName + ) + override def isPrivate = None + override val inputs = Nil + override def evaluate0 = ??? + })) + } else { + val instantiated = ResolveCore + .instantiateModule(rootModule, r.segments.init, cache) + .flatMap(instantiateNamedTask(r, _, cache)) + instantiated.map(Some(_)) + } case r: Resolved.Command => val instantiated = ResolveCore diff --git a/core/resolve/src/mill/resolve/ResolveCore.scala b/core/resolve/src/mill/resolve/ResolveCore.scala index c60603f8aa9..e3e3d6a27d0 100644 --- a/core/resolve/src/mill/resolve/ResolveCore.scala +++ b/core/resolve/src/mill/resolve/ResolveCore.scala @@ -141,6 +141,9 @@ private object ResolveCore { } (head, current) match { + case (Segment.Label("super"), task: Resolved.NamedTask) => + Success(Seq(Resolved.NamedTask(task.segments ++ Seq(head)))) + case (Segment.Label(singleLabel), m: Resolved.Module) => val resOrErr: mill.api.Result[Seq[Resolved]] = singleLabel match { case "__" => diff --git a/core/resolve/test/src/mill/resolve/ResolveTests.scala b/core/resolve/test/src/mill/resolve/ResolveTests.scala index 088659b3062..4a3dc619829 100644 --- a/core/resolve/test/src/mill/resolve/ResolveTests.scala +++ b/core/resolve/test/src/mill/resolve/ResolveTests.scala @@ -21,6 +21,29 @@ object ResolveTests extends TestSuite { lazy val millDiscover = Discover[this.type] } + // Test modules for supertask resolution + trait BaseModule extends TestBaseModule { + def baseTask = Task { "base" } + def multiOverride = Task { "base-multi" } + } + + object singleOverrideModule extends BaseModule { + // Single override of baseTask + override def baseTask = Task { "single-override" } + lazy val millDiscover = Discover[this.type] + } + + trait MidModule extends BaseModule { + // First level override of multiOverride + override def multiOverride = Task { "mid-override" } + } + + object multiOverrideModule extends MidModule { + // Second level override of multiOverride + override def multiOverride = Task { "final-override" } + lazy val millDiscover = Discover[this.type] + } + def isShortError(x: Result[?], s: String) = x.errorOpt.exists(_.contains(s)) && // Make sure the stack traces are truncated and short-ish, and do not @@ -250,5 +273,68 @@ object ResolveTests extends TestSuite { ) } + test("supertasks") { + test("singleOverride") { + val check = new Checker(singleOverrideModule) + + // Super task should resolve to the base implementation + test("superTask") - check( + "baseTask.super", + Result.Success(Set(_.baseTask)), + Set("baseTask.super") + ) + } + + test("multiOverride") { + val check = new Checker(multiOverrideModule) + + // Direct super task should resolve to the mid-level implementation + test("directSuperTask") - check( + "multiOverride.super", + Result.Success(Set(_.multiOverride)), + Set("multiOverride.super") + ) + + // Qualified super task should resolve to the base implementation + test("qualifiedSuperTask") - check( + "multiOverride.super.BaseModule", + Result.Success(Set(_.multiOverride)), + Set("multiOverride.super.BaseModule") + ) + } + + // Test for complex super task resolution + test("complexSuperTask") { + // Create a more complex module hierarchy to test super task resolution + trait ComplexBase extends TestBaseModule { + def artifactSuffix = Task { "base-suffix" } + } + + trait ComplexMid extends ComplexBase { + override def artifactSuffix = Task { "mid-suffix" } + } + + object complexModule extends ComplexMid { + override def artifactSuffix = Task { "final-suffix" } + lazy val millDiscover = Discover[this.type] + } + + val check = new Checker(complexModule) + + // Test direct super task resolution + test("directSuperTask") - check( + "artifactSuffix.super", + Result.Success(Set(_.artifactSuffix)), + Set("artifactSuffix.super") + ) + + // Test qualified super task resolution + test("qualifiedSuperTask") - check( + "artifactSuffix.super.ComplexBase", + Result.Success(Set(_.artifactSuffix)), + Set("artifactSuffix.super.ComplexBase") + ) + } + } } }