@@ -269,42 +269,70 @@ object TypeErasure {
269
269
}
270
270
}
271
271
272
- /** Underlying type that does not contain aliases or abstract types
273
- * at top-level, treating opaque aliases as transparent.
272
+ /** Is `Array[tp]` a generic Array that needs to be erased to `Object`?
273
+ * This is true if among the subtypes of `Array[tp]` there is either:
274
+ * - both a reference array type and a primitive array type
275
+ * (e.g. `Array[_ <: Int | String]`, `Array[_ <: Any]`)
276
+ * - or two different primitive array types (e.g. `Array[_ <: Int | Double]`)
277
+ * In both cases the erased lub of those array types on the JVM is `Object`.
278
+ *
279
+ * In addition, if `isScala2` is true, we mimic the Scala 2 erasure rules and
280
+ * also return true for element types upper-bounded by a non-reference type
281
+ * such as in `Array[_ <: Int]` or `Array[_ <: UniversalTrait]`.
274
282
*/
275
- def classify (tp : Type )(using Context ): Type =
276
- if (tp.typeSymbol.isClass) tp
277
- else tp match {
278
- case tp : TypeProxy => classify(tp.translucentSuperType)
279
- case tp : AndOrType => tp.derivedAndOrType(classify(tp.tp1), classify(tp.tp2))
280
- case _ => tp
281
- }
283
+ def isGenericArrayElement (tp : Type , isScala2 : Boolean )(using Context ): Boolean = {
284
+ /** A symbol that represents the sort of JVM array that values of type `t` can be stored in:
285
+ * - If we can always store such values in a reference array, return Object
286
+ * - If we can always store them in a specific primitive array, return the
287
+ * corresponding primitive class
288
+ * - Otherwise, return `NoSymbol`.
289
+ */
290
+ def arrayUpperBound (t : Type ): Symbol = t.dealias match
291
+ case t : TypeRef if t.symbol.isClass =>
292
+ val sym = t.symbol
293
+ // Only a few classes have both primitives and references as subclasses.
294
+ if (sym eq defn.AnyClass ) || (sym eq defn.AnyValClass ) || (sym eq defn.MatchableClass ) || (sym eq defn.SingletonClass )
295
+ || isScala2 && ! (t.derivesFrom(defn.ObjectClass ) || t.isNullType) then
296
+ NoSymbol
297
+ // We only need to check for primitives because derived value classes in arrays are always boxed.
298
+ else if sym.isPrimitiveValueClass then
299
+ sym
300
+ else
301
+ defn.ObjectClass
302
+ case tp : TypeProxy =>
303
+ arrayUpperBound(tp.translucentSuperType)
304
+ case tp : AndOrType =>
305
+ val repr1 = arrayUpperBound(tp.tp1)
306
+ val repr2 = arrayUpperBound(tp.tp2)
307
+ if repr1 eq repr2 then
308
+ repr1
309
+ else if tp.isAnd then
310
+ repr1.orElse(repr2)
311
+ else
312
+ NoSymbol
313
+ case _ =>
314
+ NoSymbol
282
315
283
- /** Is `tp` an abstract type or polymorphic type parameter that has `Any`, `AnyVal`, `Null`,
284
- * or a universal trait as upper bound and that is not Java defined? Arrays of such types are
285
- * erased to `Object` instead of `Object[]`.
286
- */
287
- def isUnboundedGeneric (tp : Type )(using Context ): Boolean = {
288
- def isBoundedType (t : Type ): Boolean = t match {
289
- case t : OrType => isBoundedType(t.tp1) && isBoundedType(t.tp2)
290
- case _ => t.derivesFrom(defn.ObjectClass ) || t.isNullType
291
- }
316
+ /** Can one of the JVM Array type store all possible values of type `t`? */
317
+ def fitsInJVMArray (t : Type ): Boolean = arrayUpperBound(t).exists
292
318
293
319
tp.dealias match {
294
320
case tp : TypeRef if ! tp.symbol.isOpaqueAlias =>
295
321
! tp.symbol.isClass &&
296
- ! isBoundedType(classify(tp)) &&
297
- ! tp.symbol.is( JavaDefined )
322
+ ! tp.symbol.is( JavaDefined ) && // In Java code, Array[T] can never erase to Object
323
+ ! fitsInJVMArray(tp )
298
324
case tp : TypeParamRef =>
299
- ! isBoundedType(classify(tp))
300
- case tp : TypeAlias => isUnboundedGeneric(tp.alias)
325
+ ! fitsInJVMArray(tp)
326
+ case tp : TypeAlias =>
327
+ isGenericArrayElement(tp.alias, isScala2)
301
328
case tp : TypeBounds =>
302
- val upper = classify(tp.hi)
303
- ! isBoundedType(upper) &&
304
- ! upper.isPrimitiveValueType
305
- case tp : TypeProxy => isUnboundedGeneric(tp.translucentSuperType)
306
- case tp : AndType => isUnboundedGeneric(tp.tp1) && isUnboundedGeneric(tp.tp2)
307
- case tp : OrType => isUnboundedGeneric(tp.tp1) || isUnboundedGeneric(tp.tp2)
329
+ ! fitsInJVMArray(tp.hi)
330
+ case tp : TypeProxy =>
331
+ isGenericArrayElement(tp.translucentSuperType, isScala2)
332
+ case tp : AndType =>
333
+ isGenericArrayElement(tp.tp1, isScala2) && isGenericArrayElement(tp.tp2, isScala2)
334
+ case tp : OrType =>
335
+ isGenericArrayElement(tp.tp1, isScala2) || isGenericArrayElement(tp.tp2, isScala2)
308
336
case _ => false
309
337
}
310
338
}
@@ -642,8 +670,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
642
670
643
671
private def eraseArray (tp : Type )(using Context ) = {
644
672
val defn .ArrayOf (elemtp) = tp
645
- if (classify(elemtp).derivesFrom(defn.NullClass )) JavaArrayType (defn.ObjectType )
646
- else if (isUnboundedGeneric(elemtp) && ! sourceLanguage.isJava) defn.ObjectType
673
+ if (isGenericArrayElement(elemtp, isScala2 = sourceLanguage.isScala2)) defn.ObjectType
647
674
else JavaArrayType (erasureFn(sourceLanguage, semiEraseVCs = false , isConstructor, isSymbol, wildcardOK)(elemtp))
648
675
}
649
676
0 commit comments