Port Mill build.sc files to Scala 3
- Start: Aug/2024, End: Oct/2024
 - 29 minute read
 - all projects
 
Project URL this project is completed.
Summary: Enhance the Mill build tool (github.com/com-lihaoyi/mill) by enabling users to write build.sc files with Scala 3 syntax and libraries.
About the Project
The Mill build tool lets users write build pipelines in the Scala programming language. It provides a DSL that makes it simple for users to build a graph of tasks, and dependencies between them, and Mill provides a command line interface to invoke these tasks.
Before starting the project in August 2024, Mill build definitions were locked to the latest Scala 2.13 version. This prevents users and plugin authors from benefitting from the latest additions in Scala 3, which first launched in 2021.
The goal of this project is to make it possible to use the latest Scala 3 version (as of writing Scala 3.5.0) to define Mill builds. This isn't a standard migration effort however, as Mill customises the language in various ways:
- Macros to support the direct-style task DSL. (Macros have a brand new API in Scala 3)
 - Macros to reflect the tasks to the CLI resolution mechanism.
 - Custom Scala parser to detect special imports such as dependencies or the meta-build.
 - Compiler plugins to support script files and Module definitions.
 - Bytecode analyzers to detect changes in the build.
 
Current Status
My work and open Pull Request was transferred to the Mill project maintainers on November 1st 2025, where the PR remained open while the 0.12.0 Mill release was co-ordinated. The PR was merged On February 1st 2025, concluding the project.
Done:
- ✅ Check that bytecode analyzers work with Scala 3
 - ✅ Discover macro
 - ✅ Applicative macro
 - ✅ Caller macro
 - ✅ Cross.Factory macro
 - ✅ EnclosingClass macro
 - ✅ Task macros
 - ✅ Cacher macro
 - ✅ Moduledefs compiler plugin (override inferrence)
 - ✅ All core Mill modules compile with Scala 3.5.0
 - ✅ Fix Zinc reporter patch linenumbers of build scripts
 - ✅ Fix all library dependencies
 - ✅ Identify any possible hidden bugs discovered by testing Scala 3 code.
 - ✅ Support new Scala 3 syntax in build.sc files
 
Further work left open:
- 🚧 Port acyclic plugin to Scala 3
 
Here is a gif of compiling a Mill project where the build.sc file is compiled with Scala 3.5.0:

Progress Diary
Below is a log of the progress made on this project.
2024-aug-05
- Setup project, reading build definition
 - Asked Haoyi about acyclic plugin
- Conclusion - ignore for now, nice to have
 - Remove it from compile deps when scalaversion is 3.x
 
 - Build definition is pretty big - so i discover the entry points to try and work backwards
mill.runner.MillMain,mill.runner.MillServerMain, andmill.main.client.MillClientMain.
 - Also look at understanding how the inprocess example test suites are invoked,
- Starting from command 
./mill 'example.basic[1-simple].local'I see in the build thatexample.basicis a cross module, and inside that thelocalmodule extendsTestModule. - I show the 
discoveredTestClassestarget onexample.basic[1-simple].localand find that it is runningmill.example.ExampleTestSuite. 
 - Starting from command 
 - Understanding how 
mill.example.ExampleTestSuiteworks.- See that in 
localmode it invokes the mill launcher produced bydev.launcherrepeatedly. (which invokes themill.main.client.MillClientMainclass) mill.runner.MillServerMaingets started by MillClientMain, before running the main ofmill.runner.MillMain
 - See that in 
 - Looking at 
MillBuildBootstrap- Found the part where Mill parses build.sc files.
 
 - Try to build the 
dev.runnertarget by changingscalaVersionto3.5.0-RC6. - First update the tasks in the 
bridgecross module to resolve the dotty sbt-bridge module. - Edits to 
main.apimodule:- Had to fix some extension method problem with Array.map
 - Manually define Mirror for JarManifest (to derive upickle RW) because it isn’t a case class.
 - Agg had some new problems automatically mixing-in collect and zipWithIndex, so they are overridden manually. Also ++ is final so override in Agg is removed
 
 - Edits to 
main.utilmodule:- Use 2.13 binary suffix in coursier dep
 - Fix extension method issue with coursier.ResolutionExtensions
 
 - Edits to 
