Skip to content

Fix bridge generation when erased precedes a non-erased parameter#25923

Open
tanishiking wants to merge 1 commit intoscala:mainfrom
tanishiking:i25726
Open

Fix bridge generation when erased precedes a non-erased parameter#25923
tanishiking wants to merge 1 commit intoscala:mainfrom
tanishiking:i25726

Conversation

@tanishiking
Copy link
Copy Markdown
Member

@tanishiking tanishiking commented Apr 23, 2026

Fixes #25726.

The following crashed during erasure with
"assertion failed: bad adapt for this.run(): (x$0: Int): String":

class Ev
trait Svc[A]:
  def run(using erased Ev)(a: A): String
class ISvc extends Svc[Int]:
  def run(using erased Ev)(a: Int): String = a.toString

Since A is specialized to Int, the post-erasure signatures of the parent ((a: Object): String) and the child ((a: Int): String) differ, so a bridge method is generated in ISvc.

However, while bridge body has application based on post-erasure arguments, Erasure's typer expected bridge method to have pre-erasure form:

For example,
Bridges.bridgeRhs built a single flat Apply(this.run, [a]) one argument list, that matches the post-erasure method shape.

On the other hand, Erasure.Typer.typedApply re-types the tree using the pre-erasure type (ev: Ev)(a: A): String and filters out arguments in erased slots. For the bridge function, it mistook a for the erased slot, and drop it.
As a result, the Apply become something like this.run() and adaptToType failed on the dangling ``
The Apply became this.run() and `adaptToType` failed.

The same mismatch breaks every shape where erased appears before a non-erased parameter:

(using erased Ev)(a: A)
(erased x, y)
(erased x, y)(z)

This commit fixes the problem by:

(1) Now, bridgeRHS reconstructs the pre-erasure list shape in .applyMemberArgs.
For each pre-erasure parameter list we emit nested Applys
that contains only that list's non-erased arguments.
(2) For bridge, we attach Erasure.PostErasureArgs to those Applys,
that represents all args in the list are non-erased args.
Erasure.Typer.typedApply skips its erased-argument filter when
that attachment is there (because the args are already in
post-erasure form).

For (using erased Ev)(a: A) the bridge produces

Apply(Apply(this.run, []), [a])
// instead of Apply(this.run, [a])

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

Extensively for problem investigation, minimally for writing code.

How was the solution tested?

testCompilation i25726

Fixes scala#25726.

The following crashed during erasure with
"assertion failed: bad adapt for this.run(): (x$0: Int): String":

```scala
class Ev
trait Svc[A]:
  def run(using erased Ev)(a: A): String
class ISvc extends Svc[Int]:
  def run(using erased Ev)(a: Int): String = a.toString
```

Since `A` is specialized to `Int`, the post-erasure signatures of the
parent (`(a: Object): String`) and the child (`(a: Int): String`) differ,
so a bridge method is generated in `ISvc`.

However, while bridge body has application based on post-erasure arguments,
Erasure's typer expected bridge method to have pre-erasure form:

For example,
`Bridges.bridgeRhs` built a single flat `Apply(this.run, [a])`
one argument list, that matches the post-erasure method shape.

On the other hand, `Erasure.Typer.typedApply` re-types the tree
using the *pre-erasure* type `(ev: Ev)(a: A): String` and filters out
arguments in erased slots. For the bridge function,
it mistook `a` for the erased slot, and drop it.
As a result, the `Apply` become something like `this.run()` and
`adaptToType` failed on the dangling ``
The Apply became `this.run()` and `adaptToType` failed.

The same mismatch breaks every shape where `erased` appears before a
non-erased parameter:

```scala
(using erased Ev)(a: A)
(erased x, y)
(erased x, y)(z)
```

This commit fixes the problem by:

(1) Now, `bridgeRHS` reconstructs the pre-erasure list shape in `.applyMemberArgs`.
    For each pre-erasure parameter list we emit nested `Apply`s
    that contains only that list's non-erased arguments.
(2) For bridge, we attach `Erasure.PostErasureArgs` to those `Apply`s,
    that represents all args in the list are non-erased args.
    `Erasure.Typer.typedApply` skips its erased-argument filter when
    that attachment is there (because the args are already in
    post-erasure form).

For `(using erased Ev)(a: A)` the bridge produces

```scala
Apply(Apply(this.run, []), [a])
// instead of Apply(this.run, [a])
```
@tanishiking tanishiking marked this pull request as ready for review April 24, 2026 03:13
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.

AssertionError in erasure when overriding generic trait method with erased using clause before type-param argument

1 participant