Skip to content

PC version override: decouple PC artifact version from build target#95

Open
rochala wants to merge 3 commits into
mainfrom
feat/pc-version-override
Open

PC version override: decouple PC artifact version from build target#95
rochala wants to merge 3 commits into
mainfrom
feat/pc-version-override

Conversation

@rochala

@rochala rochala commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

What

A launch-time knob to load the presentation compiler from a different scala3-presentation-compiler_3 version than the build target's own Scala version — and the classloader/dependency work needed to actually make such a PC load. Useful for testing a locally-built dotty PC against a real project.

The override (commit 1)

  • PcVersionOverride — resolved once at startup from system properties (env fallback):
    • -Dsls.pc.version=<v> / SLS_PC_VERSION — global default for every module.
    • -Dsls.pc.versions=<module>=<v>,… / SLS_PC_VERSIONS — per-module map, keyed by build-target display name, taking precedence over the global.
    • Resolution per module: per-module entry → global default → the target's own Scala version (unchanged behaviour when nothing is set).
  • PresentationCompilerProvider.get resolves the effective version per module and logs (slf4j) once per PC creation when an override is active.
  • Coursier's default ivy2Local repo means a publishLocal-ed snapshot resolves directly.

Making it actually load (commit 2)

The override alone wasn't enough — a PC built against a different Scala version than sls's bundled compiler failed to load. Three coupled fixes:

  • PcParentClassLoader — the PC's URLClassLoader used the host app loader as parent, so dotty.tools.* (e.g. InteractiveDriver) leaked from sls's bundled scala3-compiler (via scala3-tasty-inspector) while dotty.tools.pc.* loaded from the override jars → NoSuchMethodError. The new parent has the bootstrap loader as parent and shares only the host↔PC bridge packages (scala.meta.pc, org.eclipse.lsp4j, com.google.gson), forcing dotty.tools.* and the stdlib to load from the PC's own jars.
  • build.mill — bump mtags-interfaces 1.6.3 → 1.6.7 so the shared scala.meta.pc interface has the methods newer PCs call (e.g. sourcePathMode).
  • ServerImpl — 1.6.7 pulls a newer lsp4j where Diagnostic.getMessage is Either[String, MarkupContent]; a chimney transformer unwraps it to the String that lsp.Diagnostic.message expects.

Note for review

Commit 2 globally bumps the host's mtags-interfaces (and transitively lsp4j) to make a newer PC line work. That's an intended dependency move, but worth a sanity check that 1.6.7 is the right floor.

Why module-keyed

A project has multiple modules; pinning the PC per module (with a global fallback) lets you isolate debugging to one target in a cross-built project. The key is the build-target display name, logged on PC creation so it's discoverable.

Future work

The override is currently static (read at startup). The design supports making it runtime-updatable via an LSP endpoint (workspace/didChangeConfiguration or a custom command) with no restart — hold it in a Ref, update on the endpoint, call invalidateCompilers(). Not in this PR.

Test

PcVersionOverrideSpec covers map parsing (incl. malformed entries) and the precedence chain. ./mill sls.test is fully green.

🤖 Generated with Claude Code

rochala and others added 3 commits June 15, 2026 23:55
Adds a launch-time debugging knob to load the presentation compiler from a
different scala3-presentation-compiler_3 version than the build target's own
Scala version — without a recompile.

- PcVersionOverride: per-module map (`-Dsls.pc.versions=mod=ver,...` /
  SLS_PC_VERSIONS, keyed by build-target display name) over a global default
  (`-Dsls.pc.version` / SLS_PC_VERSION); resolution falls back to the target's
  own Scala version. Read once at startup.
- PresentationCompilerProvider.get resolves the effective version per module
  and logs when an override is in effect.
- Combined with coursier's ivy2Local default repo, a locally publishLocal-ed
  dotty PC build is directly usable.
- README: Debugging the presentation compiler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The version override alone wasn't enough to actually load a PC built against a
different Scala version than sls's bundled compiler. Three coupled fixes:

- PcParentClassLoader: the PC's URLClassLoader used the host app loader as
  parent, so dotty.tools.* (e.g. InteractiveDriver) leaked from sls's bundled
  scala3-compiler (via scala3-tasty-inspector) while dotty.tools.pc.* loaded
  from the override jars -> NoSuchMethodError. The new parent has the bootstrap
  loader as parent and shares only the host<->PC bridge packages
  (scala.meta.pc, org.eclipse.lsp4j, com.google.gson), forcing dotty.tools.*
  and the stdlib to load from the PC's own jars.

- build.mill: bump mtags-interfaces 1.6.3 -> 1.6.7 so the shared scala.meta.pc
  interface has the methods newer PCs call (e.g. sourcePathMode).

- ServerImpl: 1.6.7 pulls a newer lsp4j where Diagnostic.getMessage is
  Either[String, MarkupContent]; add a chimney transformer unwrapping it to the
  plain String that lsp.Diagnostic.message expects.

Also switches the PC provider's two log lines from IO.consoleForIO to slf4j,
matching the rest of the app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rochala rochala marked this pull request as ready for review June 21, 2026 18:48
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.

1 participant