Skip to content

Commit 3f4d0cc

Browse files
committed
Fix generic signatures for mixin forwarders conflicting type parameter names
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]`, ```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.)
1 parent 7eab684 commit 3f4d0cc

File tree

4 files changed

+82
-10
lines changed

4 files changed

+82
-10
lines changed

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

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,36 @@ object GenericSignatures {
4141

4242
@noinline
4343
private final def javaSig0(sym0: Symbol, info: Type)(using Context): Option[String] = {
44+
// This works as long as mangled names are always valid valid Java identifiers,
45+
// if we change our name encoding, we'll have to `throw new UnknownSig` here for
46+
// names which are not valid Java identifiers (see git history of this method).
47+
def sanitizeName(name: Name): String = name.mangledString
48+
4449
val builder = new StringBuilder(64)
4550
val isTraitSignature = sym0.enclosingClass.is(Trait)
4651

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+
}
59+
val methodTypeParamRenaming = collection.mutable.Map.empty[String, String]
60+
def freshTypeParamName(sanitizedName: String): String = {
61+
if !usedNames.contains(sanitizedName) then sanitizedName
62+
else {
63+
var i = 1
64+
var newName = sanitizedName + i
65+
while usedNames.contains(newName) do
66+
i += 1
67+
newName = sanitizedName + i
68+
methodTypeParamRenaming(sanitizedName) = newName
69+
usedNames += newName
70+
newName
71+
}
72+
}
73+
4774
def superSig(cls: Symbol, parents: List[Type]): Unit = {
4875
def isInterfaceOrTrait(sym: Symbol) = sym.is(PureInterface) || sym.is(Trait)
4976

@@ -133,15 +160,16 @@ object GenericSignatures {
133160
else
134161
Right(parent))
135162

136-
def paramSig(param: TypeParamInfo): Unit = {
137-
builder.append(sanitizeName(param.paramName.lastPart))
163+
def tparamSig(param: TypeParamInfo): Unit = {
164+
val freshName = freshTypeParamName(sanitizeName(param.paramName.lastPart))
165+
builder.append(freshName)
138166
boundsSig(hiBounds(param.paramInfo.bounds))
139167
}
140168

141169
def polyParamSig(tparams: List[TypeParamInfo]): Unit =
142170
if (tparams.nonEmpty) {
143171
builder.append('<')
144-
tparams.foreach(paramSig)
172+
tparams.foreach(tparamSig)
145173
builder.append('>')
146174
}
147175

@@ -151,6 +179,12 @@ object GenericSignatures {
151179
builder.append(';')
152180
}
153181

182+
def typeParamSigWithName(sanitizedName: String): Unit = {
183+
builder.append(ClassfileConstants.TVAR_TAG)
184+
builder.append(sanitizedName)
185+
builder.append(';')
186+
}
187+
154188
def methodResultSig(restpe: Type): Unit = {
155189
val finalType = restpe.finalResultType
156190
val sym = finalType.typeSymbol
@@ -160,11 +194,6 @@ object GenericSignatures {
160194
jsig(finalType)
161195
}
162196

163-
// This works as long as mangled names are always valid valid Java identifiers,
164-
// if we change our name encoding, we'll have to `throw new UnknownSig` here for
165-
// names which are not valid Java identifiers (see git history of this method).
166-
def sanitizeName(name: Name): String = name.mangledString
167-
168197
// Anything which could conceivably be a module (i.e. isn't known to be
169198
// a type parameter or similar) must go through here or the signature is
170199
// likely to end up with Foo<T>.Empty where it needs Foo<T>.Empty$.
@@ -244,11 +273,15 @@ object GenericSignatures {
244273
// don't emit type param name if the param is upper-bounded by a primitive type (including via a value class)
245274
if erasedUnderlying.isPrimitiveValueType then
246275
jsig(erasedUnderlying, toplevel = toplevel, unboxedVCs = unboxedVCs)
247-
else typeParamSig(ref.paramName.lastPart)
276+
else {
277+
val name = sanitizeName(ref.paramName.lastPart)
278+
val nameToUse = methodTypeParamRenaming.getOrElse(name, name)
279+
typeParamSigWithName(nameToUse)
280+
}
248281

249282
case ref: SingletonType =>
250283
// Singleton types like `x.type` need to be widened to their underlying type
251-
// For example, `def identity[A](x: A): x.type` should have signature
284+
// For example, `def identity[A](x: A): x.type` should have signature
252285
// with return type `A` (not `java.lang.Object`)
253286
jsig(ref.underlying, toplevel = toplevel, unboxedVCs = unboxedVCs)
254287

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ i17186b.scala
3333
i11982a.scala
3434
i17255
3535
i17735.scala
36+
i24134
3637

3738
# Tree is huge and blows stack for printing Text
3839
i7034.scala
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
final class Flow[In, Out]:
2+
def collect[T](pf: PartialFunction[Out, T]): Flow[In, T] = ???
3+
4+
object Flow:
5+
def create[T](): Flow[T, T] = ???
6+
7+
8+
abstract class Message:
9+
def asTextMessage: TextMessage
10+
11+
abstract class TextMessage extends Message
12+
13+
abstract class JavaPartialFunction[A, B] extends PartialFunction[A, B]:
14+
@throws(classOf[Exception]) // required, compiles without annotation
15+
def apply(x: A, isCheck: Boolean): B
16+
17+
final def isDefinedAt(x: A): Boolean = ???
18+
final override def apply(x: A): B = ???
19+
final override def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 = ???
20+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
public class JavaTestServer {
2+
public static Flow<Message, Message> greeter() {
3+
return Flow.<Message>create()
4+
.collect(
5+
new JavaPartialFunction<Message, Message>() {
6+
@Override
7+
public Message apply(Message msg, boolean isCheck) throws Exception {
8+
if (isCheck) throw new RuntimeException();
9+
else return handleTextMessage(msg.asTextMessage());
10+
}
11+
});
12+
}
13+
14+
public static TextMessage handleTextMessage(TextMessage msg) {
15+
return null;
16+
}
17+
}
18+

0 commit comments

Comments
 (0)