Skip to content

Commit e2fdcb2

Browse files
committed
Special case comparisons between LazyRefs and bottom/top types
Fixes #11064 When comparing a LazyRef with some other type, we need to force the LazyRef which can potentially lead to a cycle (as observed in #11064). However, in the situations ``` Nothing <: LazyRef(...) LazyRef(...) <: Any ``` we can skip that since the comparison is always true. This scheme fixes #11064, but it breaks down in similar situations if additional bounds are introduced (see neg/i11064.scala). So it is quite fragile. But it's probably the best we can do. The example in #11064 looks harmless, but type-theoretically it's actually a pretty explosive mix of F-bounded types and existential types (in Scala 2). In Scala 2, we need to force more for F-bounded types and we need to approximate existential types by dependent types, so not everything works the same way. My long term advice would be: get rid of F-bounds. Model them with intersections at the use site.
1 parent 5672999 commit e2fdcb2

File tree

3 files changed

+22
-3
lines changed

3 files changed

+22
-3
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
306306
}
307307
compareWild
308308
case tp2: LazyRef =>
309-
!tp2.evaluating && recur(tp1, tp2.ref)
309+
isBottom(tp1) || !tp2.evaluating && recur(tp1, tp2.ref)
310310
case tp2: AnnotatedType if !tp2.isRefining =>
311311
recur(tp1, tp2.parent)
312312
case tp2: ThisType =>
@@ -373,7 +373,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
373373
if (recur(info1.alias, tp2)) return true
374374
if (tp1.prefix.isStable) return tryLiftedToThis1
375375
case _ =>
376-
if (tp1 eq NothingType) return true
376+
if (tp1 eq NothingType) || isBottom(tp1) then return true
377377
}
378378
thirdTry
379379
case tp1: TypeParamRef =>
@@ -420,7 +420,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
420420
// If `tp1` is in train of being evaluated, don't force it
421421
// because that would cause an assertionError. Return false instead.
422422
// See i859.scala for an example where we hit this case.
423-
!tp1.evaluating && recur(tp1.ref, tp2)
423+
tp2.isRef(AnyClass, skipRefined = false)
424+
|| !tp1.evaluating && recur(tp1.ref, tp2)
424425
case tp1: AnnotatedType if !tp1.isRefining =>
425426
recur(tp1.parent, tp2)
426427
case AndType(tp11, tp12) =>

tests/neg/i11064.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
trait TypedArray[T, Repr]
2+
3+
trait Ops[T <: TypedArray[_, T]] {
4+
def typedArray(): T
5+
}
6+
7+
object Test {
8+
def test(ops: Ops[_ <: TypedArray[_ <: AnyRef, _]]) = ops.typedArray() // error: Recursion limit exceeded.
9+
}

tests/pos/i11064.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
trait TypedArray[T, Repr]
2+
3+
trait Ops[T <: TypedArray[_, T]] {
4+
def typedArray(): T
5+
}
6+
7+
object Test {
8+
def test(ops: Ops[_ <: TypedArray[_, _]]) = ops.typedArray()
9+
}

0 commit comments

Comments
 (0)