From ef3ab60c9d2273d6ccb01cea2d4d469d393c1a83 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 27 Mar 2024 10:20:56 +0100 Subject: [PATCH] Replace mergeIfSuper Replace mergeIfSuper by a different algorithm that is more efficient. We drop or-summands in both arguments of a lub that are subsumed by the other. This avoids expesnive recusive calls to lub or expensive comparisons with union types on the right. --- .../dotty/tools/dotc/core/TypeComparer.scala | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 302ad7987889..3f46994c2262 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2363,7 +2363,16 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || isBottom(tp2) then tp1 else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || isBottom(tp1) then tp2 else - def mergedLub(tp1: Type, tp2: Type): Type = { + def mergedLub(tp1: Type, tp2: Type): Type = + // First, if tp1 and tp2 are the same singleton type, return one of them. + if tp1.isSingleton && isSubType(tp1, tp2, whenFrozen = !canConstrain) then + return tp2 + if tp2.isSingleton && isSubType(tp2, tp1, whenFrozen = !canConstrain) then + return tp1 + + // Second, handle special cases when tp1 and tp2 are disjunctions of + // singleton types. This saves time otherwise spent in + // costly subtype comparisons performed in dropIfSub below. tp1.atoms match case Atoms.Range(lo1, hi1) if !widenInUnions => tp2.atoms match @@ -2373,18 +2382,22 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if (hi1 & hi2).isEmpty then return orType(tp1, tp2, isSoft = isSoft) case none => case none => - val t1 = mergeIfSuper(tp1, tp2, canConstrain) - if (t1.exists) return t1 - val t2 = mergeIfSuper(tp2, tp1, canConstrain) - if (t2.exists) return t2 - - def widen(tp: Type) = if (widenInUnions) tp.widen else tp.widenIfUnstable + // Third, try to simplify after widening as follows: + // 1. Drop all or-factors in tp2 that are subtypes of an or-factor + // in tp1, yielding tp2Final. + // 2. Drop all or-factors in tp1 that are subtypes of an or-factor + // in tp2Final, yielding tp1Final. + // 3. Combine the two final types in an OrType + def widen(tp: Type) = + if widenInUnions then tp.widen else tp.widenIfUnstable val tp1w = widen(tp1) val tp2w = widen(tp2) - if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w, canConstrain = canConstrain, isSoft = isSoft) - else orType(tp1w, tp2w, isSoft = isSoft) // no need to check subtypes again - } + val tp2Final = dropIfSub(tp2w, tp1w, canConstrain) + val tp1Final = dropIfSub(tp1w, tp2Final, canConstrain) + recombine(tp1Final, tp2Final, orType(_, _, isSoft = isSoft)) + end mergedLub + mergedLub(dropExpr(tp1.stripLazyRef), dropExpr(tp2.stripLazyRef)) } @@ -2448,21 +2461,34 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling Nil } - private def recombineAnd(tp: AndType, tp1: Type, tp2: Type) = - if (!tp1.exists) tp2 - else if (!tp2.exists) tp1 - else tp.derivedAndType(tp1, tp2) + private def recombine(tp1: Type, tp2: Type, rebuild: (Type, Type) => Type): Type = + if !tp1.exists then tp2 + else if !tp2.exists then tp1 + else rebuild(tp1, tp2) + + + private def recombine(tp: AndOrType, tp1: Type, tp2: Type): Type = + recombine(tp1, tp2, tp.derivedAndOrType) /** If some (&-operand of) `tp` is a supertype of `sub` replace it with `NoType`. */ private def dropIfSuper(tp: Type, sub: Type): Type = - if (isSubTypeWhenFrozen(sub, tp)) NoType - else tp match { + if isSubTypeWhenFrozen(sub, tp) then NoType + else tp match case tp @ AndType(tp1, tp2) => - recombineAnd(tp, dropIfSuper(tp1, sub), dropIfSuper(tp2, sub)) + recombine(tp, dropIfSuper(tp1, sub), dropIfSuper(tp2, sub)) case _ => tp - } + + private def dropIfSub(tp: Type, sup: Type, canConstrain: Boolean): Type = + def isSub(sup: Type): Boolean = sup.stripTypeVar match + case OrType(sup1, sup2) => isSub(sup1) || isSub(sup2) + case _ => isSubType(tp, sup, whenFrozen = !canConstrain) + tp.stripTypeVar match + case tp @ OrType(tp1, tp2) => + recombine(tp, dropIfSub(tp1, sup, canConstrain), dropIfSub(tp2, sup, canConstrain)) + case _ => + if isSub(sup) then NoType else tp /** Merge `t1` into `tp2` if t1 is a subtype of some &-summand of tp2. */