Skip to content

Conversation

@tanishiking
Copy link
Member

Fixes #24134

f99dba2 started emitting Java generic signature for mixin forwarders.

However, when generating Java generic signatures for mixin forwarders, method-level type parameters could shadow class-level type parameters with the same name.

For example, when JavaPartialFunction[A, B] implements PartialFunction1[A, B],

trait Function1[-T1, +R]:
  def compose[A](g: A => T1): A => R = ???

trait PartialFunction[-A, +B] extends Function1[A, B]:
  def compose[R](k: PartialFunction[R, A]): PartialFunction[R, B] = ???

abstract class JavaPartialFunction[A, B] extends PartialFunction[A, B]

the generated mixin forwarder for compose in Function1 was like:

public abstract class JavaPartialFunction<A, B> implements scala.PartialFunction<A, B> {
  public <A> scala.Function1<A, B> compose(scala.Function1<A, A>);

which is obviously incorrect, the type parameter A of compose[A] is shadowed by the A in JavaPartialFunction<A, B>.

The compose's type parameter should use an unique name like:

public abstract class JavaPartialFunction<A, B> implements scala.PartialFunction<A, B> {
  public <T> scala.Function1<T, B> compose(scala.Function1<T, A>);

This commit fix the problem by

  • Tracks class-level type parameter names when generating method signatures
  • Renames conflicting method-level type parameters (A → A1, A2, etc.)

@hamzaremmal
Copy link
Member

Do we know yet why the name changed in the first place?

@tanishiking
Copy link
Member Author

I'll check that later today

@hamzaremmal
Copy link
Member

Alright, thanks @tanishiking.

@tanishiking
Copy link
Member Author

tanishiking commented Nov 29, 2025

I confirmed that 9bd7774 already have this issue. Ever since generic signature generation for mixin forwarders was introduced in #23942, it has been producing signatures with name clashes in these cases.


The problem is that the member type stored in mixinForwarderGenericInfos is def compose[A](g: A => T1): A => R = ... (since Future1 defines it that way). And GenericSignature uses the signature A as-is, without considering name shadowing in the subclass.

val (infoBeforeErasure, isDifferentThanInfoNow) = atPhase(erasurePhase) {
val beforeErasure = cls.thisType.memberInfo(target)
(beforeErasure, !(beforeErasure =:= sym.info))
}
if isDifferentThanInfoNow then
// The info before erasure would not have been the same as the info now.
// We want to store it for the backend to compute the generic Java signature.
// However, we must still avoid doing that if erasing that signature would
// not give the same erased type. If it doesn't, we'll just give a completely
// incorrect Java signature. (This could be improved by generating dedicated
// bridges, but we don't go that far; scalac doesn't either.)
if TypeErasure.transformInfo(target, infoBeforeErasure) =:= sym.info then
mixinForwarderGenericInfos(sym) = infoBeforeErasure

@tanishiking tanishiking marked this pull request as ready for review November 29, 2025 10:32
@tanishiking tanishiking marked this pull request as draft November 29, 2025 12:27
@tanishiking tanishiking marked this pull request as ready for review November 29, 2025 14:30
@tanishiking
Copy link
Member Author

Some CI checks have failed, but they are unrelated to this change:

@Gedochao Gedochao requested a review from sjrd December 1, 2025 10:28
…r names

Fixes scala#24134

scala@f99dba2 started emitting Java generic signature for mixin forwarders.

However, when generating Java generic signatures for mixin forwarders,
method-level type parameters could shadow class-level type parameters with the same name.

For example, when `JavaPartialFunction[A, B]` implements
`PartialFunction1[A, B]`,

```scala
trait Function1[-T1, +R]:
  def compose[A](g: A => T1): A => R = ???

trait PartialFunction[-A, +B] extends Function1[A, B]:
  def compose[R](k: PartialFunction[R, A]): PartialFunction[R, B] = ???

abstract class JavaPartialFunction[A, B] extends PartialFunction[A, B]
```

the generated mixin forwarder for `compose` in Function1 was like:

```java
public abstract class JavaPartialFunction<A, B> implements scala.PartialFunction<A, B> {
  public <A> scala.Function1<A, B> compose(scala.Function1<A, A>);
```

which is obviously incorrect, the type parameter `A` of
`compose[A]` is shadowed by the `A` in `JavaPartialFunction<A, B>`.

The `compose`'s type parameter should use an unique name like:

```java
public abstract class JavaPartialFunction<A, B> implements scala.PartialFunction<A, B> {
  public <T> scala.Function1<T, B> compose(scala.Function1<T, A>);
```

This commit fix the problem by
- Tracks class-level type parameter names when generating
  method signatures
- Renames conflicting method-level type parameters (A → A1,
  A2, etc.)
@tanishiking tanishiking enabled auto-merge December 1, 2025 13:09
@tanishiking tanishiking merged commit e2890e8 into scala:main Dec 1, 2025
46 checks passed
@tanishiking tanishiking deleted the i24134 branch December 1, 2025 15:36
// Collect class-level type parameter names to avoid conflicts with method-level type parameters
val usedNames = collection.mutable.Set.empty[String]
if(sym0.is(Method)) {
sym0.enclosingClass.typeParams.foreach { tp =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this enough? We should get the type parameters of all the enclosing classes.
The core of this issue has to do with substitution and the fact that we convert to String. I could substitute the enclosing of the enclosing class type parameters by something that will create conflicts (from a String point of view).

I would have suggested a more robust algorithm where we do a first pass to go over all the types referred to in the Signature and disambiguate if they have a different id but the same name (as a String).

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.

Regression for java compatibility in multiple akka / pekko projects

3 participants