@@ -10,7 +10,7 @@ import core.Flags._
10
10
import core .Names .{DerivedName , Name , SimpleName , TypeName }
11
11
import core .Symbols ._
12
12
import core .TypeApplications .TypeParamInfo
13
- import core .TypeErasure .{erasure , isUnboundedGeneric }
13
+ import core .TypeErasure .{erasedGlb , erasure , isUnboundedGeneric }
14
14
import core .Types ._
15
15
import core .classfile .ClassfileConstants
16
16
import ast .Trees ._
@@ -19,6 +19,7 @@ import TypeUtils._
19
19
import java .lang .StringBuilder
20
20
21
21
import scala .annotation .tailrec
22
+ import scala .collection .mutable .ListBuffer
22
23
23
24
/** Helper object to generate generic java signatures, as defined in
24
25
* the Java Virtual Machine Specification, §4.3.4
@@ -65,18 +66,79 @@ object GenericSignatures {
65
66
66
67
def boxedSig (tp : Type ): Unit = jsig(tp.widenDealias, primitiveOK = false )
67
68
69
+ /** The signature of the upper-bound of a type parameter.
70
+ *
71
+ * @pre none of the bounds are themselves type parameters.
72
+ * TODO: Remove this restriction so we can support things like:
73
+ *
74
+ * class Foo[A]:
75
+ * def foo[S <: A & Object](...): ...
76
+ *
77
+ * Which should emit a signature `S <: A`. See the handling
78
+ * of `AndType` in `jsig` which already supports `def foo(x: A & Object)`.
79
+ */
68
80
def boundsSig (bounds : List [Type ]): Unit = {
69
- val (isTrait, isClass) = bounds partition (_.typeSymbol.is(Trait ))
70
- isClass match {
71
- case Nil => builder.append(':' ) // + boxedSig(ObjectTpe)
72
- case x :: _ => builder.append(':' ); boxedSig(x)
73
- }
74
- isTrait.foreach { tp =>
81
+ val (repr :: _, others) = splitIntersection(bounds)
82
+ builder.append(':' )
83
+
84
+ // According to the Java spec
85
+ // (https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.4),
86
+ // intersections erase to their first member and must start with a class.
87
+ // So, if our intersection erases to a trait, in theory we should emit
88
+ // just that trait in the generic signature even if the intersection type
89
+ // is composed of multiple traits. But in practice Scala 2 has always
90
+ // ignored this restriction as intersections of traits seem to be handled
91
+ // correctly by javac, we do the same here since type soundness seems
92
+ // more important than adhering to the spec.
93
+ if repr.classSymbol.is(Trait ) then
94
+ builder.append(':' )
95
+ boxedSig(repr)
96
+ // If we wanted to be compliant with the spec, we would `return` here.
97
+ else
98
+ boxedSig(repr)
99
+ others.filter(_.classSymbol.is(Trait )).foreach { tp =>
75
100
builder.append(':' )
76
101
boxedSig(tp)
77
102
}
78
103
}
79
104
105
+ /** The parents of this intersection where type parameters
106
+ * that cannot appear in the signature have been replaced
107
+ * by their upper-bound.
108
+ */
109
+ def flattenedIntersection (tp : AndType )(using Context ): List [Type ] =
110
+ val parents = ListBuffer [Type ]()
111
+
112
+ def collect (parent : Type , parents : ListBuffer [Type ]): Unit = parent.widenDealias match
113
+ case AndType (tp1, tp2) =>
114
+ collect(tp1, parents)
115
+ collect(tp2, parents)
116
+ case parent : TypeRef =>
117
+ if parent.symbol.isClass || isTypeParameterInSig(parent.symbol, sym0) then
118
+ parents += parent
119
+ else
120
+ collect(parent.superType, parents)
121
+ case parent =>
122
+ parents += parent
123
+
124
+ collect(tp, parents)
125
+ parents.toList
126
+ end flattenedIntersection
127
+
128
+ /** Split the `parents` of an intersection into two subsets:
129
+ * those whose individual erasure matches the overall erasure
130
+ * of the intersection and the others.
131
+ */
132
+ def splitIntersection (parents : List [Type ])(using Context ): (List [Type ], List [Type ]) =
133
+ val erasedParents = parents.map(erasure)
134
+ val erasedCls = erasedGlb(erasedParents).classSymbol
135
+ parents.zip(erasedParents)
136
+ .partitionMap((parent, erasedParent) =>
137
+ if erasedParent.classSymbol eq erasedCls then
138
+ Left (parent)
139
+ else
140
+ Right (parent))
141
+
80
142
def paramSig (param : LambdaParam ): Unit = {
81
143
builder.append(sanitizeName(param.paramName))
82
144
boundsSig(hiBounds(param.paramInfo.bounds))
@@ -263,8 +325,20 @@ object GenericSignatures {
263
325
builder.append(')' )
264
326
methodResultSig(restpe)
265
327
266
- case AndType (tp1, tp2) =>
267
- jsig(intersectionDominator(tp1 :: tp2 :: Nil ), primitiveOK = primitiveOK)
328
+ case tp : AndType =>
329
+ // Only intersections appearing as the upper-bound of a type parameter
330
+ // can be preserved in generic signatures and those are already
331
+ // handled by `boundsSig`, so here we fallback to picking a parent of
332
+ // the intersection to determine its overall signature. We must pick a
333
+ // parent whose erasure matches the erasure of the intersection
334
+ // because javac relies on the generic signature to determine the
335
+ // bytecode signature. Additionally, we prefer picking a type
336
+ // parameter since that will likely lead to a more precise type.
337
+ val parents = flattenedIntersection(tp)
338
+ val (reprParents, _) = splitIntersection(parents)
339
+ val repr =
340
+ reprParents.find(_.typeSymbol.is(TypeParam )).getOrElse(reprParents.head)
341
+ jsig(repr, primitiveOK = primitiveOK)
268
342
269
343
case ci : ClassInfo =>
270
344
def polyParamSig (tparams : List [TypeParamInfo ]): Unit =
@@ -308,38 +382,6 @@ object GenericSignatures {
308
382
309
383
private class UnknownSig extends Exception
310
384
311
- /** The intersection dominator (SLS 3.7) of a list of types is computed as follows.
312
- *
313
- * - If the list contains one or more occurrences of scala.Array with
314
- * type parameters El1, El2, ... then the dominator is scala.Array with
315
- * type parameter of intersectionDominator(List(El1, El2, ...)). <--- @PP: not yet in spec.
316
- * - Otherwise, the list is reduced to a subsequence containing only the
317
- * types that are not supertypes of other listed types (the span.)
318
- * - If the span is empty, the dominator is Object.
319
- * - If the span contains a class Tc which is not a trait and which is
320
- * not Object, the dominator is Tc. <--- @PP: "which is not Object" not in spec.
321
- * - Otherwise, the dominator is the first element of the span.
322
- */
323
- private def intersectionDominator (parents : List [Type ])(using Context ): Type =
324
- if (parents.isEmpty) defn.ObjectType
325
- else {
326
- val psyms = parents map (_.typeSymbol)
327
- if (psyms contains defn.ArrayClass )
328
- // treat arrays specially
329
- defn.ArrayType .appliedTo(intersectionDominator(parents.filter(_.typeSymbol == defn.ArrayClass ).map(t => t.argInfos.head)))
330
- else {
331
- // implement new spec for erasure of refined types.
332
- def isUnshadowed (psym : Symbol ) =
333
- ! (psyms exists (qsym => (psym ne qsym) && (qsym isSubClass psym)))
334
- val cs = parents.iterator.filter { p => // isUnshadowed is a bit expensive, so try classes first
335
- val psym = p.classSymbol
336
- psym.ensureCompleted()
337
- psym.isClass && ! psym.is(Trait ) && isUnshadowed(psym)
338
- }
339
- (if (cs.hasNext) cs else parents.iterator.filter(p => isUnshadowed(p.classSymbol))).next()
340
- }
341
- }
342
-
343
385
/* Drop redundant types (ones which are implemented by some other parent) from the immediate parents.
344
386
* This is important on Android because there is otherwise an interface explosion.
345
387
*/
0 commit comments