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
Draft
Conversation
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>
a68989d to
b9fafe3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #26231
Problem
Changing a case class field type causes a
NoSuchMethodErrorat runtime because the file pattern-matching on it is not recompiled by Zinc.Scala 3 generates def
unapply(x: C): C = xfor 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_1is 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 torecordTreethat records each product selector (_1,_2, …) as a used name viaaddMemberRefDependency. Note:fun.tpeis aTermRef, so.widenis needed before.finalResultTypeto reach the underlyingMethodTypeand 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; thatunapply's hash does not (documenting why the old code failed); and that_1now appears in the used names for a file that pattern-matches on the case class.