Skip to content

Fix Scala parser losing type attribution when classpath lacks stdlib#7446

Merged
knutwannheden merged 2 commits intomainfrom
fix-scalaparser-bad-symbolic-refs-on-dotty-imports
Apr 22, 2026
Merged

Fix Scala parser losing type attribution when classpath lacks stdlib#7446
knutwannheden merged 2 commits intomainfrom
fix-scalaparser-bad-symbolic-refs-on-dotty-imports

Conversation

@knutwannheden
Copy link
Copy Markdown
Contributor

Motivation

mod build on openrewrite/rewrite fails to type-check 5 Scala files that import dotty compiler APIs, emitting errors such as:

Bad symbolic reference. A signature in scala3-compiler_3-3.8.4-RC2.jar(dotty/tools/dotc/core/Contexts.tasty)
refers to */T in object dotty.tools.dotc.core.Contexts which is not available.

Root cause: when a caller's classpath includes scala3-compiler but is missing one of the stdlib jars dotty relies on (scala-library, tasty-core), dotty's Definitions can't resolve foundational types like scala.Unit. Depending on where typechecking blows up, this surfaces as the Bad symbolic reference TASTy error above, or as a ClassCastException: NoSymbol cannot be cast to ClassSymbol in Definitions.UnitClass. Either way, the affected files lose type attribution entirely.

Because ScalaCompilerBridge itself is loaded from those same jars, it can locate them at runtime and self-heal — analogous to how JavaParser doesn't require callers to pass rt.jar.

Summary

  • ScalaCompilerBridge now computes the dotty classpath through a new buildClasspath helper that appends any missing stdlib jars (scala-library, tasty-core, scala3-compiler), located via ProtectionDomain.getCodeSource() on probe classes (scala.Option, dotty.tools.tasty.TastyFormat, dotty.tools.dotc.core.Contexts).
  • The self-heal only triggers when the caller's classpath already contains at least one scala*/tasty*/dotty* artifact. This preserves the narrow-classpath contract that ScalaTemplate depends on to keep type resolution out of template parsing.
  • Dedup is filename-based so callers that pass the same artifact under a different on-disk path (Maven vs. Gradle cache, symlinks) don't end up with duplicate entries.
  • New regression test DottyImportTypeAttributionTest feeds the bridge a classpath containing only scala3-compiler (mimicking the mod build scenario) and verifies that both an ordinary Scala file and one importing dotty.tools.dotc.core.Contexts.* come back with a populated typed tree.
  • Drive-by: drop :latest.release from three junit-jupiter test dependencies in rewrite-scala/build.gradle.kts. :latest.release was resolving to 6.1.0-M1 (milestone), whose junit-platform-commons removed CollectionUtils.toUnmodifiableList() — breaking the junit-platform-launcher:1.14.0 that Gradle's test runner uses. No tests in this module could run. Inheriting from the junit BOM (as the other modules do) restores consistency.

Test plan

  • ./gradlew :rewrite-scala:test — full module green, including the new DottyImportTypeAttributionTest and the pre-existing template/matching suites.
  • Manual mod build on a fresh openrewrite/rewrite checkout with the CLI's sibling-Java fix (moderneinc/moderne-cli#3704) to confirm the Bad symbolic reference errors no longer appear on the 5 files listed in the motivation.

The `:latest.release` constraint on `junit-jupiter-api`/`params`/`engine`
was resolving to `6.1.0-M1` (milestone) whose `junit-platform-commons`
dropped `CollectionUtils.toUnmodifiableList()`, breaking the
`junit-platform-launcher:1.14.0` that Gradle brings in for test
discovery. The resulting `NoSuchMethodError` prevented any test from
running in this module.

Drop the explicit version so the dependencies align with the junit BOM
used elsewhere in the build, matching how other modules declare these.
When a caller (e.g. `mod build`) passes a classpath that contains
`scala3-compiler` but is missing one of the transitive stdlib jars
dotty needs to operate (`scala-library`, `tasty-core`), dotty's
`Definitions` cannot resolve foundational types like `scala.Unit`.
The resulting corruption surfaces as either a `ClassCastException`
(`NoSymbol cannot be cast to ClassSymbol` in `Definitions.UnitClass`)
or the related `Bad symbolic reference` TASTy decoding error when the
source imports dotty compiler APIs, causing affected files to lose
type attribution entirely.

`ScalaCompilerBridge` is itself loaded from those stdlib jars, so it
can locate them at runtime via `ProtectionDomain.getCodeSource()` and
append any that are missing. The self-heal only triggers when the
caller's classpath already contains a Scala artifact, which preserves
the narrow-classpath contract that `ScalaTemplate` relies on to keep
type resolution out of template parsing.
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Apr 21, 2026
@knutwannheden knutwannheden merged commit 4e5656f into main Apr 22, 2026
1 check passed
@knutwannheden knutwannheden deleted the fix-scalaparser-bad-symbolic-refs-on-dotty-imports branch April 22, 2026 16:09
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants