diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 6500c166c1a4..6d79f377c84e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -8,8 +8,7 @@ import Phases.{gettersPhase, elimByNamePhase} import StdNames.nme import TypeOps.refineUsingParent import collection.mutable -import util.Stats -import util.NoSourcePosition +import util.{Stats, NoSourcePosition, EqHashMap} import config.Config import config.Feature.migrateTo3 import config.Printers.{subtyping, gadts, matchTypes, noPrinter} @@ -163,6 +162,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling /** A flag to prevent recursive joins when comparing AndTypes on the left */ private var joined = false + /** A variable to keep track of number of outstanding isSameType tests */ + private var sameLevel = 0 + + /** A map that records successful isSameType comparisons. + * Used together with `sameLevel` to avoid exponential blowUp of isSameType + * comparisons for deeply nested invariant applied types. + */ + private var sames: util.EqHashMap[Type, Type] | Null = null + + /** The `sameLevel` nesting depth from which on we want to keep track + * of isSameTypes suucesses using `sames` + */ + val startSameTypeTrackingLevel = 3 + private inline def inFrozenGadtIf[T](cond: Boolean)(inline op: T): T = { val savedFrozenGadt = frozenGadt frozenGadt ||= cond @@ -1553,8 +1566,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && defn.isByNameFunction(arg2.dealias) => isSubArg(arg1res, arg2.argInfos.head) case _ => - (v > 0 || isSubType(arg2, arg1)) && - (v < 0 || isSubType(arg1, arg2)) + if v < 0 then isSubType(arg2, arg1) + else if v > 0 then isSubType(arg1, arg2) + else isSameType(arg2, arg1) isSubArg(args1.head, args2.head) } && recurArgs(args1.tail, args2.tail, tparams2.tail) @@ -2012,11 +2026,28 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // Type equality =:= - /** Two types are the same if are mutual subtypes of each other */ + /** Two types are the same if they are mutual subtypes of each other. + * To avoid exponential blowup for deeply nested invariant applied types, + * we cache successes once the stack of outstanding isSameTypes reaches + * depth `startSameTypeTrackingLevel`. See pos/i15525.scala, where this matters. + */ def isSameType(tp1: Type, tp2: Type): Boolean = - if (tp1 eq NoType) false - else if (tp1 eq tp2) true - else isSubType(tp1, tp2) && isSubType(tp2, tp1) + if tp1 eq NoType then false + else if tp1 eq tp2 then true + else if sames != null && (sames.nn.lookup(tp1) eq tp2) then true + else + val savedSames = sames + sameLevel += 1 + if sameLevel >= startSameTypeTrackingLevel then + Stats.record("cache same type") + sames = new util.EqHashMap() + val res = + try isSubType(tp1, tp2) && isSubType(tp2, tp1) + finally + sameLevel -= 1 + sames = savedSames + if res && sames != null then sames.nn(tp2) = tp1 + res override protected def isSame(tp1: Type, tp2: Type)(using Context): Boolean = isSameType(tp1, tp2) diff --git a/tests/neg/i15525.scala b/tests/neg/i15525.scala new file mode 100644 index 000000000000..0813d7c82435 --- /dev/null +++ b/tests/neg/i15525.scala @@ -0,0 +1,53 @@ +class /[D, T] +class Delegating[D] + +type Aux[E] = Container { type Elements = E } + +class Container: + type Elements = Delegating[Delegates] + type Delegates + +class Resolution[E](value: Aux[E]): + type Type = Aux[E] + +def element22( + transmittable0: Resolution[?], transmittable1: Resolution[?], + transmittable2: Resolution[?], transmittable3: Resolution[?], + transmittable4: Resolution[?], transmittable5: Resolution[?], + transmittable6: Resolution[?], transmittable7: Resolution[?], + transmittable8: Resolution[?], transmittable9: Resolution[?], + transmittable10: Resolution[?], transmittable11: Resolution[?], + transmittable12: Resolution[?], transmittable13: Resolution[?], + transmittable14: Resolution[?], transmittable15: Resolution[?], + transmittable16: Resolution[?], transmittable17: Resolution[?], + transmittable18: Resolution[?], transmittable19: Resolution[?], + transmittable20: Resolution[?], transmittable21: Resolution[?]) +: Container { + type Delegates = + transmittable0.Type / transmittable1.Type / + transmittable2.Type / transmittable3.Type / + transmittable4.Type / transmittable5.Type / + transmittable6.Type / transmittable7.Type / + transmittable8.Type / transmittable9.Type / + transmittable10.Type / transmittable11.Type / + transmittable12.Type / transmittable13.Type / + transmittable14.Type / transmittable15.Type / + transmittable16.Type / transmittable17.Type / + transmittable18.Type / transmittable19.Type / + transmittable20.Type / transmittable21.Type + } = ??? + +def test22 = + Resolution( + element22( + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0), // error // error + Resolution(element0), Resolution(element0)))// error // error diff --git a/tests/pos/i15525.scala b/tests/pos/i15525.scala new file mode 100644 index 000000000000..89d4bb38d94b --- /dev/null +++ b/tests/pos/i15525.scala @@ -0,0 +1,55 @@ +class /[D, T] +class Delegating[D] + +type Aux[E] = Container { type Elements = E } + +class Container: + type Elements = Delegating[Delegates] + type Delegates + +class Resolution[E](value: Aux[E]): + type Type = Aux[E] + +def element0: Container { type Delegates = Unit } = ??? + +def element22( + transmittable0: Resolution[?], transmittable1: Resolution[?], + transmittable2: Resolution[?], transmittable3: Resolution[?], + transmittable4: Resolution[?], transmittable5: Resolution[?], + transmittable6: Resolution[?], transmittable7: Resolution[?], + transmittable8: Resolution[?], transmittable9: Resolution[?], + transmittable10: Resolution[?], transmittable11: Resolution[?], + transmittable12: Resolution[?], transmittable13: Resolution[?], + transmittable14: Resolution[?], transmittable15: Resolution[?], + transmittable16: Resolution[?], transmittable17: Resolution[?], + transmittable18: Resolution[?], transmittable19: Resolution[?], + transmittable20: Resolution[?], transmittable21: Resolution[?]) +: Container { + type Delegates = + transmittable0.Type / transmittable1.Type / + transmittable2.Type / transmittable3.Type / + transmittable4.Type / transmittable5.Type / + transmittable6.Type / transmittable7.Type / + transmittable8.Type / transmittable9.Type / + transmittable10.Type / transmittable11.Type / + transmittable12.Type / transmittable13.Type / + transmittable14.Type / transmittable15.Type / + transmittable16.Type / transmittable17.Type / + transmittable18.Type / transmittable19.Type / + transmittable20.Type / transmittable21.Type + } = ??? + +def test22 = + Resolution( + element22( + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0), + Resolution(element0), Resolution(element0)))