Skip to content

Fix undercompilation when a case class field type changes and the class is used in a pattern match#26262

Draft
retronym wants to merge 2 commits into
scala:mainfrom
retronym:fix/26231-case-class-pattern-match-undercompilation
Draft

Fix undercompilation when a case class field type changes and the class is used in a pattern match#26262
retronym wants to merge 2 commits into
scala:mainfrom
retronym:fix/26231-case-class-pattern-match-undercompilation

Conversation

@retronym

@retronym retronym commented Jun 8, 2026

Copy link
Copy Markdown
Member

Fixes #26231

Problem

Changing a case class field type causes a NoSuchMethodError at runtime because the file pattern-matching on it is not recompiled by Zinc.

Scala 3 generates def unapply(x: C): C = x for case classes — its signature never changes regardless of field types. ExtractDependencies runs before PatternMatcher, so the _1(), _2(), … calls that PatternMatcher will emit are not yet in the tree. Since _1 is never recorded as a used name, Zinc doesn't recompile the dependent file when _1's return type changes.

Fix

Add a case UnApply(fun, ...) branch to recordTree that records each product selector (_1, _2, …) as a used name via addMemberRefDependency. Note: fun.tpe is a TermRef, so .widen is needed before .finalResultType to reach the underlying MethodType and then the case class type.

How much have you relied on LLM-based tools in this contribution?

Extensively, for investigation and implementation.

How was the solution tested?

New automated tests

Three new assertions in ExtractAPISpecification: that _1's API hash changes when the field type changes; that unapply's hash does not (documenting why the old code failed); and that _1 now appears in the used names for a file that pattern-matches on the case class.

retronym and others added 2 commits June 9, 2026 08:52
When a case class field type is changed, files that pattern-match on it
are not recompiled by Zinc, leading to NoSuchMethodError at runtime.

Root cause: Scala 3 generates `def unapply(x: C): C = x` whose signature
is stable regardless of field types. ExtractDependencies records `unapply`
as a used name but never `_1`, which is the method the generated bytecode
actually calls. Since `unapply`'s name hash never changes, Zinc skips
recompilation of the dependent file.

The three new tests document:
- `_1` API hash changes when field type changes (expected)
- `unapply` API hash does NOT change (the stable signature hiding the change)
- `_1` is absent from the used names recorded for a pattern-matching file (the bug)

Fixes scala#26231

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…26231)

In Scala 3, the compiler generates `def unapply(x: C): C = x` for case
classes.  Its signature is always `(C): C` regardless of field types, so
its API hash never changes when a field is renamed or retyped.

The PatternMatcher phase (which runs after ExtractDependencies) lowers
`case C(x)` to a direct call to the product selector `_1()`, `_2()`, etc.
Because ExtractDependencies ran before PatternMatcher, those selector
calls were never in the tree, so `_1` was never recorded as a used name
in the dependent file.  Zinc therefore saw no reason to recompile the
file when the field type changed, producing a NoSuchMethodError at
runtime.

Fix: in `AbstractExtractDependenciesCollector.recordTree`, add a case for
`UnApply` that records each product selector (`_1`, `_2`, …) found on
the unapply's result type as a member-reference used name.  When the
selector's return type changes (because the field type changed) its name
hash changes and Zinc correctly invalidates the dependent file.

The key detail is that `fun.tpe` in an `UnApply` node is a `TermRef`;
we must call `.widen.finalResultType` to reach the underlying case-class
type before asking `Applications.productSelectors` for the `_N` members.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@retronym retronym force-pushed the fix/26231-case-class-pattern-match-undercompilation branch from a68989d to b9fafe3 Compare June 8, 2026 22:53
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.

Undercompilation in all scala3/sbt versions leading to NoSuchMethodError on pattern matching

1 participant