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

Feed to whole classpath to scalafix and fix ExplicitResultTypes #172

Merged
merged 7 commits into from
Jan 7, 2024

Conversation

lefou
Copy link
Contributor

@lefou lefou commented Dec 14, 2023

Feed the whole classpath to scalafix, which is needed for some scalafix rules like ExplicitReusltTypes to work correctly.

Added the ExplicitResultTypes rule to the integration test to reproduces the initial issue discovered in a Mill PR (github.com/com-lihaoyi/mill/pull/2922) and make sure we correctly handle it.

Fix #174

This PR also raises the minimal supported Mill version to 0.10.15, as this is the fist release which makes the ScalaModule.semanticDbEnablePluginScalacOptions task accessible for inheriting modules.

@lefou lefou changed the title Add test for ExlicitResultTypes fix Add (failing) test for ExlicitResultTypes fix Jan 5, 2024
@lefou lefou changed the title Add (failing) test for ExlicitResultTypes fix Feed to whole classpath to scalafix and fix ExlicitResultTypes Jan 6, 2024
@lefou lefou changed the title Feed to whole classpath to scalafix and fix ExlicitResultTypes Feed to whole classpath to scalafix and fix ExplicitResultTypes Jan 6, 2024
@lefou
Copy link
Contributor Author

lefou commented Jan 6, 2024

I tried to fix the issue by applying the findings of @bjaglin in #174 (comment) and the details of the Javadoc comments for ScalafixArguments.withClasspath:

     /**
     * @param classpath Full Java classpath of the module being fixed. Required for running
     *                  semantic rewrites such as ExpliticResultTypes. Source files that
     *                  are to be fixed must be compiled with the semanticdb-scalac compiler
     *                  plugin and must have corresponding <code>META-INF/semanticdb/../*.semanticdb</code>
     *                  payloads. The dependency classpath must be included as well but dependency
     *                  sources do not have to be compiled with semanticdb-scalac.
     */
    ScalafixArguments withClasspath(List<Path> classpath);

This indeed makes the ExplicitResultTypes rule work, but now another integration test custom fails with the following error:

[info] compiling 1 Scala source to /home/runner/work/mill-scalafix/mill-scalafix/out/itest/0.10.12/test.dest/custom-rule/out/project/compile.dest/classes ...
Error:  bad option: -P:semanticdb:synthetics:on
Error:  one error found
1 targets failed
project.compile Compilation failed

@lefou
Copy link
Contributor Author

lefou commented Jan 6, 2024

but now another integration test custom fails with the following error:

[info] compiling 1 Scala source to /home/runner/work/mill-scalafix/mill-scalafix/out/itest/0.10.12/test.dest/custom-rule/out/project/compile.dest/classes ...
Error:  bad option: -P:semanticdb:synthetics:on
Error:  one error found
1 targets failed
project.compile Compilation failed

Although, I have now idea, why this didn't fail before, I think the issue is, that we can't feed extra semanticDb options via scalacOptions, as the semanticDb plugin isn't loaded for normal compilation.

Instead, we need to customize the options specific for the semanticDbData task. Turns out, in Mill we never anticipated such a need an haven't provided a dedicated semanticDbOptions target.

But I think we can override the semanticDbEnablePluginScalacOptions target to get the same result.

@lefou
Copy link
Contributor Author

lefou commented Jan 6, 2024

Looks like the semanticDbEnablePluginScalacOptions target is private in Mill 0.10, so I can't make the custom-rules feature work out-of-the-box.

Here are our options:

  1. Don't support custom options for semanticdb for Mill 0.10
  2. No longer support Mill 0.10 in mill-scalafix
  3. Fix Mill 0.10 and make semanticDbEnablePluginScalacOptions protected as in Mill 0.11
  4. Introduce a new semanticDbOptions target in Mill 0.11 and backport it to Mill 0.10.

I think option 1. is the most straight forward actionable options. Additionally, we might strive for option 4.

Another workaround for the custom rules features in Mill 0.10 might be to explicitly add the semanticDb plugin to the project, such that it is available in the default compilation and can be customized via scalacOptions.

lefou added a commit to com-lihaoyi/mill that referenced this pull request Jan 6, 2024
This makes it accessible for downstream tooling.

* Use case: joan38/mill-scalafix#172

Essentially, this is a backport of what we have in Mill 0.11, so this PR
is in the spirit of the previous pull requests we merged.

Pull request: #2951
@bjaglin
Copy link
Contributor

bjaglin commented Jan 6, 2024

  1. My understanding is that on master, it's possible but cumbersome to run rules that require -P:semanticdb:synthetics:on (since adding the option prevents regular compiling, so you can only do it for one-shot rule execution, outside CI). Of course, on Mill 0.11, it's better as it's possible without compromising the compile task just like shown in 9a72ab9, but it was probably unknown because undocumented until now (and unrequested anyway?).
  2. -P:semanticdb:synthetics:on turns out to be needed by the custom rule tested here (which is was a popular one, granted), but it's not a very commonly needed flag from what I know, and I can't think of any other important semanticdb-scalac flag for scalafix rules.

As a result, from my outsider Scalafix perspective (not taking into account Mill 0.10 vs 0.11 usage), I'd say that (1) is the most pragmatic choice as downside is very limited for a big upside: better enable the built-in ExplicitResultTypes with the downside of preventing a community rule to run with Mill 0.10 than waiting to find a regression-less solution.

@bjaglin
Copy link
Contributor

bjaglin commented Jan 6, 2024

Looking at the fix, I was wondering how the incremental compiler handles a regular compilation concurrent to the semantidcb-output one, as introduced by this PR. A quick look at out/itest/0.11.1/test.dest/fix/out/chrome-profile.json seems to indicate that they are indeed 2 separate processes as the task graph shows, most likely serialized by the zinc worker.

How much do they benefit from each other and how does their order impact the incremental gain? Considering the overhead of semanticdb-scalac, I assume the design rationale behind the 2 separate compilations was that it is beneficial to postpone the semantidcb-output one (potentially to "never"). However, for cold scalafix invocations, the extra compilation may become a significant overhead on large projects.

Another workaround for the custom rules features in Mill 0.10 might be to explicitly add the semanticDb plugin to the project, such that it is available in the default compilation and can be customized via scalacOptions.

This functional workaround would also address the performance consideration above I believe. I assume mill-scalafix could then override semanticDbData to rely on compile so that BSP or other clients of that task benefit from the eager semanticdb generation? Sorry if this is slightly off topic, I went down the rabbit hole of Mill internals with this issue 😄

@lefou
Copy link
Contributor Author

lefou commented Jan 7, 2024

In Mill, semanticDbData is a complete separate zinc invocation, with the only purpose to produce semanticDb files. The idea is to be able to make semanticDb available in any modules, without the need to change and impact the regular compile workflow. There are various cons with having semanticDb enabled with the default compilation, like compilation slowdown or potentially invalid compiler options (-Werror). On the other side, semanticDb is needed in Metals or other toolchains like scalafix, hence I'm rather proud to found a way to produce it without affecting the normal compilation at all. There's a even longer story. If you interested, you can read the comments of PR com-lihaoyi/mill#1977, which initially introduced it in Mill, and follow the pointers.

@lefou
Copy link
Contributor Author

lefou commented Jan 7, 2024

How much do they benefit from each other and how does their order impact the incremental gain?

Both tasks are independent but take profit from previous runs (they are incremental) as well as the upstream zinc analysis data.

Considering the overhead of semanticdb-scalac, I assume the design rationale behind the 2 separate compilations was that it is beneficial to postpone the semantidcb-output one (potentially to "never"). However, for cold scalafix invocations, the extra compilation may become a significant overhead on large projects.

The semanticDb compiler invocation just stops after the phase that generates/writes the semanticDb data, so the overhead is lower then you might think. And in a large project with multiple modules, the compiler is already hot after a small amount of module compilations, so it's not that bad.

Another workaround for the custom rules features in Mill 0.10 might be to explicitly add the semanticDb plugin to the project, such that it is available in the default compilation and can be customized via scalacOptions.

This functional workaround would also address the performance consideration above I believe. I assume mill-scalafix could then override semanticDbData to rely on compile so that BSP or other clients of that task benefit from the eager semanticdb generation?

