Skip to content

Commit ced3ca8

Browse files
committed
SI-8931 make generic signature consistent with interface list in classfiles
An optimization was introduced in 7a99c03 (SI-5278) to remove redundant interfaces from the list of implemented interfaces in the bytecode. However the same change was not propagated to the generic signature of a class, which also contains a list of direct parent classes and interfaces. The JVM does not check the well-formedness of signatures at class loading or linking (see §4.3.4 of jdk7 jvms), but other tools might assume the number of implemented interfaces is the same whether one asked for generic or erased interfaces. It doesn't break reflection so nobody complained, but it does show: scala> val c = classOf[Tuple1[String]] c: Class[(String,)] = class scala.Tuple1 scala> c.getInterfaces // Product is gone res0: Array[Class[_]] = Array(interface scala.Product1, interface scala.Serializable) scala> c.getGenericInterfaces // Product is back! res1: Array[java.lang.reflect.Type] = Array(scala.Product1<T1>, interface scala.Product, interface scala.Serializable) This moves the optimization to erasure, for use in emitting the generic signature, and the backend calls into it later for the list of interfaces.
1 parent bdae51d commit ced3ca8

File tree

5 files changed

+54
-48
lines changed

5 files changed

+54
-48
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -195,32 +195,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
195195
case _ => NoSymbol
196196
}
197197

198-
/**
199-
* Drop redundant interfaces (which are implemented by some other parent) from the immediate
200-
* parents. In other words, no two interfaces in the result are related by subtyping.
201-
*/
202-
def dropRedundantInterfaces(lstIfaces: List[Symbol]): List[Symbol] = {
203-
var rest = lstIfaces
204-
var leaves = List.empty[Symbol]
205-
while (!rest.isEmpty) {
206-
val candidate = rest.head
207-
val nonLeaf = leaves exists { lsym => lsym isSubClass candidate }
208-
if (!nonLeaf) {
209-
leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym })
210-
}
211-
rest = rest.tail
212-
}
213-
214-
leaves
215-
}
216-
217198
val superInterfaces0: List[Symbol] = classSym.mixinClasses
218199
val superInterfaces = existingSymbols(superInterfaces0 ++ classSym.annotations.map(newParentForAnnotation)).distinct
219200

220201
assert(!superInterfaces.contains(NoSymbol), s"found NoSymbol among: ${superInterfaces.mkString(", ")}")
221202
assert(superInterfaces.forall(s => s.isInterface || s.isTrait), s"found non-interface among: ${superInterfaces.mkString(", ")}")
222203

223-
dropRedundantInterfaces(superInterfaces)
204+
erasure.minimizeInterfaces(superInterfaces.map(_.info)).map(_.typeSymbol)
224205
}
225206

226207
private def buildNestedInfo(innerClassSym: Symbol): Option[NestedInfo] = {

src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,30 +1214,12 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
12141214
case _ => NoSymbol
12151215
}
12161216

1217-
/* Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents.
1218-
* This is important on Android because there is otherwise an interface explosion.
1219-
*/
1220-
def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = {
1221-
var rest = lstIfaces
1222-
var leaves = List.empty[Symbol]
1223-
while(!rest.isEmpty) {
1224-
val candidate = rest.head
1225-
val nonLeaf = leaves exists { lsym => lsym isSubClass candidate }
1226-
if(!nonLeaf) {
1227-
leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym })
1228-
}
1229-
rest = rest.tail
1230-
}
1231-
1232-
leaves
1233-
}
1234-
12351217
val ps = c.symbol.info.parents
12361218
val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses
12371219
val superInterfaces = existingSymbols(superInterfaces0 ++ c.symbol.annotations.map(newParentForAttr)).distinct
12381220

12391221
if(superInterfaces.isEmpty) EMPTY_STRING_ARRAY
1240-
else mkArray(minimizeInterfaces(superInterfaces) map javaName)
1222+
else mkArray(erasure.minimizeInterfaces(superInterfaces.map(_.info)).map(t => javaName(t.typeSymbol)))
12411223
}
12421224

12431225
var clasz: IClass = _ // this var must be assigned only by genClass()

src/compiler/scala/tools/nsc/transform/Erasure.scala

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -185,23 +185,50 @@ abstract class Erasure extends AddInterfaces
185185

186186
private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType]
187187

188+
/* Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents.
189+
* This is important on Android because there is otherwise an interface explosion.
190+
*/
191+
def minimizeInterfaces(lstIfaces: List[Type]): List[Type] = {
192+
var rest = lstIfaces
193+
var leaves = List.empty[Type]
194+
while(!rest.isEmpty) {
195+
val candidate = rest.head
196+
val nonLeaf = leaves exists { t => t.typeSymbol isSubClass candidate.typeSymbol }
197+
if(!nonLeaf) {
198+
leaves = candidate :: (leaves filterNot { t => candidate.typeSymbol isSubClass t.typeSymbol })
199+
}
200+
rest = rest.tail
201+
}
202+
203+
leaves.reverse
204+
}
205+
206+
188207
/** The Java signature of type 'info', for symbol sym. The symbol is used to give the right return
189208
* type for constructors.
190209
*/
191210
def javaSig(sym0: Symbol, info: Type): Option[String] = enteringErasure {
192211
val isTraitSignature = sym0.enclClass.isTrait
193212

194213
def superSig(parents: List[Type]) = {
195-
val ps = (
196-
if (isTraitSignature) {
214+
def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait
215+
216+
// a signature should always start with a class
217+
def ensureClassAsFirstParent(tps: List[Type]) = tps match {
218+
case Nil => ObjectTpe :: Nil
219+
case head :: tail if isInterfaceOrTrait(head.typeSymbol) => ObjectTpe :: tps
220+
case _ => tps
221+
}
222+
223+
val minParents = minimizeInterfaces(parents)
224+
val validParents =
225+
if (isTraitSignature)
197226
// java is unthrilled about seeing interfaces inherit from classes
198-
val ok = parents filter (p => p.typeSymbol.isTrait || p.typeSymbol.isInterface)
199-
// traits should always list Object.
200-
if (ok.isEmpty || ok.head.typeSymbol != ObjectClass) ObjectTpe :: ok
201-
else ok
202-
}
203-
else parents
204-
)
227+
minParents filter (p => isInterfaceOrTrait(p.typeSymbol))
228+
else minParents
229+
230+
val ps = ensureClassAsFirstParent(validParents)
231+
205232
(ps map boxedSig).mkString
206233
}
207234
def boxedSig(tp: Type) = jsig(tp, primitiveOK = false)

test/files/run/t8931.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
List(interface B)

test/files/run/t8931.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
trait A
3+
4+
trait B extends A
5+
6+
class C extends A with B
7+
8+
object Test extends App {
9+
val c = classOf[C]
10+
11+
println(c.getGenericInterfaces.toList)
12+
13+
assert(c.getGenericInterfaces.length == c.getInterfaces.length,
14+
s"mismatch between ${c.getGenericInterfaces} and ${c.getInterfaces}")
15+
}

0 commit comments

Comments
 (0)