Skip to content

Commit 2165c4f

Browse files
committed
Optimize same type test for invariant applied types with the same structure
Two deeply applied types with the same structure uses recursive isSameType tests. Each of these translated to two isSubType tests which can lead to exponential blowup relative to the type's nesting depth. This problem does not occur if the two types are `eq`. But two types might still be structurally equal modulo dealiasing. We now cache isSameType successes after a certain nesting level to avoid recomputation. Fixes #15525. Reclassifies #15365 as a pos test
1 parent 6062192 commit 2165c4f

File tree

4 files changed

+150
-9
lines changed

4 files changed

+150
-9
lines changed

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

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import Phases.{gettersPhase, elimByNamePhase}
88
import StdNames.nme
99
import TypeOps.refineUsingParent
1010
import collection.mutable
11-
import util.Stats
12-
import util.NoSourcePosition
11+
import util.{Stats, NoSourcePosition, EqHashMap}
1312
import config.Config
1413
import config.Feature.migrateTo3
1514
import 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(arg1, arg2)
1570+
else if v < 0 then isSubType(arg2, arg1)
1571+
else isSameType(arg1, arg2)
15581572

15591573
isSubArg(args1.head, args2.head)
15601574
} && recurArgs(args1.tail, args2.tail, tparams2.tail)
@@ -2012,11 +2026,30 @@ 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
2038+
sames != null && (sames.nn.lookup(tp1) eq tp2)
2039+
|| {
2040+
val savedSames = sames
2041+
sameLevel += 1
2042+
if sameLevel >= startSameTypeTrackingLevel then
2043+
Stats.record("cache same type")
2044+
sames = new util.EqHashMap()
2045+
val res =
2046+
try isSubType(tp1, tp2) && isSubType(tp2, tp1)
2047+
finally
2048+
sameLevel -= 1
2049+
sames = savedSames
2050+
if res && sames != null then sames.nn(tp2) = tp1
2051+
res
2052+
}
20202053

20212054
override protected def isSame(tp1: Type, tp2: Type)(using Context): Boolean = isSameType(tp1, tp2)
20222055

tests/neg/i15525.scala

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
class /[D, T]
2+
class Delegating[D]
3+
4+
type Aux[E] = Container { type Elements = E }
5+
6+
class Container:
7+
type Elements = Delegating[Delegates]
8+
type Delegates
9+
10+
class Resolution[E](value: Aux[E]):
11+
type Type = Aux[E]
12+
13+
def element22(
14+
transmittable0: Resolution[?], transmittable1: Resolution[?],
15+
transmittable2: Resolution[?], transmittable3: Resolution[?],
16+
transmittable4: Resolution[?], transmittable5: Resolution[?],
17+
transmittable6: Resolution[?], transmittable7: Resolution[?],
18+
transmittable8: Resolution[?], transmittable9: Resolution[?],
19+
transmittable10: Resolution[?], transmittable11: Resolution[?],
20+
transmittable12: Resolution[?], transmittable13: Resolution[?],
21+
transmittable14: Resolution[?], transmittable15: Resolution[?],
22+
transmittable16: Resolution[?], transmittable17: Resolution[?],
23+
transmittable18: Resolution[?], transmittable19: Resolution[?],
24+
transmittable20: Resolution[?], transmittable21: Resolution[?])
25+
: Container {
26+
type Delegates =
27+
transmittable0.Type / transmittable1.Type /
28+
transmittable2.Type / transmittable3.Type /
29+
transmittable4.Type / transmittable5.Type /
30+
transmittable6.Type / transmittable7.Type /
31+
transmittable8.Type / transmittable9.Type /
32+
transmittable10.Type / transmittable11.Type /
33+
transmittable12.Type / transmittable13.Type /
34+
transmittable14.Type / transmittable15.Type /
35+
transmittable16.Type / transmittable17.Type /
36+
transmittable18.Type / transmittable19.Type /
37+
transmittable20.Type / transmittable21.Type
38+
} = ???
39+
40+
def test22 =
41+
Resolution(
42+
element22(
43+
Resolution(element0), Resolution(element0), // error // error
44+
Resolution(element0), Resolution(element0), // error // error
45+
Resolution(element0), Resolution(element0), // error // error
46+
Resolution(element0), Resolution(element0), // error // error
47+
Resolution(element0), Resolution(element0), // error // error
48+
Resolution(element0), Resolution(element0), // error // error
49+
Resolution(element0), Resolution(element0), // error // error
50+
Resolution(element0), Resolution(element0), // error // error
51+
Resolution(element0), Resolution(element0), // error // error
52+
Resolution(element0), Resolution(element0), // error // error
53+
Resolution(element0), Resolution(element0)))// error // error

