Skip to content

Commit 5fcf49a

Browse files
Backport "Only rename method type params shadowing enclosing class type params" to 3.8.0 (#24809)
Backports #24684 to the 3.8.0-RC4. PR submitted by the release tooling. [skip ci]
2 parents 5aafcc7 + 4c0c3a1 commit 5fcf49a

File tree

8 files changed

+157
-11
lines changed

8 files changed

+157
-11
lines changed

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

Lines changed: 46 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(')')
@@ -528,4 +540,27 @@ object GenericSignatures {
528540
val rte = recur(mtd)
529541
(tparams.toList, vparams.toList, rte)
530542
end collectMethodParams
543+
544+
/** Collect type parameters that are actually used in the given types. */
545+
private def collectUsedTypeParams(types: List[Type], initialSymbol: Symbol)(using Context): (Set[Name], Set[Symbol]) =
546+
assert(initialSymbol.is(Method))
547+
def isTypeParameterInMethSig(sym: Symbol, initialSymbol: Symbol)(using Context) =
548+
!sym.maybeOwner.isTypeParam && // check if it's not higher order type param
549+
sym.isTypeParam && sym.owner == initialSymbol
550+
551+
val usedMethodTypeParamNames = collection.mutable.Set.empty[Name]
552+
val usedClassTypeParams = collection.mutable.Set.empty[Symbol]
553+
554+
def collect(tp: Type): Unit = tp.foreachPart:
555+
case ref @ TypeParamRef(_: PolyType, _) =>
556+
usedMethodTypeParamNames += ref.paramName
557+
case tp: TypeRef =>
558+
val sym = tp.typeSymbol
559+
if sym.isTypeParam && sym.isContainedIn(initialSymbol.topLevelClass) then
560+
usedClassTypeParams += sym
561+
case _ =>
562+
563+
types.foreach(collect)
564+
(usedMethodTypeParamNames.toSet, usedClassTypeParams.toSet)
565+
end collectUsedTypeParams
531566
}

compiler/test/dotc/pos-test-pickling.excludelist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ i11982a.scala
3434
i17255
3535
i17735.scala
3636
i24134
37+
i24134-nested
3738

3839
# Tree is huge and blows stack for printing Text
3940
i7034.scala
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
noShadow: public scala.Tuple3<T, U, V> Outer$Middle$Inner.noShadow(T,U,V)
2+
shadowOuter: public <T1> T Outer$Middle$Inner.shadowOuter()
3+
shadowMiddle: public <U1> U Outer$Middle$Inner.shadowMiddle()
4+
shadowInner: public <V1> V Outer$Middle$Inner.shadowInner()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class Outer[T]:
2+
def outerValue: T = ???
3+
4+
class Middle[U]:
5+
def middleValue: U = ???
6+
7+
class Inner[V]:
8+
def innerValue: V = ???
9+
def noShadow(x: T, y: U, z: V): (T, U, V) = (x, y, z)
10+
def shadowOuter[T] = outerValue
11+
def shadowMiddle[U] = middleValue
12+
def shadowInner[V] = innerValue
13+
14+
@main def Test(): Unit =
15+
val innerMethods = classOf[Outer[_]].getDeclaredClasses()
16+
.find(_.getName.contains("Middle")).get.getDeclaredClasses()
17+
.find(_.getName.contains("Inner")).get.getDeclaredMethods()
18+
19+
printMethodSig(innerMethods, "noShadow")
20+
printMethodSig(innerMethods, "shadowOuter")
21+
printMethodSig(innerMethods, "shadowMiddle")
22+
printMethodSig(innerMethods, "shadowInner")
23+
24+
def printMethodSig(methods: Array[java.lang.reflect.Method], name: String): Unit =
25+
methods.find(_.getName.endsWith(name)).foreach { m =>
26+
println(s"$name: ${m.toGenericString}")
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
copy$default$1: public <T1> T Bar.copy$default$1()
7+
m: public <T1> T C.m()
8+
compose: public <A1> scala.Function1<A1, B> JavaPartialFunction.compose(scala.Function1<A1, A>)
9+
compose: public <R> scala.PartialFunction<R, B> JavaPartialFunction.compose(scala.PartialFunction<R, A>)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
// `m: public <T1> T C.m()` rather than `m: public <T> T C.m()`
12+
// that was wrong and could be crashed with
13+
// `val c = C[String]; String x = c.<Object>m();`.
14+
abstract class C[T]:
15+
def x: T
16+
def m[T] = x
17+
18+
// https://github.com/scala/scala3/issues/24134
19+
// The mixin forwarders for compose in Function1 method has signature
20+
// `def compose[A](g: A => T1): A => R`
21+
// Where the JavaPartialFunction[A, B] has type parameter A (name clash),
22+
// The type parameter A in method should be renamed to avoid name duplication.
23+
abstract class JavaPartialFunction[A, B] extends PartialFunction[A, B]
24+
25+
@main def Test(): Unit =
26+
val fooMethods = classOf[Foo[_]].getDeclaredMethods()
27+
printMethodSig(fooMethods, "bar")
28+
printMethodSig(fooMethods, "baz")
29+
printMethodSig(fooMethods, "qux")
30+
printMethodSig(fooMethods, "quux")
31+
32+
val barMethods = classOf[Bar[_]].getDeclaredMethods()
33+
printMethodSig(barMethods, "copy")
34+
// copy$default$1 have `<T1> T Bar.copy$default$1` rather than `<T> T Bar.copy$default$1`
35+
// as reported in https://github.com/scala/scala3/issues/24671
36+
// The type parameter rename occurs because the return type T refers the enclosing class's type param T.
37+
printMethodSig(barMethods, "copy$default$1")
38+
39+
val cMethods = classOf[C[_]].getDeclaredMethods()
40+
printMethodSig(cMethods, "m")
41+
42+
val jpfMethods = classOf[JavaPartialFunction[_, _]].getDeclaredMethods()
43+
printMethodSigs(jpfMethods, "compose")
44+
45+
def printMethodSig(methods: Array[java.lang.reflect.Method], name: String): Unit =
46+
methods.find(_.getName.endsWith(name)).foreach { m =>
47+
println(s"$name: ${m.toGenericString}")
48+
}
49+
50+
def printMethodSigs(methods: Array[java.lang.reflect.Method], name: String): Unit =
51+
methods.filter(_.getName == name).sortBy(_.toGenericString).foreach { m =>
52+
println(s"$name: ${m.toGenericString}")
53+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class Container[A]:
2+
abstract class JavaPartialFunction[B] extends PartialFunction[A, B]

tests/pos/i24134-nested/Main.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
public class Main {
2+
public static void main(String[] args) {
3+
Container<String> container = new Container<>();
4+
Container<String>.JavaPartialFunction<Integer> pf = container.new JavaPartialFunction<Integer>() {
5+
@Override
6+
public boolean isDefinedAt(String x) {
7+
return x != null && !x.isEmpty();
8+
}
9+
@Override
10+
public Integer apply(String x) {
11+
return x.length();
12+
}
13+
};
14+
}
15+
}

0 commit comments

Comments
 (0)