From d635a56fe172e863a7774128f932bb23b0487ccb Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 27 Jan 2022 18:41:20 +0000 Subject: [PATCH] Break circularity in TypeBounds with LazyRef wraps --- .../tools/dotc/core/ConstraintHandling.scala | 15 ++++++++++++++- tests/pos-deep-subtype/i14287.scala | 15 +++++++++++++++ tests/pos/i14287.min.scala | 6 ++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/pos-deep-subtype/i14287.scala create mode 100644 tests/pos/i14287.min.scala diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 835da2176a33..566ce15bd873 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -127,7 +127,20 @@ trait ConstraintHandling { * of some param when comparing types might lead to infinite recursion. Consider `bounds` instead. */ def fullBounds(param: TypeParamRef)(using Context): TypeBounds = - nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param)) + val lo = wrapRecursiveInLazy(fullLowerBound(param)) + val hi = wrapRecursiveInLazy(fullUpperBound(param)) + nonParamBounds(param).derivedTypeBounds(lo, hi) + + def wrapRecursiveInLazy(tp: Type)(using Context) = + if IsRecursiveAccumulator()(false, tp) then LazyRef.of(tp) + else tp + + class IsRecursiveAccumulator(using Context) extends TypeAccumulator[Boolean]: + private val seen = scala.collection.mutable.HashSet.empty[Type] + def apply(x: Boolean, tp: Type): Boolean = x || tp.match + case tp: LazyRef => false + case tp @ TypeRef(NoPrefix, _) => if seen.add(tp) then apply(false, tp.info) else true + case _ => foldOver(false, tp) /** An approximating map that prevents types nested deeper than maxLevel as * well as WildcardTypes from leaking into the constraint. diff --git a/tests/pos-deep-subtype/i14287.scala b/tests/pos-deep-subtype/i14287.scala new file mode 100644 index 000000000000..c6700c6007c5 --- /dev/null +++ b/tests/pos-deep-subtype/i14287.scala @@ -0,0 +1,15 @@ +enum Free[+F[_], A]: + case Return(a: A) + case Suspend(s: F[A]) + case FlatMap[F[_], A, B]( + s: Free[F, A], + f: A => Free[F, B]) extends Free[F, B] + + def flatMap[F2[x] >: F[x], B](f: A => Free[F2,B]): Free[F2,B] = + FlatMap(this, f) + + @annotation.tailrec + final def step: Free[F, A] = this match + case FlatMap(FlatMap(fx, f), g) => fx.flatMap(x => f(x).flatMap(y => g(y))).step + case FlatMap(Return(x), f) => f(x).step + case _ => this diff --git a/tests/pos/i14287.min.scala b/tests/pos/i14287.min.scala new file mode 100644 index 000000000000..8845c0bfae69 --- /dev/null +++ b/tests/pos/i14287.min.scala @@ -0,0 +1,6 @@ +enum Foo[+H[_]]: + case Bar[F[_]](f: Foo[F]) extends Foo[F] + + def test: Foo[H] = this match + case Bar(Bar(f)) => Bar(f) + case _ => this