tests/neg-custom-args/allow-deep-subtypes/i15365.scala renamed to tests/pos/i15365.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ class OptionInputType[TO](ofType: InputType[TO]) extends InputType[Option[TO]]
1414
type Argument[TA]
1515
def argument[Ta](argumentType: InputType[Ta])(implicit fromInput: FromInput[Ta], res: WithoutInputTypeTags[Ta]): Argument[Option[Ta]] = ???
1616

17-
def test = argument(OptionInputType(??? : InputType[WithTag[Boolean, Int]])) :: Nil // error
17+
def test = argument(OptionInputType(??? : InputType[WithTag[Boolean, Int]])) :: Nil

tests/pos/i15525.scala

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
class /[D, T]
2+
class Delegating[D]
3+
4+
type Aux[E] = Container { type Elements = E }
5+
6+
class Container:
7+
type Elements = Delegating[Delegates]
8+
type Delegates
9+
10+
class Resolution[E](value: Aux[E]):
11+
type Type = Aux[E]
12+
13+
def element0: Container { type Delegates = Unit } = ???
14+
15+
def element22(
16+
transmittable0: Resolution[?], transmittable1: Resolution[?],
17+
transmittable2: Resolution[?], transmittable3: Resolution[?],
18+
transmittable4: Resolution[?], transmittable5: Resolution[?],
19+
transmittable6: Resolution[?], transmittable7: Resolution[?],
20+
transmittable8: Resolution[?], transmittable9: Resolution[?],
21+
transmittable10: Resolution[?], transmittable11: Resolution[?],
22+
transmittable12: Resolution[?], transmittable13: Resolution[?],
23+
transmittable14: Resolution[?], transmittable15: Resolution[?],
24+
transmittable16: Resolution[?], transmittable17: Resolution[?],
25+
transmittable18: Resolution[?], transmittable19: Resolution[?],
26+
transmittable20: Resolution[?], transmittable21: Resolution[?])
27+
: Container {
28+
type Delegates =
29+
transmittable0.Type / transmittable1.Type /
30+
transmittable2.Type / transmittable3.Type /
31+
transmittable4.Type / transmittable5.Type /
32+
transmittable6.Type / transmittable7.Type /
33+
transmittable8.Type / transmittable9.Type /
34+
transmittable10.Type / transmittable11.Type /
35+
transmittable12.Type / transmittable13.Type /
36+
transmittable14.Type / transmittable15.Type /
37+
transmittable16.Type / transmittable17.Type /
38+
transmittable18.Type / transmittable19.Type /
39+
transmittable20.Type / transmittable21.Type
40+
} = ???
41+
42+
def test22 =
43+
Resolution(
44+
element22(
45+
Resolution(element0), Resolution(element0),
46+
Resolution(element0), Resolution(element0),
47+
Resolution(element0), Resolution(element0),
48+
Resolution(element0), Resolution(element0),
49+
Resolution(element0), Resolution(element0),
50+
Resolution(element0), Resolution(element0),
51+
Resolution(element0), Resolution(element0),
52+
Resolution(element0), Resolution(element0),
53+
Resolution(element0), Resolution(element0),
54+
Resolution(element0), Resolution(element0),
55+
Resolution(element0), Resolution(element0)))

0 commit comments

Comments
 (0)