Skip to content

Commit c12a6fd

Browse files
Reimplement TypeErasure#erasedLub with the documented algorithm
This is removing a disparity between lubs obtained before and after erasure. Consider this example: ``` if b ast.Trees.Match(???) else ast.Trees.Literal(???) ``` Before erasure this would be an or type that's later widen to `ast.Trees.TermTree`. Because of `erasedLub` bias for classes, recomputing this lub after erasure lead to `ast.Trees.Tree` instead. This disparity prevents a local optimisation from being Ycheckable.
1 parent 564a764 commit c12a6fd

File tree

1 file changed

+33
-19
lines changed

1 file changed

+33
-19
lines changed

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,8 @@ object TypeErasure {
226226
* S is last : in the linearization of the first argument type `tp1`
227227
* there are no minimal common superclasses or traits that
228228
* come after S.
229-
* (the reason to pick last is that we prefer classes over traits that way).
229+
* The reason to pick last is that we prefer classes over traits that way,
230+
* which leads to more predictable bytecode and (?) faster dynamic dispatch.
230231
*/
231232
def erasedLub(tp1: Type, tp2: Type)(implicit ctx: Context): Type = tp1 match {
232233
case JavaArrayType(elem1) =>
@@ -245,25 +246,38 @@ object TypeErasure {
245246
case JavaArrayType(_) => defn.ObjectType
246247
case _ =>
247248
val cls2 = tp2.classSymbol
248-
@tailrec def loop(bcs: List[ClassSymbol], bestSoFar: ClassSymbol): ClassSymbol = bcs match {
249-
case bc :: bcs1 =>
250-
if (cls2.derivesFrom(bc)) {
251-
val newBest = if (bestSoFar.derivesFrom(bc)) bestSoFar else bc
252-
253-
if (!bc.is(Trait) && bc != defn.AnyClass)
254-
newBest
255-
else
256-
loop(bcs1, newBest)
257-
} else
258-
loop(bcs1, bestSoFar)
259-
case nil =>
260-
bestSoFar
249+
250+
/** takeWhile+1 */
251+
def takeUntil[T](l: List[T])(f: T => Boolean): List[T] = {
252+
@tailrec def loop(tail: List[T], acc: List[T]): List[T] =
253+
tail match {
254+
case h :: t => loop(if (f(h)) t else Nil, h :: acc)
255+
case Nil => acc.reverse
256+
}
257+
loop(l, Nil)
258+
}
259+
260+
// We are not interested in anything that is not a supertype of tp2
261+
val tp2superclasses = tp1.baseClasses.filter(cls2.derivesFrom)
262+
263+
// From the spec, "Linearization also satisfies the property that a
264+
// linearization of a class always contains the linearization of its
265+
// direct superclass as a suffix"; it's enought to consider every
266+
// candidate up to the first class.
267+
val candidates = takeUntil(tp2superclasses)(!_.is(Trait))
268+
269+
// Candidates st "no other common superclass or trait derives from S"
270+
val minimums = candidates.filter { cand =>
271+
candidates.forall(x => !x.derivesFrom(cand) || x.eq(cand))
272+
}
273+
274+
// Pick the last minimum to prioritise classes over traits
275+
minimums.lastOption match {
276+
case Some(lub) if lub != defn.AnyClass && lub != defn.AnyValClass =>
277+
lub.typeRef
278+
case _ => // Any/AnyVal only exist before erasure
279+
defn.ObjectType
261280
}
262-
val t = loop(tp1.baseClasses, defn.ObjectClass)
263-
if (t eq defn.AnyValClass)
264-
// while AnyVal is a valid common super class for primitives it does not exist after erasure
265-
defn.ObjectType
266-
else t.typeRef
267281
}
268282
}
269283

0 commit comments

Comments
 (0)