Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make
Target
type abstract to allow overriding by different concrete…
… implementations (#2402) This PR makes all of the `T.{apply, input, source, sources, persistent}` functions return the same `Target` type, so that you can easily override one with another. `T.{command, worker}` are still distinct types. ## Motivation This allows us to override one with another, e.g. override a `T.sources` with a `T.apply` if we want to replace source files with computed sources. ```scala trait Foo extends Module{ def thing = T.source(millSourcePath / "foo.txt") } trait Bar extends Foo{ def thing = T{ os.write(T.dest / "foo.txt", "hello") PathRef(T.dest / "foo.txt") } } ``` Currently, the workaround is to make `T.source` do the computation, as follows: ```scala trait Foo extends Module{ def thing = T.source(millSourcePath / "foo.txt") } trait Bar extends Foo{ def thing = T.source{ os.write(T.dest / "foo.txt", "hello") PathRef(T.dest / "foo.txt") } } ``` But the status quo is wasteful: it would begin hashing `foo.txt` at the start of every evaluation, it will watch `foo.txt` for changes, etc.. Even though we know it could never change since it's a generated file. This is because we are not allowed to change the method type of `Source` to `T[PathRef]` during the override, and thus we have to preserve all the `Source` characteristics even though we know they no longer apply With this PR, we can simply replace the `T.sources` with a `T{...}`, and Mill makes use of that information to avoid hashing/watching the `PathRef` unnecessarily ## Implementation 1. `NamedTask` and `NamedTaskImpl` were merged 2. `Target`'s logic was mostly hoisted into `NamedTask`, leaving `Target` an empty marker trait 3. `TargetImpl` is unchanged 4. `Input`, `Source` and `Sources` have been renamed `InputImpl`, `SourceImpl`, and `SourcesImplt` 5. All the functions that used to return `Source`/`Sources`/`Persistent`/etc. now return the same type `Target`, meaning that we can easily override one with the other. 6. I added stubs in `mill/define/package.scala` to make existing type annotations `: Sources`, `: Input`, etc. continue to work as type aliases to `Target[T]`, and our large codebase and test suite required relatively few changes 7. `Command`/`T.command` and `Worker`/`T.worker` continue to return their specific type `Command[T]`/`Worker[T]`, since they are not sub-types of `Target[T]`. The `Task` type hierarchy is considerably flatter and simpler: Before: - `Task` - `Task.Sequence`, `Task.TraverseCtx`, `Task.Mapped`, `Task.Zipped`, `T.task`, etc. - `NamedTask` - `NamedTaskImpl` - `Command` - `Worker` - `Input` - `Source` - `Sources` - `Target` - `TargetImpl` (also inherits from `NamedTaskImpl`) - `Persistent` After: - `Task` - `Task.Sequence`, `Task.TraverseCtx`, `Task.Mapped`, `Task.Zipped`, `T.task`, etc. - `NamedTask` - `Command` - `Worker` - `Target` - `TargetImpl` - `PersistentImpl` - `InputImpl` - `SourceImpl` - `SourcesImpl` ## Testing Added some tests to `TaskTests.scala` to demonstrate and validate the behavior when people override with different types. This was previously a compile error I had to update the error message for the wrong number of param lists, from `T{...} definitions must have 0 parameter lists` to `Target definitions must have 0 parameter lists`, since we no longer know *which* target sub-type a method returns based on its signature ## Notes - Despite the overhaul of the type hierarchy, this should mostly be a transparent change. Hopefully it would be minimally source-incompatible with existing code, so even though there's a huge bin-compat breakage here, people upgrading shouldn't be too affected. Pull request: #2402
- Loading branch information