Skip to content

Conversation

@tanishiking
Copy link
Member

@tanishiking tanishiking commented Dec 6, 2025

Previously, method type parameters were always renamed if they had the same name as any class type parameter, regardless of whether the class type parameter was actually used in the method signature.

As a result, we rename methods's type params for unnecessary names like: #24671 (Note that renaming actually doesn't cause binary incompatibility, and this change is just for make generic signature a bit clear and silence false-positive MIMA reporting).

For example, in an example below, the Scala compiler rename the generic signature of bar to something like bar[T1](x: T1): T1 because T is used by Foo[T]. However, this is unnessary rename because none of T in method signature refer the T of Foo[T].

class Foo[T]:
  def bar[T](x: T): T = ???

This commit makes the renaming conditional:
Method type parameters are only renamed when

  • (1) A class type parameter is referenced in the method signature, and
  • (2) That class type parameter is shadowed by a method type parameter

Previously, method type parameters were always renamed if they had the same name as any class type parameter, regardless of whether the class type parameter was actually used in the method signature.

As a result, we rename methods's type params for unnecessary names like: scala#24671
(Note that renaming actually doesn't cause binary incompatibility, and this change is just for make generic signature a bit clear and silence false-positive MIMA reporting).

For example, in an example below, the Scala compiler rename the generic signature of `bar` to something like `bar[T1](x: T1): T1` because `T` is used by `Foo[T]`. However, this is unnessary rename because none of T in method signature refer the `T` of `Foo[T]`.

```scala
class Foo[T]:
  def bar[T](x: T): T = ???
```

This commit makes the renaming conditional:
Method type parameters are only renamed when
- (1) A class type parameter is referenced in the method signature, and
- (2) That class type parameter is shadowed by a method type parameter
Comment on lines 27 to 30
// copy$default$1 still have `<T1> T Bar.copy$default$1` rather than `<T> T Bar.copy$default$1`
// as reported in https://github.com/scala/scala3/issues/24671
// The type parameter rename occurs because the return type T refers the enclosing class's type param T.
// printMethodSig(barMethods, "copy$default$1")
Copy link
Member Author

@tanishiking tanishiking Dec 7, 2025

Choose a reason for hiding this comment

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

Still MiMa won't be happy with <T1> T Bar.copy$default$1 rather than <T> T Bar.copy$default$1 (but the original signature was wrong, the return type T is shadowed by method type parameter.)

Copy link
Member

Choose a reason for hiding this comment

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

Ah! I didn't pay attention. A small reproducer is

abstract class C[T] {
  def x: T
  def m[T] = x
}

We have that wrong in Scala 2, I created scala/bug#13144.

Copy link
Member Author

@tanishiking tanishiking Dec 8, 2025

Choose a reason for hiding this comment

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

Thanks! that's a good reproducer :)
It seems like renaming to <T1:Ljava/lang/Object;>()TT; is right thing to do 👍

Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

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

false-positive MIMA reporting

nitpicking.. it's not really a false positive report by mima, but mima can be configured to check changes in the classfile (generic signatures) that go beyond binary compatibility.

Comment on lines 27 to 30
// copy$default$1 still have `<T1> T Bar.copy$default$1` rather than `<T> T Bar.copy$default$1`
// as reported in https://github.com/scala/scala3/issues/24671
// The type parameter rename occurs because the return type T refers the enclosing class's type param T.
// printMethodSig(barMethods, "copy$default$1")
Copy link
Member

Choose a reason for hiding this comment

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

Ah! I didn't pay attention. A small reproducer is

abstract class C[T] {
  def x: T
  def m[T] = x
}

We have that wrong in Scala 2, I created scala/bug#13144.

val (tparams, vparams, rte) = collectMethodParams(mtd)
if (toplevel && !sym0.isConstructor) polyParamSig(tparams)
if (toplevel && !sym0.isConstructor) {
if (sym0.is(Method)) {
Copy link
Member

Choose a reason for hiding this comment

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

It seems name shadowing can also happen for nested classes, though that's a lot more exotic

abstract class C[T] {
  val x: T
  class I[T <: x.type]
}

the class C$I has signautre <T:TT;>Ljava/lang/Object;, which I guess is also wrong? Either way, I think we don't need to fix this in this PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right. In that case, the renaming strategy for methods in nested classes will be more complicated. I won't fix it in this PR. Instead, I gonna rebase #24621 on top of this change and address it there.

val usedMethodTypeParamNames = collection.mutable.Set.empty[Name]
val usedClassTypeParams = collection.mutable.Set.empty[Symbol]

def collect(tp: Type): Unit = tp.dealias match
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, we have

abstract class TypeTraverser(using Context) extends TypeAccumulator[Unit] {
def traverse(tp: Type): Unit
def apply(x: Unit, tp: Type): Unit = traverse(tp)
protected def traverseChildren(tp: Type): Unit = foldOver((), tp)
}
👍

@Gedochao Gedochao added the backport:nominated If we agree to backport this PR, replace this tag with "backport:accepted", otherwise delete it. label Dec 8, 2025
@Gedochao Gedochao added this to the 3.8.0 milestone Dec 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:nominated If we agree to backport this PR, replace this tag with "backport:accepted", otherwise delete it.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants