Skip to content

Commit a93c849

Browse files
committed
Only rename method type params shadowing enclosing class type params
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]`. ```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
1 parent c9309e7 commit a93c849

File tree

3 files changed

+123
-11
lines changed

3 files changed

+123
-11
lines changed

compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,21 @@ object GenericSignatures {
4949
val builder = new StringBuilder(64)
5050
val isTraitSignature = sym0.enclosingClass.is(Trait)
5151

52-
// Collect class-level type parameter names to avoid conflicts with method-level type parameters
53-
val usedNames = collection.mutable.Set.empty[String]
54-
if(sym0.is(Method)) {
55-
sym0.enclosingClass.typeParams.foreach { tp =>
56-
usedNames += sanitizeName(tp.name)
57-
}
58-
}
52+
// Track class type parameter names that are shadowed by method type parameters
53+
// Used to trigger renaming of method type parameters to avoid conflicts
54+
val shadowedClassTypeParamNames = collection.mutable.Set.empty[String]
5955
val methodTypeParamRenaming = collection.mutable.Map.empty[String, String]
56+
6057
def freshTypeParamName(sanitizedName: String): String = {
61-
if !usedNames.contains(sanitizedName) then sanitizedName
58+
if !shadowedClassTypeParamNames.contains(sanitizedName) then sanitizedName
6259
else {
6360
var i = 1
6461
var newName = sanitizedName + i
65-
while usedNames.contains(newName) do
62+
while shadowedClassTypeParamNames.contains(newName) do
6663
i += 1
6764
newName = sanitizedName + i
6865
methodTypeParamRenaming(sanitizedName) = newName
69-
usedNames += newName
66+
shadowedClassTypeParamNames += newName
7067
newName
7168
}
7269
}
@@ -347,7 +344,22 @@ object GenericSignatures {
347344

348345
case mtd: MethodOrPoly =>
349346
val (tparams, vparams, rte) = collectMethodParams(mtd)
350-
if (toplevel && !sym0.isConstructor) polyParamSig(tparams)
347+
if (toplevel && !sym0.isConstructor) {
348+
if (sym0.is(Method)) {
349+
val (usedMethodTypeParamNames, usedClassTypeParams) = collectUsedTypeParams(vparams :+ rte, sym0)
350+
val methodTypeParamNames = tparams.map(tp => sanitizeName(tp.paramName.lastPart)).toSet
351+
// Only add class type parameters to shadowedClassTypeParamNames if they are:
352+
// 1. Referenced in the method signature, AND
353+
// 2. Shadowed by a method type parameter with the same name
354+
// This will trigger renaming of the method type parameter
355+
usedClassTypeParams.foreach { classTypeParam =>
356+
val classTypeParamName = sanitizeName(classTypeParam.name)
357+
if methodTypeParamNames.contains(classTypeParamName) then
358+
shadowedClassTypeParamNames += classTypeParamName
359+
}
360+
}
361+
polyParamSig(tparams)
362+
}
351363
builder.append('(')
352364
for vparam <- vparams do jsig1(vparam)
353365
builder.append(')')
@@ -448,6 +460,12 @@ object GenericSignatures {
448460
(initialSymbol.is(Method) && initialSymbol.typeParams.contains(sym))
449461
)
450462

463+
private def isTypeParameterInMethSig(sym: Symbol, initialSymbol: Symbol)(using Context) =
464+
!sym.maybeOwner.isTypeParam &&
465+
sym.isTypeParam && (
466+
(initialSymbol.is(Method) && initialSymbol.typeParams.contains(sym))
467+
)
468+
451469
// @M #2585 when generating a java generic signature that includes
452470
// a selection of an inner class p.I, (p = `pre`, I = `cls`) must
453471
// rewrite to p'.I, where p' refers to the class that directly defines
@@ -528,4 +546,48 @@ object GenericSignatures {
528546
val rte = recur(mtd)
529547
(tparams.toList, vparams.toList, rte)
530548
end collectMethodParams
549+
550+
/** Collect type parameters that are actually used in the given types. */
551+
private def collectUsedTypeParams(types: List[Type], initialSymbol: Symbol)(using Context): (Set[Name], Set[Symbol]) =
552+
val usedMethodTypeParamNames = collection.mutable.Set.empty[Name]
553+
val usedClassTypeParams = collection.mutable.Set.empty[Symbol]
554+
555+
def collect(tp: Type): Unit = tp.dealias match
556+
case ref @ TypeParamRef(_: PolyType, _) =>
557+
usedMethodTypeParamNames += ref.paramName
558+
case TypeRef(pre, _) =>
559+
val sym = tp.typeSymbol
560+
if isTypeParameterInMethSig(sym, initialSymbol) then
561+
usedMethodTypeParamNames += sym.name
562+
else if sym.isTypeParam && sym.isContainedIn(initialSymbol.topLevelClass) then
563+
usedClassTypeParams += sym
564+
else
565+
collect(pre)
566+
case AppliedType(tycon, args) =>
567+
collect(tycon)
568+
args.foreach(collect)
569+
case AndType(tp1, tp2) =>
570+
collect(tp1)
571+
collect(tp2)
572+
case OrType(tp1, tp2) =>
573+
collect(tp1)
574+
collect(tp2)
575+
case RefinedType(parent, _, refinedInfo) =>
576+
collect(parent)
577+
collect(refinedInfo)
578+
case TypeBounds(lo, hi) =>
579+
collect(lo)
580+
collect(hi)
581+
case ExprType(res) =>
582+
collect(res)
583+
case AnnotatedType(tpe, _) =>
584+
collect(tpe)
585+
case defn.ArrayOf(elemtp) =>
586+
collect(elemtp)
587+
case _ =>
588+
()
589+
590+
types.foreach(collect)
591+
(usedMethodTypeParamNames.toSet, usedClassTypeParams.toSet)
592+
end collectUsedTypeParams
531593
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
bar: public <T> T Foo.bar(T)
2+
baz: public <T> T Foo.baz(T,Foo<T>)
3+
qux: public <U> U Foo.qux(U,Foo<T>)
4+
quux: public <T,U> scala.Tuple2<T, U> Foo.quux(T,U,Foo<T>)
5+
copy: public <T> Bar<T> Bar.copy(T)
6+
compose: public <A1> scala.Function1<A1, B> JavaPartialFunction.compose(scala.Function1<A1, A>)
7+
compose: public <R> scala.PartialFunction<R, B> JavaPartialFunction.compose(scala.PartialFunction<R, A>)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// All of type params shouldn't rename
2+
class Foo[T]:
3+
def bar[T](x: T): T = x
4+
def baz[T](x: T, y: Foo[T]): T = x
5+
def qux[U](x: U, y: Foo[T]): U = x
6+
def quux[T, U](x: T, y: U, z: Foo[T]): (T, U) = (x, y)
7+
8+
// https://github.com/scala/scala3/issues/24671
9+
final case class Bar[+T](t: T)
10+
11+
// https://github.com/scala/scala3/issues/24134
12+
// The mixin forwarders for compose in Function1 method has signature
13+
// `def compose[A](g: A => T1): A => R`
14+
// Where the JavaPartialFunction[A, B] has type parameter A (name clash),
15+
// The type parameter A in method should be renamed to avoid name duplication.
16+
abstract class JavaPartialFunction[A, B] extends PartialFunction[A, B]
17+
18+
@main def Test(): Unit =
19+
val fooMethods = classOf[Foo[_]].getDeclaredMethods()
20+
printMethodSig(fooMethods, "bar")
21+
printMethodSig(fooMethods, "baz")
22+
printMethodSig(fooMethods, "qux")
23+
printMethodSig(fooMethods, "quux")
24+
25+
val barMethods = classOf[Bar[_]].getDeclaredMethods()
26+
printMethodSig(barMethods, "copy")
27+
// copy$default$1 still have `<T1> T Bar.copy$default$1` rather than `<T> T Bar.copy$default$1`
28+
// as reported in https://github.com/scala/scala3/issues/24671
29+
// The type parameter rename occurs because the return type T refers the enclosing class's type param T.
30+
// printMethodSig(barMethods, "copy$default$1")
31+
32+
val jpfMethods = classOf[JavaPartialFunction[_, _]].getDeclaredMethods()
33+
printMethodSigs(jpfMethods, "compose")
34+
35+
def printMethodSig(methods: Array[java.lang.reflect.Method], name: String): Unit =
36+
methods.find(_.getName.endsWith(name)).foreach { m =>
37+
println(s"$name: ${m.toGenericString}")
38+
}
39+
40+
def printMethodSigs(methods: Array[java.lang.reflect.Method], name: String): Unit =
41+
methods.filter(_.getName == name).sortBy(_.toGenericString).foreach { m =>
42+
println(s"$name: ${m.toGenericString}")
43+
}

0 commit comments

Comments
 (0)