main.definemodule:- Remove 
mill-moduledefsplugin temporarily - will be necessary later to supportoverrideinsertion. - Don’t include 
scala-reflecton classpath - Use 2.13 binary suffix in jarjarabrams dep
 - Comment out macros definitions in 
Task.scala 
 - Remove 
 
2024-aug-06
- Edits to 
main.definemodule:- (identified fastparse usage in 
Reflect.scala) - Comment out macros in 
Discover.scala - Comment out macros in 
Applicative.scala - Copy implementation of 
mill.moduledefs.CacherforModule.BaseClass. - Comment out macro of 
Caller.scala - Comment out Factory macro in 
Cross.scala - Comment out macro of 
EnclosingClass.scala - Comment out macro impl in 
Task.scala 
 - (identified fastparse usage in 
 - Edits to 
main.evalmodule:- Change syntax of explicit context parameter passing
 
 - Edits to 
main.resolvemodule:- Change syntax of explicit context parameter passing
 - (identified fastparse usage in 
ExpandBraces.scala) - (identified fastparse usage in 
ParseArgs.scala) - Change syntax of pattern matching for comprehension
 
 - Edits to 
mainmodule- Remove scala-reflect from compile classpath
 - Copy implementation of 
mill.moduledefs.ScaladocforMainModule.scala. - Make 
Target.loginline (inmain.define) so that it drops the ctx argument (avoiding compiletimeonly errors) - Make 
Applyer.ctx()inline (inmain.define) so that it drops the ctx argument (avoiding compiletimeonly errors) - Make 
Applyable.apply()inline (inmain.define) so that it drops the handler argument (avoiding compiletimeonly errors) - Temporarily remove ctx argument from 
ClassLoader.createin (inmain.api) 
 - Edits to 
scalalibmodule:- Use 2.13 binary suffix on 
scalafmt-dynamicdep - Temporarily add 
@mainargs.mainannotation toIvyDepsTreeArgsso compilation continues - opened https://github.com/com-lihaoyi/mainargs/issues/143 to track problem - Fix syntax of lambda parameters
 - Reimplement 
scala.tools.nsc.io.Streamable.bytes - Generate Mirrors for types in 
JsonFormatters.scala - (identified fastparse usage in 
VersionParser.scala) - Comment out majority of 
PublishModule.scala 
 - Use 2.13 binary suffix on 
 - Now I am seeing that 
Taskis pretty much everywhere at this point, and too much to comment away/removeCtxfrom signatures, so likely not going to progress much without implementing it properly.- Remove any 
compileTimeOnlyannotations (for now) - Restore signatures of 
ctxaccessors inTasks.scala, andApplicative.scala, and restoreClassLoader.create. - Restore all code in  
PublishModule.scala - Add import to help derive RW for 
PublishData. 
 - Remove any 
 - Edits to 
scalalib.workermodule:- Change classpath of zinc module and scalap
 
 - Edits to 
scalajslibmodule:- Generate Mirrors for classes and objects in 
ScalaJSApi.scalaandReport.scala 
 - Generate Mirrors for classes and objects in 
 - Edits to 
bspmodule:- Generate mirrors for 
BspServerResult 
 - Generate mirrors for 
 - Edits to 
codesigmodule- Fix syntax and type inference
 
 - Edits to 
runnermodule- Temporarily remove 
linenumbersdependency - Fix lambda syntax
 - Fix explicit context bound passing
 - (identified fastparse usage in 
Parsers.scala) - Temporarily disable mainarg parsing for 
MillCliConfig. (because of a crash caused by a type mismatch in default parameters) 
 - Temporarily remove 
 - Edits to 
devmodule:- Comment out some contrib deps not necessary to run example.basic test suite,
 - (also some minor fixes to classpath of contrib.bloop, and syntax of contrib.buildInfo)
 
 - Now I can run 
example.basic[1-simple].localbut it fails due to all the???I added in.- Primarily the 
MillCliConfigmainargs parser - so we can work backwards from this. 
 - Primarily the 
 
2024-aug-07
- I cloned a local version of mainargs to try and debug the problem in 
MillCliConfig.- First I tried to print the actual types of the arguments that were mismatched, it turned out I got a 
Leftover[String]whenFlagwas expected. - Then i printed the parameters of the apply method that was selected, it turned out it was the overloaded “shim” apply method.
 - So I published locally a fix to mainargs to select the apply method where the names of parameters match the class constructor. (in reality perhaps we should invent a new annotation to deterministically declare which method to pick for the class parser?)
 - Also took the opportunity to create a default main annotation if none was found. So i removed main annotations from 
MillCliConfigand fromIvyDepsTreeArgs 
 - First I tried to print the actual types of the arguments that were mismatched, it turned out I got a 
 - With the locally published mainargs, i updated the mill build to use it, and could revert any changes made previously and the build worked.
 - Now the next unimplemented error in 
example.basic[1-simple].localis theDiscovermacro. - Edits to the 
main.definemodule:- I implement the 
Discovermacro, it seems to be mostly portable exactly as before - i notice however it does a funny thing - it imports all the givenTokenReadersfrom themainmodule, which is not possible to do in quotes because they must be well typed at definition. - When i run the discover macro on 
RootModule.Foreignit crashes because for some reason the typeMembers returned by dotty includes the NoSymbol, so i had to explicitly filter that out - however that should be a bug in dotty. - Next error - there is an “reference to parameter 
bwas used outside its scope in inlining phase”, which is an internal dotty error so i can’t catch it in the macro. By searching forb:In both mill and mainargs, i found thatbis the parameter of a quoted lambda in themainargslibrary - because I renamed it tobSpookyand the error message changed accordingly. - Turns out it was solved by swapping the type arguments of createMainData,
 - Now the problem is that mainargs does not do varargs adaption - e.g. 
MainModule.clean - Implement in mainargs the varargs adaption trick copied from upickle.
 - Next I saw that the default argument parsing for mainargs was again incorrect when there are several overloaded commands. I ignore defaults in mainargs when the method has no defaults by checking for the flag HasDefaults.
 - Now there is one last discovery error: “
invalid new prefix = JavaModule.this.JavaModuleTests cannot replace (JavaModule.this : mill.scalalib.JavaModule) in type JavaModule.this.JavaModuleTests” not sure where this comes from, will need investigation. - The problem was to do with path dependent types - mainargs isn’t resolving default accessors properly, because it needs a prefix to select from. 
 - I implement the 
 
2024-aug-08
- Edits to the 
main.definemodule:- I filter out synthetic methods in the discover macro, (i found that a superaccessor was being generated as a command - due to an override)
 - also switch to only scanning module values in Discover.
 - In lieu of fixing mainargs, i manually created a Discover instance with code gen for 
MillBuildBootstrap (MillBuildRootModule.BootstrapModule)- which was a pain. - I fixed mainargs to select the default getter from the lambda parameter, which means it works for non-static getters. So i deleted the manual discover instance, and could restore any commented out Discover macro callsites.
 - Now all 
Discovermacro call sites are building (perhaps implementation is still not 1:1 with scala 2? Needs more testing) - Now the next 
unimplementedErrorproblem in theexample.basic[1-simple].localtest isCallermacro. 
 
2024-aug-09
Trying to implement the caller macro - it seems not possible to implement correctly in extends clauses - so i opened https://github.com/scala/scala3/issues/21358 - perhaps we should have 2 subclasses of Caller so one is for methods (where enclosing class is correct), and one for extends clauses (where we need to get the class outside the current class) - waiting on comment from Haoyi
- Based on comment - i remove the 
Caller.generatemacro, instead define an implicit within Module that returns itself - this will perform the equivalent as the macro - Now next unimplementedError in 
example.basic[1-simple].localisTarget.apply 
2024-aug-12
- Attempting to fix the Target.apply macro - for now i construct the TargetImpl object with placeholder values - now Applicative 
defaultApplyHandler(previously compiletimeonly) is being reached, so we do need to implement theTarget.applymacro to eliminate this. - Start implementing Task.apply (implicit conversion macro) - and implement detection of if target owner is private - but now i need two other macro definitions - Cacher.impl0 and Applicative.impl0
 - Implemented Applicative macro - needed to manually pass in the caller rather than 
c.internal.prefix- this isn’t part of quoted api. Also I needed to add more precise types to help with actually typing the inner expression. - This lead to the 
Target.taskmacro being next to fail, so I implemented that, followed byTarget.sources(sourcesImpl2), followed byTarget.inputsand finallyTarget.sources(sourcesImpl1). - Now at this point i get to 
MillBuildRootModule.lineNumberPluginClasspathin the example test, and it tries to evaluatedefaultApplyHandler- so either this macro wasnt handled yet, or there is a bug in the macro to not eliminate it. However it seems to just beTask.apply(with result input) is not implemented. - Next 
defaultApplyHandleris from ScalaModule.compile - which is the unimplementedT.persistentmacro - Next was failing the 
Target.worker. - Then 
Target.apply(of a Task) - Next only errors were to fix the 
???in coursier module, but now i have a test failure that isn’t to do with unimplementedError - so will need to debug that. But for now i will focus on restoring compileTimeOnly annotations to be sure rewrites are correct. - Now implementing all remaining Target macros
 - Determined that the fault is due to the default classpath of the mill bootstrap module - will have to enable more debugging info so i can see exactly what isn’t resolved.
 
2024-aug-13
- Classpath resolution is failing in an opaque manor, because it is just throwing an exception inside a task - so I added a new 
resolveDeps0method to theCoursierModule.Resolverthat returns its resolution as aResult, this means we can propagate resolution errors correctly to theScalaModule.scalaCompilerClasspathtask. - Identifying the missing dependencies as some compiler plugins so need to remove those temporarily.
 - Then ZincWorkerImpl was having issues resolving 
scala-libraryfrom the compiler classpath when building themill-buildtask - which is correct as the resolution of theLib.scalaRuntimeIvyDepsdeps was excludingscala-library- which i discovered by printing the deps before resolution. This exclusion comes fromMillBuildRootModule.resolveDepsExclusionsas it is part of the classpath of mill itself - so i made an exception to excluding the scala-library for the mill root build - perhaps there is a better and localised solution? - Next i discovered in running example test that the generated script file discover macro needed explicit TokenReader imports so added that.
 - Now i can actually run many integration tests out of the box - e.g. 
example.tasks[1-task-graph].local. - i am still relying on my locally published mainargs however - so should quickly submit some PR for that to be released. - With some integration tests passing locally I opened a PR to Mill
 - Some features which are necessary to pass more integration tests are
mill-moduledefscompiler plugin to infer override keywords, and insert annotations that record scaladoc comments. (although we can access the scaladoc of a method via macros? - it seems java reflection resolves this info in the task but maybe we could redesign)mill-runner-linenumberscompiler plugin to fix line numbers of trees (is this even allowed in dotty?)
 - Got encouragement to open a mainargs PR to add my fixes - so then prepared my changes for a PR - while adding restoring unit tests for vararg handling, i noted that the parsing at runtime was actually incorrect - so what was needed is to copy the Scala 2 implementation - convert the parameter type from 
T*toLeftover[T]to create theArgSig. Then at the callsite you still need a vararg value, but the argument will need to be converted fromLeftover[T]back toT*. I will add unit tests for the path dependent type handling of default args, Classparser for classes without@main, and the overloaded apply method in companion for classparser. 
2024-aug-14
- Spent the day adding unit tests to mainargs to prepare for a pull request, opening it at the end of the day.
 - Also forked the mill-moduledefs repo, fixed the build.sc to set up cross building both library and compiler plugin for scala 2.13 and 3.5.0.
 
2024-aug-16
- Mainargs PR was merged, released in version 0.7.2. I updated my Mill PR to include the new dependency and GitHub actions CI now records several integration tests passing, such as 
example.tasks[6-workers].local 
2024-aug-20
- Implemented the 
EnableScaladocAnnotationphase in the mill-moduledefs compiler plugin. It’s greatly simplified from scala 2 as dotty makes it easy to access documentation for symbols, and Its easier to create annotations. I had to make some tweaks - adjustrunsAfterto be”posttyper”rather than”parser”(standard plugin must be after typer). Also I had to adjustbuild.scagain to hardcode the artifact name due to the outer module now being a cross module. 
2024-aug-21
- Implemented the 
AutoOverridephase in mill-moduledefs, now the plugin is finished - opened a PR after some cleanups. - Fixed the 
millProjectModuleto have_3suffix by default - now all of example.basic integration tests are passing except4-builtin-commands- for some reason theshowUpdatescommand is failing - upon investigation - it seems that it is overloaded, and theDiscovermacro picks the wrong one (i.e. the deprecated one with no default arguments) - will need to fix this. - Went with the fix of filtering out deprecated methods in the Discover macro, which fixed the test. Now all 
example.basictests are passing locally. Next for integration tests would be theCrossmacro. - Next I discovered that my patch to build scripts to include the empty package prefix was wrong - multi build roots now mean that there can be nested packages in the prefix - so I adjusted the code generation there - now passing all of 
example.misctests. 
2024-aug-22
- mill-moduledefs PR was merged, changes released in 0.11.0-M1. I rebased my Mill PR, which revealed another type inference issue (solved with an explicit type).
 
2024-aug-23
- Began work on 
Cross.Factorymacro. I realised I could not proceed without using the experimental methodsSymbol.newClassandClassDef.apply. Moreover, these methods are not adequate because they do not allow to customise the primary constructor, which was necessary to add the Context parameter. I knew I would have to rely upon compiler internals - so I decided to implement a Shim interface in themill-moduledefslibrary, with an implementation provided by thescalac-mill-moduledefs-pluginlibrary. I tried this and realised that the plugin library isn’t on the classpath when loading macros, so this didn’t work. - I asked the Discord channel for advice and Haoyi said it would be permissible to use compiler internals even in the 
main.definemodule. 
2024-aug-27
- I implemented the Shims inside 
main.definemodule and finished theCross.Factorymacro 
2024-sep-04
- Cleaned the code and pushed my changes to implement the 
Cross.Factorymacro. 
2024-sep-06
- Investigated and fixed two problems related to the 
Discovermacro:- A. not scanning correctly the type argument of a cross module
 - The scala 2 method of an immediately invoked closure (to split bytecode into manageable size) was optimised away, so the large-project integration test was failing - changing this to an explicit def avoids the optimisation, so the test works again.
 
 - At this point - it would seem most test failures are now due to misconfiguration of classpath dependencies. I need to investigate the 
linenumberscompiler plugin, before I can attempt to support new scala 3 syntax. 
2024-sep-09
- Decided to try porting 
linenumberscompiler plugin, or find an alternative. - Dotty will not allow a plugin to mutate source positions before type checking. Therefore we should modify positions via the reporter.
- There are two reporters: one in the BSP module, and one in the zinc worker. We need to share where possible the logic to fix positions.
 
 - I copied some logic from the 
LineNumberPluginto theZincWorkerImpl(identify build files) - still need to compute the updated positions. There is a bit of help already via theManagedLoggedReporterwhich accepts a function argument to remap positions. 
2024-sep-10
- Worked on rendering the correct positions for the reporter in 
ZincWorkerImpl, and also replicating the style of the console error reporter from dotty. (Still remains to share/copy the logic in the BSP module’s reporter) 
2024-sep-11
- Fixed remaining 
integration.failuretests that check compiler error messages (note: line number, column, and “kind” of error were unchanged, but other elements of messages were compiler specific) - Moved 
integration.failure[things-outside-top-level-module]tointegration.featureto account for the fact that top-level definitions are generally allowed in Scala 3 (after approval from Haoyi). - Moving on, it seems that the CodeSig checker is incorrect (
integration.invalidation[codesig-hello]fails) - Fixed compilation issues in the unit tests for codesig 
main.codesig.test. - Now I can identify that at least 1 method hash test fails, and three call graph tests. - I analysed the byte code of the failing method hash test, and I see that the failing method fails due to changing the line where 
sourcecode.Lineis summoned. So my assumption is that something changed between scala 2 and 3. I then compared bytecode outputs of the programs when compiled by Scala 2 or 3. In scala 2 the macro generatesnew Line(n)but in Scala 3 it isLine.apply(n). I then went to the PR for adding CodeSig, and i see a footnote that the “new Line” pattern is special cased - so that will need to be fixed (either in sourcecode, or in CodeSig) - Fixing the special casing did help with the methodhash test, but the callgraph tests are still failing. In particular - for 
basic.18-scala-anon-class-lambda,complicated.8-linked-list-scala,realistic.4-actors,realistic.5-parser, - The problems with 
8-linked-list-scalaand4-actorswere caused by changes in inference semantics for private fields, and for overridden methods (which i fixed by updating the code of the test, rather than Codesig implementation). The5-parsertest will be much harder to validate as the internals of fastparse’s macro changed - I might just have to copy the new result and identify any regression when fixing other tests. 
2024-sep-12
- I determined that the problem with 
18-scala-anon-class-lambdais that its bytecode was dependent on how scala 3 does specialisation, so i replaced it and17-scala-lambdawith a similar SAM that isn’t specialised. - Then i changed 
5-parseras i assumed, and it was only additions, no changes of other methods, so i believe it is safe. So now allmain.codesig.testunit tests were passing - not explaining the problem in the integration tests. - Then I updated dependencies for scalatags and scalaj-http, to enable invalidation tests to run. By enabling some verbose logging i saw that for 
[codesig-hello]the class was recompiled after changing the implementation. So then I decided to compare the bytecode of compiling the build.mill file in scala 2 vs 3. It seems scala 3 has a different naming convention for lambda functions, which I will investigate as the cause of not tracking the changes. 
2024-sep-13
- Investigating the causes for 
[codesig-hello]to fail, I then run the same test on themainbranch (i.e. with Scala 2 implementation) - with debugging turned on, i can compare the outputs of themethodCodeHashSignatures.destin the failing part (i.e. changing the body offoohad no effect). I saw that inprettyCallGraph.jsonthat for some reason there is no call recorded to the no-arg lambda implementation of foo (which gets passed to Cacher), when there was in Scala 2 implementation. Eventually i checked the ignoreCalls filter inMillBuildBootstrapModuleand it was clear that the lambda function was ignored because it was treated as a “simple target” because it has no-args. This is a consequence of the lambda encoding of scala 3, which uses instance methods, not static ones. So i taught the filter to look for lambda methods and it passes the test. Next was failing[codesig-scalamodule]. Originally i could see output was including extra warnings due to indentation changes caused by inserting newlines as part of the test. So i corrected the indentation. The test still failed due to the old version ofsourcecodebeing on the classpath when compiling the build.mill file. I fixed this with an explicit dependency on sourcecode in the main.define module - I should check which module first depends on it. Now all codesig tests were passing. 
2024-sep-14
- To fix 
integration.invalidation[multi-level-editing].local, i noticed that the meta-build dependency on scalatags was bringing in a conflicting scala 2 version of sourcecode, preventing the build.mill file from compiling. I instead changed to a scala 3 version in the metabuild, and updated the scalatags dependency - I then re-enabled publishing of test deps for the playlib and scoverage contrib modules, fixing a few more tests
 - I toyed around with fixing 
integration.feature[plugin-classpath].local, but this seems harder to fix. Essentially the plugin brings in a conflicting old dependency of mill (i.e. with scala 2 binary version). Either i have to exclude the dependency and hope nothing breaks, or build the metabuild with a suitable scala 2 version. I parked this for now. 
2024-sep-15
- To fix 
integration.ide[gen-idea].local, I had to fix some xml generation code which relied upon implicit conversions that no longer work in Scala 3. I also had to update some checkfiles to account for the updated versions. - I enabled all contrib test module dependencies, fixing necessary compile errors.
 - Next, i fixed the 
integration.feature[init].localtest by updating the classpath of the Giter8 module in scalalib. I noticed that a dependency resolution error was not being reported so i ensure that it does. - I also removed the deadcode linenumbers module.
 
2024-sep-16
- Rebased the PR against Main, in which I had to tweak 
CodeGento account for the new structuring of json formatters and TokenReaders. CodeGenalso had to be tweaked to generate the Discover value in the same wrapper object as the user code. If not, then path dependent types would not match when trying to summonmainargs.TokenReaders[Foo]ifFoowas a custom type. This meant having to merge all the Discover values from child modules (frompackage.millfiles), and also substitutingclassOf[package_]forclassOf[package_.type]in the map.- ^ it might be possible in 
mainargsto substitute the prefix of the caller when summoningTokenReaderswhich would make this “hack” unnecessary, but it is unverified if it could work. - I also reimplemented the 
contrib.scoverage.apimodule to be java based, so it didn’t matter if the scala version was not compatible with the underlying scoverage library API. 
2024-sep-17
- Skipped the test 
integration.feature[plugin-classpath].localas it depends on a third party Mill plugin, not binary compatible with the scala 3 version of mill 
2024-sep-19
- Fix classpath resolution problems with contrib twirllib and contrib playlib
 
2024-sep-20
- Fix warnings about 
scala.AnyKindin thecontrib.proguardtests - Remove a test source from being compiled in 
example.thirdparty[3-mockito].localbecause it often failed in the CI (and it isn’t a necessary test to prove mill can substitute as the build tool) - Fix contrib.scoverage integration test
 - Filtered a problematic file from scalafmt checks (due to outdated scalafmt dependency)
 - Skipped checking scalafix in scala 3 sources, because the scalafix-interfaces library does not support reflectively loading the scala 3 scalafix library.
 - Patched Mima checks to correctly load the previous jar (accounting for platform suffix changes)
 - Then skipped the mima checks because there were 1000s of (expected) errors.
 - Patched any remaining failures due to not reformatting with scalafmt
 - Generated a patch file so that the 
ci/test-mill-bootstrap.shtest passed. - Managed to pass all tests in the CI on the PR #3369
 
2024-sep-21
- Created initial 
integration.feature[scala-3-syntax]test, which failed due to fastparse’s “scalaparse” being inadequate. - Fixed a problem with the new Zinc error formatter which broke ansi escape codes.
 - Experimented with reimplementing scalaparse from dotty’s grammar, but it was taking too long.
 - Investigated scalameta as a possible parser. Haoyi preferred to reuse dotty’s parser to reduce dependencies, it was also decided to only support the same version as mill is built with.
 
2024-sep-23
- I abstracted the necessary parsing operations 
splitScriptandparseImportHooksWithIndicesinto a trait, in a newrunner.worker-apimodule. The existingrunner.Parsersobject implements the trait for Scala 2, and the Scala 3 implementation was stubbed in arunner.workermodule, which an instance for should be loaded via reflection. - Next I abstracted the 
FileImportGraph.parseBuildFilesmethod over the new parser trait, which is called inMillBuildBootstrap(i.e. before we loadMillBuildRootModule). This posed a challenge for how to resolve and load themill-runner-workerlibrary reflectively. - I implemented the steps to load a worker instance are as follows:
- Resolve in 
MillMainthe dependenciesmill-runner-workerandscala3-compilerto a classpath and reflectively load the runner. - Pass the worker and resolved classpath to 
MillBuildRootModule.BootstrapModulewhich then re-uses it in its ownparseBuildFilestask. - The pre-computed worker classpath is then used in 
generateScriptSourcesto write the classpath to the generatedMillMiscInfowhich can then resolve the worker the next time the class is loaded. 
 - Resolve in 
 
2024-sep-24
- I implemented the first part of scriptSplitting - setting up the compiler to run only the parser phase, and report any errors - I implemented my own error formatting, as I wasn't sure if I would need to manipulate positions again.
 - I also tweaked the SkipScalafix Mill module so it can call the super.fix (by moving scala version from a task to a method)
 - I then realised it wasn’t necessary to run the proper compiler pipeline, I could initialise a context with a source file and reporter, then create an outline parser directly. I then traversed the output to extract the top-level packages, and top level statement strings by creating slices of the source file content for the span of each statement, and slices of the whitespace in between.
 
2024-sep-25
- I noticed there was a long delay in loading the standard definitions, so I wanted to see if it was possible to cache the initial context loading. I discovered by re-running split script 10000 times concurrently with futures that parsing had to be synchronized, but it was safe to share the same initial context
 - I then discovered that actually the standard definitions did not need to be initialised before parsing, so this saved another initial load time.
 - Implement import parsing - i had to fix a mistake in script splitting, because in dotty comma-separated imports are treated as separate statements. However they are not able to be parsed standalone from text, so I had to pack them all together as one statement
 - Implemented top-level object scanning (compatible with scala 2), however i needed to extract more information to be compatible with Scala 3 - a possible end marker (for renaming 
`package`topackage_), and also the problem with path-dependent types in the discover macro also needed to be applied to when the user provides an explicit top level object. This means i had to extract a suitable position to splice in code within the user code (i.e. within an object), so i extracted the position of the initial statement 
2024-sep-26
- Back to top-level object scanning, I realised that splicing at the top of user code would require more hacking with positions in the reporter, so I changed to extract the position of the final statement instead.
 - Then I rebased against main, and refactored my scala 3 parsing code to remove leftovers from implementing.
 - When running the 
ci/test-mill-bootstrap.shtest, it crashed when parsing the build with the new parser - this was due to not escaping EmptyTree from the outline object parser (if it turns out that it causes more pain then needed, we can revert to the regular parser) - i fixed the brokenatSpanmethod. - Pushed the scala 3 parser and syntax test to the main PR
 - Next, cleanup and refactoring the new 
runner.workermodule - Add 
runner.worker.testDeptodist0, making it available to integration testlocalmodules - Recomputed the 
ci/mill-bootstrap.patchfile - Noticed in the CI an error - mill files should allow expressions at the top-level - so i adjusted the parser to treat block statements as top-level statements.
 - I also noticed a crash caused by stray EmptyTree’s being returned in the parser, so escaped those
 
2024-sep-27
- Noticed another parsing error in the CI - need to filter out EmptyTree returned by scanning ModuleDef bodies
 - Fixed another minor issue (include backticks in package names)
 - Then i noticed that parsing mill statements with blockStatSeq prevents access modifiers, so i changed to templateStatSeq, (and skipping self-defs)
 - Added some stronger checking of spans on trees to ensure they exist in the source code.
 - Fixed parsing of backticked object names
 - Fixed the column number of parser error messages emitted by dotty.
 - Fixed the 
ZincWorkerImplreporter to account for user-written code that can appear after spliced__innerMillDiscoverdefinitions. - Escaped more 
EmptyTrees - Added a prelude to parser errors 
s”${file} failed to parse:\n” - CI on PR is now green 🎉
 - Validated that the BSP reporter forwards messages from the Zinc reporter, so no adjustment of positions was needed.
 
2024-oct-10
- Rebased the 
scala3-build-scagainstcom-lihaoyi:main. - Added 
casemodifier to objects inKotlinJSModule- this was new code, but without the modifier, we would need to generate Mirror objects as we did previously. - Adapt the 
Applicativemacro. Callerthistrees are no longer always anApplicative.Applyer, so resolvingtraverseCtxis no longer static. I made two reimplementations, first, resolve the method via quoted reflection API, and check the types manually. This avoids boilerplate at call site, but maybe less elegant. The second implementation passes a lambda that will construct the call to the righttraverseCtxmethod, when provided with argument Exprs. This second option requires more boilerplate at the call-site, but is more resilient to API changes. - fixed various type and syntax errors introduced by newer code added in the base branch.
 - Re-introduced the 
millDiscovermethod inRootModule.SubFolder, in the scala 3 code gen we need to explicitly generate discover in child modules, and merge in the main module. If it is possible to substitute the correct prefix inmainargslibrary, e.g. for custom types, we can revert this change. - After pushing to CI, there is a new test failure that tests output of inspect command for Modules - new code which might behave differently in Scala 3.
 
2024-oct-12
- Rebased again
 - Replaced the manually generated mirrors from 
manual_mirror_gen.scwith a new macro inmill.api.Mirrors.- User declares a single 
Root[Foo]given, which is generated by a macro. - Root stores all possible mirrors for subclasses of 
Foo(including itself). - It tries to be conservative in what it generates, so it doesn't generate mirrors for case class / case object.
 - another macro 
Mirrors.autoMirrorcan provide a givenMirror.Of[T], as long as there is a givenRoot[R], and it can construct a proof that a mirror forTis contained in the root. 
 - User declares a single 
 - The new macro saves a lot of boilerplate - i.e. only 1-2 lines per class hierarchy. (+ 1 line for imports)
 
2024-oct-13
- Haoyi mentioned that the linenumber changes should be propagated to the backend, so I investigated reviving the compiler plugin to edit positions.
 - Plugins can only be installed after type checking, so we would need to keep the Zinc reporter changes,
 - However I did an initial implementation that followed the same transform as performed by the Zinc reporter.
 - Unfortunately, the linenumbers were still incorrect in the bytecode.
 - I investigated the Scala 3 compiler backend to see how line numbers are generated. I saw that it is by looking at the line number of the offset in the position, in the source file of the current compilation unit, rather than in the source file of the position.
 - This is a problem because when we remap the positions in the plugin, we change both the offset, and the source file, so the computation in backend is incorrect.
 - It's still possible to mitigate this without patching the compiler, i.e. we can collapse the position to be zero-length.
- We have to do a two-stage transform: compute the line of the position in the original file,
 - then lookup the offset of the start of that line in the current source file.
 - this position will compute the right line number in the backend, however the reporter will render completely incorrect code. So, the reporter must be patched to re-direct the offset to the right line in the original script file.
 
 - The plugin would also have to patch line numbers computed by the 
sourcecode.Linemacro, and the name computed by thesourcecode.FileNamemacro, due to the difference in how macro expansion positions are computed. 
2024-oct-14
- The linenumber transform in the plugin is lossy and complex, so I made a patch to the compiler so the backend computes the line from the source file of the position.
 
