@@ -8,8 +8,7 @@ import Phases.{gettersPhase, elimByNamePhase}
8
8
import StdNames .nme
9
9
import TypeOps .refineUsingParent
10
10
import collection .mutable
11
- import util .Stats
12
- import util .NoSourcePosition
11
+ import util .{Stats , NoSourcePosition , EqHashMap }
13
12
import config .Config
14
13
import config .Feature .migrateTo3
15
14
import config .Printers .{subtyping , gadts , matchTypes , noPrinter }
@@ -163,6 +162,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
163
162
/** A flag to prevent recursive joins when comparing AndTypes on the left */
164
163
private var joined = false
165
164
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
+
166
179
private inline def inFrozenGadtIf [T ](cond : Boolean )(inline op : T ): T = {
167
180
val savedFrozenGadt = frozenGadt
168
181
frozenGadt ||= cond
@@ -1553,8 +1566,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
1553
1566
&& defn.isByNameFunction(arg2.dealias) =>
1554
1567
isSubArg(arg1res, arg2.argInfos.head)
1555
1568
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)
1558
1572
1559
1573
isSubArg(args1.head, args2.head)
1560
1574
} && recurArgs(args1.tail, args2.tail, tparams2.tail)
@@ -2012,11 +2026,28 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
2012
2026
2013
2027
// Type equality =:=
2014
2028
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
+ */
2016
2034
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
2020
2051
2021
2052
override protected def isSame (tp1 : Type , tp2 : Type )(using Context ): Boolean = isSameType(tp1, tp2)
2022
2053
0 commit comments