@@ -248,54 +248,62 @@ object TypeErasure {
248
248
* The reason to pick last is that we prefer classes over traits that way,
249
249
* which leads to more predictable bytecode and (?) faster dynamic dispatch.
250
250
*/
251
- def erasedLub (tp1 : Type , tp2 : Type )(implicit ctx : Context ): Type = tp1 match {
252
- case JavaArrayType (elem1) =>
253
- import dotty .tools .dotc .transform .TypeUtils ._
254
- tp2 match {
255
- case JavaArrayType (elem2) =>
256
- if (elem1.isPrimitiveValueType || elem2.isPrimitiveValueType) {
257
- if (elem1.classSymbol eq elem2.classSymbol) // same primitive
258
- JavaArrayType (elem1)
259
- else defn.ObjectType
260
- } else JavaArrayType (erasedLub(elem1, elem2))
261
- case _ => defn.ObjectType
262
- }
263
- case _ =>
264
- tp2 match {
265
- case JavaArrayType (_) => defn.ObjectType
266
- case _ =>
267
- val cls2 = tp2.classSymbol
268
-
269
- /** takeWhile+1 */
270
- def takeUntil [T ](l : List [T ])(f : T => Boolean ): List [T ] = {
271
- @ tailrec def loop (tail : List [T ], acc : List [T ]): List [T ] =
272
- tail match {
273
- case h :: t => loop(if (f(h)) t else Nil , h :: acc)
274
- case Nil => acc.reverse
275
- }
276
- loop(l, Nil )
277
- }
278
-
279
- // We are not interested in anything that is not a supertype of tp2
280
- val tp2superclasses = tp1.baseClasses.filter(cls2.derivesFrom)
281
-
282
- // From the spec, "Linearization also satisfies the property that a
283
- // linearization of a class always contains the linearization of its
284
- // direct superclass as a suffix"; it's enough to consider every
285
- // candidate up to the first class.
286
- val candidates = takeUntil(tp2superclasses)(! _.is(Trait ))
287
-
288
- // Candidates st "no other common superclass or trait derives from S"
289
- val minimums = candidates.filter { cand =>
290
- candidates.forall(x => ! x.derivesFrom(cand) || x.eq(cand))
291
- }
292
-
293
- // Pick the last minimum to prioritise classes over traits
294
- minimums.lastOption match {
295
- case Some (lub) => valueErasure(lub.typeRef)
296
- case _ => defn.ObjectType
297
- }
298
- }
251
+ def erasedLub (tp1 : Type , tp2 : Type )(implicit ctx : Context ): Type = {
252
+ // After erasure, C | {Null, Nothing} is just C, if C is a reference type.
253
+ // We need to short-circuit this case here because the regular lub logic below
254
+ // relies on the class hierarchy, which doesn't properly capture `Null`s subtyping
255
+ // behaviour.
256
+ if (defn.isBottomType(tp1) && tp2.derivesFrom(defn.ObjectClass )) return tp2
257
+ if (defn.isBottomType(tp2) && tp1.derivesFrom(defn.ObjectClass )) return tp1
258
+ tp1 match {
259
+ case JavaArrayType (elem1) =>
260
+ import dotty .tools .dotc .transform .TypeUtils ._
261
+ tp2 match {
262
+ case JavaArrayType (elem2) =>
263
+ if (elem1.isPrimitiveValueType || elem2.isPrimitiveValueType) {
264
+ if (elem1.classSymbol eq elem2.classSymbol) // same primitive
265
+ JavaArrayType (elem1)
266
+ else defn.ObjectType
267
+ } else JavaArrayType (erasedLub(elem1, elem2))
268
+ case _ => defn.ObjectType
269
+ }
270
+ case _ =>
271
+ tp2 match {
272
+ case JavaArrayType (_) => defn.ObjectType
273
+ case _ =>
274
+ val cls2 = tp2.classSymbol
275
+
276
+ /** takeWhile+1 */
277
+ def takeUntil [T ](l : List [T ])(f : T => Boolean ): List [T ] = {
278
+ @ tailrec def loop (tail : List [T ], acc : List [T ]): List [T ] =
279
+ tail match {
280
+ case h :: t => loop(if (f(h)) t else Nil , h :: acc)
281
+ case Nil => acc.reverse
282
+ }
283
+ loop(l, Nil )
284
+ }
285
+
286
+ // We are not interested in anything that is not a supertype of tp2
287
+ val tp2superclasses = tp1.baseClasses.filter(cls2.derivesFrom)
288
+
289
+ // From the spec, "Linearization also satisfies the property that a
290
+ // linearization of a class always contains the linearization of its
291
+ // direct superclass as a suffix"; it's enough to consider every
292
+ // candidate up to the first class.
293
+ val candidates = takeUntil(tp2superclasses)(! _.is(Trait ))
294
+
295
+ // Candidates st "no other common superclass or trait derives from S"
296
+ val minimums = candidates.filter { cand =>
297
+ candidates.forall(x => ! x.derivesFrom(cand) || x.eq(cand))
298
+ }
299
+
300
+ // Pick the last minimum to prioritise classes over traits
301
+ minimums.lastOption match {
302
+ case Some (lub) => valueErasure(lub.typeRef)
303
+ case _ => defn.ObjectType
304
+ }
305
+ }
306
+ }
299
307
}
300
308
301
309
/** The erased greatest lower bound of two erased type picks one of the two argument types.
0 commit comments