Skip to content

Commit 238b1fb

Browse files
committed
Attacking exponential complexity in TypeMaps
- Don't normalize existentials during the `contain`-s type map; `ExistentialType#normalize' calls contains internally and an exponential blowup ensues. - Ensure that the type map used in variance validation never returns modified types in order to avoid needless cloning of symbols. The enclosed test case still gets stuck in Uncurry, thanks to the way that `TypeMap#mapOver(List[Symbol])` recurses through the type first to check whether the type map would be an no-op or not. If not, it repeats the type map with cloned symbols. Doing the work twice at each level of recursion blows up the complexity. Removing that "fast path" allows the enclosed test to compile completely. As at this commit, it gets stuck in uncurry, which dealiases `s.List` to `s.c.i.List` within the type. Some more background on the troublesome part of `TypeMap`: http://lrytz.github.io/scala-aladdin-bugtracker/displayItem.do%3Fid=1210.html scala@f8b2b21
1 parent a24ca7f commit 238b1fb

File tree

5 files changed

+44
-13
lines changed

5 files changed

+44
-13
lines changed

src/reflect/scala/reflect/internal/Variances.scala

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,21 @@ trait Variances {
122122
* same is true of the parameters (ValDefs) unless we are inside a
123123
* refinement, in which case they are checked from here.
124124
*/
125-
def apply(tp: Type): Type = tp match {
126-
case _ if isUncheckedVariance(tp) => tp
127-
case _ if resultTypeOnly(tp) => this(tp.resultType)
128-
case TypeRef(_, sym, _) if sym.isAliasType => this(tp.normalize)
129-
case TypeRef(_, sym, _) if !sym.variance.isInvariant => checkVarianceOfSymbol(sym) ; mapOver(tp)
130-
case RefinedType(_, _) => withinRefinement(mapOver(tp))
131-
case ClassInfoType(parents, _, _) => parents foreach this ; tp
132-
case mt @ MethodType(_, result) => flipped(mt.paramTypes foreach this) ; this(result)
133-
case _ => mapOver(tp)
125+
def apply(tp: Type): Type = {
126+
tp match {
127+
case _ if isUncheckedVariance(tp) =>
128+
case _ if resultTypeOnly(tp) => this(tp.resultType)
129+
case TypeRef(_, sym, _) if sym.isAliasType => this(tp.normalize)
130+
case TypeRef(_, sym, _) if !sym.variance.isInvariant => checkVarianceOfSymbol(sym) ; mapOver(tp)
131+
case RefinedType(_, _) => withinRefinement(mapOver(tp))
132+
case ClassInfoType(parents, _, _) => parents foreach this
133+
case mt @ MethodType(_, result) => flipped(mt.paramTypes foreach this) ; this(result)
134+
case _ => mapOver(tp)
135+
}
136+
// We're using TypeMap here for type traversal only. To avoid wasteful symbol
137+
// cloning during the recursion, it is important to return the input `tp`, rather
138+
// than the result of the pattern match above, which normalizes types.
139+
tp
134140
}
135141
def validateDefinition(base: Symbol) {
136142
val saved = this.base

src/reflect/scala/reflect/internal/tpe/TypeMaps.scala

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -998,10 +998,20 @@ private[internal] trait TypeMaps {
998998
class ContainsCollector(sym: Symbol) extends TypeCollector(false) {
999999
def traverse(tp: Type) {
10001000
if (!result) {
1001-
tp.normalize match {
1002-
case TypeRef(_, sym1, _) if (sym == sym1) => result = true
1003-
case SingleType(_, sym1) if (sym == sym1) => result = true
1004-
case _ => mapOver(tp)
1001+
tp match {
1002+
case _: ExistentialType =>
1003+
// ExistentialType#normalize internally calls contains, which leads to exponential performance
1004+
// for types like: `A[_ <: B[_ <: ... ]]`. Example: pos/existential-contains.scala.
1005+
//
1006+
// We can just map over the components and wait until we see the underlying type before we call
1007+
// normalize.
1008+
mapOver(tp)
1009+
case _ =>
1010+
tp.normalize match {
1011+
case TypeRef(_, sym1, _) if (sym == sym1) => result = true
1012+
case SingleType(_, sym1) if (sym == sym1) => result = true
1013+
case _ => mapOver(tp)
1014+
}
10051015
}
10061016
}
10071017
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class C {
2+
class L[+A]
3+
def test = {
4+
val foo:
5+
L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: _ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: _ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_]]]]]]]]]]]]]]]]]]]]]]]]
6+
= ??? } }
7+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Ystop-after:refchecks
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class C {
2+
type L[+A] = scala.collection.immutable.List[A]
3+
def test = {
4+
val foo:
5+
L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: _ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: _ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_ <: L[_]]]]]]]]]]]]]]]]]]]]]]]]
6+
= ??? } }
7+

0 commit comments

Comments
 (0)