Skip to content

Commit cf0b66f

Browse files
committed
Handle bidirectional typebounds in AvoidMap
An example of a bidirectional TypeBounds is i15523: `[A, B >: A]`, as an ordering: `A <: B`, as types and their bounds: `A >: Nothing <: B` and `B >: A <: Any`. It's not an fatal cycle, such as `A <: B <: A`. It's more like "My father is DJ, DJ's son is me" - if you then go ask who DJ's son's father is... you might never get an answer back.
1 parent 8fc154e commit cf0b66f

File tree

6 files changed

+47
-3
lines changed

6 files changed

+47
-3
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,8 @@ object TypeOps:
429429
sym.is(Package) || sym.isStatic && isStaticPrefix(pre.prefix)
430430
case _ => true
431431

432+
private var alreadyExpanding: List[TypeRef] = Nil
433+
432434
override def apply(tp: Type): Type =
433435
try
434436
tp match
@@ -442,8 +444,12 @@ object TypeOps:
442444
tp.info match {
443445
case info: AliasingBounds =>
444446
apply(info.alias)
445-
case TypeBounds(lo, hi) =>
446-
range(atVariance(-variance)(apply(lo)), apply(hi))
447+
case bounds: TypeBounds if !alreadyExpanding.contains(tp) =>
448+
val saved = alreadyExpanding
449+
try
450+
alreadyExpanding ::= tp
451+
expandBounds(bounds)
452+
finally alreadyExpanding = saved
447453
case info: ClassInfo =>
448454
range(defn.NothingType, apply(classBound(info)))
449455
case _ =>

compiler/src/dotty/tools/dotc/typer/Inferencing.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ object Inferencing {
417417
if safeToInstantiate then tvar.instantiate(fromBelow = v == -1)
418418
else {
419419
val bounds = TypeComparer.fullBounds(tvar.origin)
420-
if bounds.hi <:< bounds.lo || bounds.hi.classSymbol.is(Final) then
420+
if (bounds.hi frozen_<:< bounds.lo) || bounds.hi.classSymbol.is(Final) then
421421
tvar.instantiate(fromBelow = false)
422422
else {
423423
// We do not add the created symbols to GADT constraint immediately, since they may have inter-dependencies.

tests/pos/i14287.min.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
enum Foo[+F[_]]:
2+
case Bar[B[_]](value: Foo[B]) extends Foo[B]
3+
4+
class Test:
5+
def test[X[_]](foo: Foo[X]): Foo[X] = foo match
6+
case Foo.Bar(Foo.Bar(x)) => Foo.Bar(x)
7+
case _ => foo

tests/pos/i14287.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// scalac: -Yno-deep-subtypes
2+
enum Free[+F[_], A]:
3+
case Return(a: A)
4+
case Suspend(s: F[A])
5+
case FlatMap[F[_], A, B](
6+
s: Free[F, A],
7+
f: A => Free[F, B]) extends Free[F, B]
8+
9+
def flatMap[F2[x] >: F[x], B](f: A => Free[F2,B]): Free[F2,B] =
10+
FlatMap(this, f)
11+
12+
@scala.annotation.tailrec
13+
final def step: Free[F, A] = this match
14+
case FlatMap(FlatMap(fx, f), g) => fx.flatMap(x => f(x).flatMap(y => g(y))).step
15+
case FlatMap(Return(x), f) => f(x).step
16+
case _ => this

tests/pos/i15523.avoid.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// scalac: -Werror
2+
// like the original, but with a case body `a`
3+
// which caused type avoidance to infinitely recurse
4+
sealed trait Parent
5+
final case class Leaf[A, B >: A](a: A, b: B) extends Parent
6+
7+
def run(x: Parent) = x match
8+
case Leaf(a, _) => a

tests/pos/i15523.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// scalac: -Werror
2+
sealed trait Parent
3+
final case class Leaf[A, B >: A](a: A, b: B) extends Parent
4+
5+
def run(x: Parent): Unit = x match {
6+
case Leaf(a, b) =>
7+
}

0 commit comments

Comments
 (0)