[OpenCB Project Fix] Fixes #26005: ClassTag synthesis for tvar with chained tvar bound#26007
Open
soronpo wants to merge 1 commit into
Open
[OpenCB Project Fix] Fixes #26005: ClassTag synthesis for tvar with chained tvar bound#26007soronpo wants to merge 1 commit into
soronpo wants to merge 1 commit into
Conversation
92 tasks
5f4908d to
7eaf582
Compare
When `synthesizedClassTag` is asked for a `ClassTag[E1]` where the only
constraint on `E1` is `E1 <: E` and `E` is itself an unsolved type
variable, the previous logic could not reach a ground instantiation:
- `isGroundConstr(fullUpperBound(E1.origin))` was false (the bound
contains an unsolved tvar, namely `E`).
- `isFullyDefined(tp, ForceDegree.all)` would default `E1` (and `E`)
to their lower bound (`Nothing`), producing a bottom type the
synthesizer rejects, yielding "No ClassTag available for E1".
The fix layers two new behaviours on top of the existing path:
1. Try `isFullyDefined(tp, ForceDegree.failBottom)` first. This
succeeds whenever the surrounding inference has pinned a real
answer (preserving the i23611 backward-compatible behaviour) but
bails when the only choice would be a bottom default.
2. If `failBottom` bails and the chain of unsolved tvars in the
upper-bound direction reaches a ground type (e.g. `E <: Throwable`),
instantiate the chain's parent tvar from that ground bound and
then instantiate the original tvar from the now-ground upper bound.
3. Fall back to `ForceDegree.all` last, preserving original behaviour
when neither chain-walking nor `failBottom` apply.
The chain walker (`forceGround`) handles the AndType case where the
constraint is recorded as `TypeVar(E) & TypeParamRef(E)` (logically a
single tvar) and recurses through TypeVar/TypeParamRef forms, with a
`seen` set to avoid infinite loops on bidirectionally-equated tvars.
Surfaced by VirtusLab community-build for `dobrynya/zio-jms`:
https://github.com/VirtusLab/community-build3/actions/runs/25237182777/job/74005934723
Adds tests/pos/i26005.scala as a regression test.
https://claude.ai/code/session_01RYTXheYmkggs3YCK3WHZno
7eaf582 to
403b857
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.
When
synthesizedClassTagis asked for aClassTag[E1]where the only constraint onE1isE1 <: EandEis itself an unsolved type variable, the previous logic could not reach a ground instantiation:isGroundConstr(fullUpperBound(E1.origin))was false (the bound contains an unsolved tvar, namelyE).isFullyDefined(tp, ForceDegree.all)would defaultE1(andE) to their lower bound (Nothing), producing a bottom type the synthesizer rejects, yielding "No ClassTag available for E1".The fix layers two new behaviours on top of the existing path:
isFullyDefined(tp, ForceDegree.failBottom)first. This succeeds whenever the surrounding inference has pinned a real answer (preserving the i23611 backward-compatible behaviour) but bails when the only choice would be a bottom default.failBottombails and the chain of unsolved tvars in the upper-bound direction reaches a ground type (e.g.E <: Throwable), instantiate the chain's parent tvar from that ground bound and then instantiate the original tvar from the now-ground upper bound.ForceDegree.alllast, preserving original behaviour when neither chain-walking norfailBottomapply.The chain walker (
forceGround) handles the AndType case where the constraint is recorded asTypeVar(E) & TypeParamRef(E)(logically a single tvar) and recurses through TypeVar/TypeParamRef forms, with aseenset to avoid infinite loops on bidirectionally-equated tvars.Fixes #26005
How much have you relied on LLM-based tools in this contribution?
Extensively, but first want to see if CI is green. This is a long-standing issue blocking a project on the community build.
How was the solution tested?
New automated tests (including the issue's reproducer, if applicable)