Mill could be more clever in semanticDbData task and detect, whether the compile task already produced all of it. But that's not that simple and probably not worth the work and resulting code complexity. (At least, there is no open request for it.)

Sorry if this is slightly off topic, I went down the rabbit hole of Mill internals with this issue 😄

No problem and Kudos for doing that. I hope you enjoyed it and try out Mill someday.

@lefou
Copy link
Contributor Author

lefou commented Jan 7, 2024

Here are our options:

1. Don't support custom options for semanticdb for Mill 0.10
2. No longer support Mill 0.10 in mill-scalafix
3. Fix Mill 0.10 and make `semanticDbEnablePluginScalacOptions` protected as in Mill 0.11
4. Introduce a new `semanticDbOptions` target in Mill 0.11 and backport it to Mill 0.10.

I went with option 3. I tagged Mill 0.10.14 which will contain com-lihaoyi/mill#2951. Once, it is released, I will update this PR.

@lefou lefou marked this pull request as ready for review January 7, 2024 14:30
@@ -12,7 +12,7 @@ import mill.scalalib.api.ZincWorkerUtil.scalaNativeBinaryVersion
import mill.scalalib.publish.{Developer, License, PomSettings, VersionControl}
import scalalib._

val millVersions = Seq("0.10.12", "0.11.1")
val millVersions = Seq("0.10.15", "0.11.0")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We depend on new API since Mill 0.10.15.

@@ -3,6 +3,6 @@ import com.goyeau.mill.scalafix.ScalafixModule
import mill.scalalib._

object project extends ScalaModule with ScalafixModule {
def scalaVersion = "2.13.6"
def scalaVersion = "2.13.10"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Necessary to make test succeed.

@bjaglin
Copy link
Contributor

bjaglin commented Jan 7, 2024

I'm rather proud to found a way to produce it without affecting the normal compilation at all. There's a even longer story. If you interested, you can read the comments of PR com-lihaoyi/mill#1977, which initially introduced it in Mill, and follow the pointers.

Pretty cool indeed! I'll definitely keep an eye on whether you manage to pull a -Ystop-after-like trick for dotty, and consider following the same pattern in sbt-scalafix (I wouldn't change anything at this time for Scala 2.x).

And in a large project with multiple modules, the compiler is already hot after a small amount of module compilations, so it's not that bad.

Makes sense. #175 is a lower-hanging fruit anyway if performance ever becomes a concern.

I hope you enjoyed it and try out Mill someday.

I definitely did enjoy the design. At the very least I'm gonna watch the mill-scalafix repo and chime-in when I can help. Thanks for your answers.

Copy link
Contributor

@bjaglin bjaglin left a comment

Choose a reason for hiding this comment

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

Looks good from a Scalafix perspective 👍

@@ -25,7 +25,7 @@ trait ScalafixModule extends ScalaModule {
T.ctx().log,
repositoriesTask(),
filesToFix(sources()).map(_.path),
Seq(semanticDbData().path),
classpath = (compileClasspath() ++ localClasspath() ++ Seq(semanticDbData())).iterator.toSeq.map(_.path),
Copy link
Contributor

Choose a reason for hiding this comment

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

A potential further improvement to reduce the added overhead would be to avoid triggering any compilation if none of the rules is semantic (rulesThatWillRun.exists(_.kind.isSemantic)) like it's done in sbt-scalafix. Not sure how/if this (dynamic tasks in sbt) can be achieved in Mill.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could be tricky, as the task graph can't be changed once it has started. But we have some options at construction time, depending on how lightweight (fast) a check can be.

Anyways, this should be done in a separate PR.

@lefou
Copy link
Contributor Author

lefou commented Jan 7, 2024

I tested this PR on com-lihaoyi/mill#2922 and it now properly changes the code with missing type annotations.

@joan38 joan38 merged commit 3662d3e into joan38:main Jan 7, 2024
1 check passed
@joan38
Copy link
Owner

joan38 commented Jan 7, 2024

Thanks @lefou and @bjaglin!
I will make a release.

@lefou lefou deleted the explicit-result-type branch January 7, 2024 21:26
@joan38
Copy link
Owner

joan38 commented Jan 8, 2024

I released 0.3.2

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.

ExlicitResultTypes fix is not working
3 participants