@@ -10,7 +10,7 @@ import core.Flags._
1010import core .Names .{DerivedName , Name , SimpleName , TypeName }
1111import core .Symbols ._
1212import core .TypeApplications .TypeParamInfo
13- import core .TypeErasure .{erasure , isUnboundedGeneric }
13+ import core .TypeErasure .{erasedGlb , erasure , isUnboundedGeneric }
1414import core .Types ._
1515import core .classfile .ClassfileConstants
1616import ast .Trees ._
@@ -19,6 +19,7 @@ import TypeUtils._
1919import java .lang .StringBuilder
2020
2121import scala .annotation .tailrec
22+ import scala .collection .mutable .ListBuffer
2223
2324/** Helper object to generate generic java signatures, as defined in
2425 * the Java Virtual Machine Specification, §4.3.4
@@ -65,18 +66,79 @@ object GenericSignatures {
6566
6667 def boxedSig (tp : Type ): Unit = jsig(tp.widenDealias, primitiveOK = false )
6768
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+ */
6880 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 =>
75100 builder.append(':' )
76101 boxedSig(tp)
77102 }
78103 }
79104
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+
80142 def paramSig (param : LambdaParam ): Unit = {
81143 builder.append(sanitizeName(param.paramName))
82144 boundsSig(hiBounds(param.paramInfo.bounds))
@@ -263,8 +325,20 @@ object GenericSignatures {
263325 builder.append(')' )
264326 methodResultSig(restpe)
265327
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)
268342
269343 case ci : ClassInfo =>
270344 def polyParamSig (tparams : List [TypeParamInfo ]): Unit =
@@ -308,38 +382,6 @@ object GenericSignatures {
308382
309383 private class UnknownSig extends Exception
310384
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-
343385 /* Drop redundant types (ones which are implemented by some other parent) from the immediate parents.
344386 * This is important on Android because there is otherwise an interface explosion.
345387 */
0 commit comments