Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make T an alias for Task instead of Target, move T.* operations to Task.* #3356

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

lihaoyi
Copy link
Member

@lihaoyi lihaoyi commented Aug 8, 2024

This PR attempts to fix #3338:

  1. T is now an alias for Task instead of Target. We expect to use foo: Task[V], Task { ... }, Task.dest, etc. APIs going forward, but all existing APIs remain on the T aliases for backwards source compatibility

  2. Task.apply or Task { ... } continues to return a Target instance, but now is typed as returning the supertype Task. This allows people to write def foo: Task[T] = Task { ... } and have it do the right thing, rather than the otherwise necessary def foo: Target[T] = Task { ... } which is pretty awkward

  3. In order to support using Task[T] everywhere as types, I flattened out the type hierarchy further: now, most APIs work directly with Task instead of Target or NamedTask

    1. Task now supports .ctx, which throws an UnsupportedOperationException by default. NamedTask overrides it with an implementation.
    2. Rather than relying on methods returning Target[_] for resolution, we now rely on methods returning Task[_] with zero argument lists. As the number of argument lists is not available through Java reflect (we can only see the total number of arguments), I rely on information collected by the Discover[T] macro.
  4. I made Input[_], Source[_], Target[_], etc. type aliases of Task[_]. This avoids issues where people might accidentally declare def foo: Target[V] and find themselves unable to override it later as type def foo: Task[V] = Task { ... }. This is similar to how Make Target type abstract to allow overriding by different concrete implementations #2402 earlier made Input Source etc. type aliases of Target[_], and serves a similar purpose.

  5. Distinguishing of cached targets (zero argument lists) with uncached other things (1 or more argument lists) is done via the information we already collect in the Discover macro. For safety, I made the def millDiscover abstract, because it cannot really be implemented in abstract superclasses or traits (due to is macro implementation) so it really needs to be implemented in every concrete object. This should avoid a common issue where we forget to override it in the test suite and command resolution wouldn't work.

The flattening out of the Task[_] type hierarchy is a compromise. def ctx: Ctx = throws on the Task trait is gross. We lost the ability to distinguish between various subtypes of Task based on method return types, and instead have to go through other ad-hoc mechanisms like number-of-argument-lists. But in exchange for a bit of sloppiness in the type signatures, users can now use Task{ ... } to construct their most common tasks (targets), : Task[V] to annotate their types, and Task.sources and Task.dest for most common task APIs

This is a binary incompatible change and will have to go into 0.13.0. I tried to leave the existing T/Target/Input/etc. aliases in place, so hopefully it's mostly source compatible (though probably not 100%)

This PR is a bit messy, but important files to review include:

  • main/src/mill/package.scala, which re-writes the various type and val aliases
  • main/define/src/mill/define/Task.scala, which moves the various T.* operations from object Target onto a shared trait TaskCompanion that can be shared with object Task, and makes them all return Task[_] instead of a more specific subtype
  • main/define/src/mill/define/Discover.scala, which tweaks the class-task-name-discovery logic to make it look for Task instead of Target, and make it precise enough to work as the primary resolution mechanism (previously there were some bugs, that didn't matter when it was just used for error message reporting, but weren't acceptable now that it is becoming the primary way we find tasks in a module)
  • main/resolve/src/mill/resolve/ResolveCore.scala, which converts the task resolution logic to use the information already collected by the updated Discover macro
  • main/define/src/mill/define/BaseModuleTree.scala, which pre-computes a bunch of values used throughout the task resolution logic
  • main/resolve/src/mill/resolve/Resolve.scala has more changes to use BaseModuleTree pre-computed values

@lihaoyi lihaoyi changed the title [WIP] Experiment with renaming T to task Move T.* operations to Task.* Aug 8, 2024
@@ -28,7 +28,7 @@ object ApplicativeTests extends TestSuite {
value
}
}
@compileTimeOnly("Target.ctx() can only be used with a T{...} block")
@compileTimeOnly("Target.ctx() can only be used with a Task {...} block")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@compileTimeOnly("Target.ctx() can only be used with a Task {...} block")
@compileTimeOnly("Target.ctx() can only be used within a Task {...} block")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work yet; need to make changes in the mill-moduledefs repo. TBH I feel like just in-sourcing it again, since even when in-sourced people can depend on the artifact separately if necessary, and it makes doing these changes in one PR a lot more convenient

Copy link
Member

@lefou lefou Aug 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's a compiler module, it needs releases for every Scala version we want to use. That's the main reason we out-sourced it. We post-release for every new Scala 2.13 version.

It might be ok for a development version to use a in-sourced patched version, but I think the goal should be to push any changes back to the external project before we reach a stable version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we in-source it to the Mill repo but still keep the publishing separate? That would mean it's treated like the bridge modules, and would keep everything in one place while still preserving the per-scala-version releases and separate publishing

Copy link
Member

@lefou lefou Aug 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will work too, if it's well documented. We do these things only occasionally, there is no muscle memory for the process, so we need a good step-by-step documentation targeting the developer/publisher.

@lihaoyi lihaoyi changed the title Move T.* operations to Task.* Make T an alias for Task instead of Target, move T.* operations to Task.* Aug 9, 2024
Copy link
Member

@lefou lefou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is all the mill-moduledefs meta info removed. It does still have a dedicated release cycle, so we should manage it via the build.sc and generated BuildInfo.

If we release it with every Mill release, we also need to post-release for newer Scala versions all those plugins. For example, external plugin authors need to build against older Mill versions to ensure they don't accidentally use newer API. But they sometimes want and sometimes must update the Scala version.

Comment on lines 5 to 8
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NullaryMethod {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API doc needed. What is @NullaryMethod for?

@lihaoyi
Copy link
Member Author

lihaoyi commented Aug 10, 2024

I think I've come up with a way go avoid modifying the compiler plugims by moving the info tonthe discover macro. That will let us avoid having to republish them, and also avoid failure modes of people forgetting to include the plugins in their published mill plugins. Will see if i can make it work

lihaoyi added a commit that referenced this pull request Sep 18, 2024
…3565)

Essentially a minimized binary-compatible version of
#3356, renaming all the factory
methods for various tasks to follow more standard naming conventions:

* `T {...}` ->  `Task {...}`
* `T.command {...}` ->  `Task.Command {...}`
* `T.input {...}` ->  `Task.Input {...}`
* `T.source {...}` ->  `Task.Source {...}`
* `T.sources {...}` ->  `Task.Sources {...}`
* `T.persistent {...}` ->  `Task.Persistent {...}`
* `T.task {...}` ->  `Task.Anon {...}`

The type `T[_]` remains an alias for `Target[_]`, and `Task{ ... }`
returns a `T[_]`, to maintain binary compatibility. Not quite ideal but
can probably be hand-waved away until Mill 0.13.0 when we are allowed to
break binary compatibility.

All the `T.*` operations have been duplicated to `Task.*` by sharing
them via a `trait TargetBase`, except the factory methods which were
copied over and upper-cased while the old version deprecated. I have
updated all the code and examples to use `Task` instead of `T` where
relevant. The only exceptions are the `implicit def apply`s which needed
to be manually copied without the `implicit` (otherwise the multiple
implicits cause ambiguity).

This gets us most of the user-facing benefits of
#3356 without the bin-compat
breakage: users no longer see an odd `T { ... }` syntax in the docs and
in their build files, and now see `Task { ... }` which should be much
more familiar. Although it does not allow us to do the type-hierarchy
cleanups that the other PR provides, it's still worth doing so we can
get it in in 0.12.0

The old `T { ... }` and `T.*` syntaxes should continue to work, and are
exercised via the bootstrap tests as they continue to be used in Mill's
own build. This PR should be source compatible to avoid migration pains,
and given the prevalence of `T` everywhere we probably should just
support it forever
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[RFC] Rename T{...} and T[_] to task{...} and Task[_], standardize "Task"/"Target" terminology around "Task"
2 participants