@@ -269,42 +269,70 @@ object TypeErasure {
269269 }
270270 }
271271
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]`.
274282 */
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
282315
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
292318
293319 tp.dealias match {
294320 case tp : TypeRef if ! tp.symbol.isOpaqueAlias =>
295321 ! 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 )
298324 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)
301328 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)
308336 case _ => false
309337 }
310338 }
@@ -642,8 +670,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
642670
643671 private def eraseArray (tp : Type )(using Context ) = {
644672 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
647674 else JavaArrayType (erasureFn(sourceLanguage, semiEraseVCs = false , isConstructor, isSymbol, wildcardOK)(elemtp))
648675 }
649676
0 commit comments