Skip to content

Commit

Permalink
Merge pull request #15556 from dotty-staging/fix-15525
Browse files Browse the repository at this point in the history
Optimize isSameType for invariant applied types with the same structure
  • Loading branch information
odersky authored Jun 30, 2022
2 parents fad3175 + a54fac4 commit f157978
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 8 deletions.
47 changes: 39 additions & 8 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
53 changes: 53 additions & 0 deletions tests/neg/i15525.scala
Original file line number Diff line number Diff line change
@@ -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
55 changes: 55 additions & 0 deletions tests/pos/i15525.scala
Original file line number Diff line number Diff line change
@@ -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)))

0 comments on commit f157978

Please sign in to comment.