Skip to content

[OpenCB Project Fix] Fixes #26005: ClassTag synthesis for tvar with chained tvar bound#26007

Open
soronpo wants to merge 1 commit into
scala:mainfrom
soronpo:claude/fix-scala3-community-build-lubiS
Open

[OpenCB Project Fix] Fixes #26005: ClassTag synthesis for tvar with chained tvar bound#26007
soronpo wants to merge 1 commit into
scala:mainfrom
soronpo:claude/fix-scala3-community-build-lubiS

Conversation

@soronpo
Copy link
Copy Markdown
Contributor

@soronpo soronpo commented May 8, 2026

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.

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)

@soronpo soronpo changed the title Fixes #26003: ClassTag synthesis for tvar with chained tvar bound Fixes #26005: ClassTag synthesis for tvar with chained tvar bound May 8, 2026
@soronpo soronpo force-pushed the claude/fix-scala3-community-build-lubiS branch 2 times, most recently from 5f4908d to 7eaf582 Compare May 8, 2026 18:29
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
@soronpo soronpo force-pushed the claude/fix-scala3-community-build-lubiS branch from 7eaf582 to 403b857 Compare May 8, 2026 18:34
@soronpo soronpo marked this pull request as ready for review May 8, 2026 19:42
@soronpo soronpo changed the title Fixes #26005: ClassTag synthesis for tvar with chained tvar bound [OpenCB Project Fix] Fixes #26005: ClassTag synthesis for tvar with chained tvar bound May 11, 2026
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.

Incomplete fix for #23611: ClassTag synthesis still fails when type variable's upper bound is itself a type variable

2 participants