@@ -8,8 +8,7 @@ import Phases.{gettersPhase, elimByNamePhase}
88import StdNames .nme
99import TypeOps .refineUsingParent
1010import collection .mutable
11- import util .Stats
12- import util .NoSourcePosition
11+ import util .{Stats , NoSourcePosition , EqHashMap }
1312import config .Config
1413import config .Feature .migrateTo3
1514import config .Printers .{subtyping , gadts , matchTypes , noPrinter }
@@ -163,6 +162,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
163162 /** A flag to prevent recursive joins when comparing AndTypes on the left */
164163 private var joined = false
165164
165+ /** A variable to keep track of number of outstanding isSameType tests */
166+ private var sameLevel = 0
167+
168+ /** A map that records successful isSameType comparisons.
169+ * Used together with `sameLevel` to avoid exponential blowUp of isSameType
170+ * comparisons for deeply nested invariant applied types.
171+ */
172+ private var sames : util.EqHashMap [Type , Type ] | Null = null
173+
174+ /** The `sameLevel` nesting depth from which on we want to keep track
175+ * of isSameTypes suucesses using `sames`
176+ */
177+ val startSameTypeTrackingLevel = 3
178+
166179 private inline def inFrozenGadtIf [T ](cond : Boolean )(inline op : T ): T = {
167180 val savedFrozenGadt = frozenGadt
168181 frozenGadt ||= cond
@@ -1553,8 +1566,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
15531566 && defn.isByNameFunction(arg2.dealias) =>
15541567 isSubArg(arg1res, arg2.argInfos.head)
15551568 case _ =>
1556- (v > 0 || isSubType(arg2, arg1)) &&
1557- (v < 0 || isSubType(arg1, arg2))
1569+ if v < 0 then isSubType(arg2, arg1)
1570+ else if v > 0 then isSubType(arg1, arg2)
1571+ else isSameType(arg2, arg1)
15581572
15591573 isSubArg(args1.head, args2.head)
15601574 } && recurArgs(args1.tail, args2.tail, tparams2.tail)
@@ -2012,11 +2026,28 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
20122026
20132027 // Type equality =:=
20142028
2015- /** Two types are the same if are mutual subtypes of each other */
2029+ /** Two types are the same if they are mutual subtypes of each other.
2030+ * To avoid exponential blowup for deeply nested invariant applied types,
2031+ * we cache successes once the stack of outstanding isSameTypes reaches
2032+ * depth `startSameTypeTrackingLevel`. See pos/i15525.scala, where this matters.
2033+ */
20162034 def isSameType (tp1 : Type , tp2 : Type ): Boolean =
2017- if (tp1 eq NoType ) false
2018- else if (tp1 eq tp2) true
2019- else isSubType(tp1, tp2) && isSubType(tp2, tp1)
2035+ if tp1 eq NoType then false
2036+ else if tp1 eq tp2 then true
2037+ else if sames != null && (sames.nn.lookup(tp1) eq tp2) then true
2038+ else
2039+ val savedSames = sames
2040+ sameLevel += 1
2041+ if sameLevel >= startSameTypeTrackingLevel then
2042+ Stats .record(" cache same type" )
2043+ sames = new util.EqHashMap ()
2044+ val res =
2045+ try isSubType(tp1, tp2) && isSubType(tp2, tp1)
2046+ finally
2047+ sameLevel -= 1
2048+ sames = savedSames
2049+ if res && sames != null then sames.nn(tp2) = tp1
2050+ res
20202051
20212052 override protected def isSame (tp1 : Type , tp2 : Type )(using Context ): Boolean = isSameType(tp1, tp2)
20222053
0 commit comments