Skip to content

Commit 713c852

Browse files
committed
Fix scala#11234: avoid cycles in unifying F-bounded type parameters
**Note**: this is a trial as `Constraints` should be immutable. In tests/pos/11234.scala, we have the following F-bounded constraints: bounds = A0 <: Foo[LazyRef(A0)] A <: Foo[LazyRef(A)] ordering = A <: A0 As `Foo[T]` is non-variant, at some point we will add `A0 <: A`, it will trigger unification of `A0` and `A`. The unification will call `Foo[A0].&(Foo[A])`, which in turn calls `TypeComparer.glb(Foo[A0], Foo[A])`. The call `glb(Foo[A0], Foo[A])` in a fresh TypeComparer will in turn add `A0 <: A` thus trigger the unification again. We break the loop by remembering the unification pair and use the knowledge in subtyping.
1 parent 11aff06 commit 713c852

File tree

5 files changed

+38
-6
lines changed

5 files changed

+38
-6
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ abstract class Constraint extends Showable {
101101
*/
102102
def unify(p1: TypeParamRef, p2: TypeParamRef)(using Context): This
103103

104+
/** Is it currently unifying p1 and p2? */
105+
def isUnifying(p1: TypeParamRef, p2: TypeParamRef)(using Context): Boolean
106+
104107
/** A new constraint which is derived from this constraint by removing
105108
* the type parameter `param` from the domain and replacing all top-level occurrences
106109
* of the parameter elsewhere in the constraint by type `tp`, or a conservative

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,16 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
393393
order(this, param1, param2).checkNonCyclic()
394394

395395
def unify(p1: TypeParamRef, p2: TypeParamRef)(using Context): This =
396+
unifyingPair = (p2, p1)
396397
val p1Bounds = (nonParamBounds(p1) & nonParamBounds(p2)).substParam(p2, p1)
398+
unifyingPair = (NoType, NoType)
397399
updateEntry(p1, p1Bounds).replace(p2, p1)
398400

401+
private var unifyingPair: (Type, Type) = (NoType, NoType)
402+
403+
def isUnifying(p1: TypeParamRef, p2: TypeParamRef)(using Context): Boolean =
404+
unifyingPair._1 == p1 && unifyingPair._2 == p2
405+
399406
// ---------- Replacements and Removals -------------------------------------
400407

401408
/** A new constraint which is derived from this constraint by removing

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
390390
}
391391
def compareTypeParamRef =
392392
assumedTrue(tp1) ||
393+
tp2.match {
394+
case tp2: TypeParamRef =>
395+
constraint.isUnifying(tp1, tp2) ||
396+
inFrozenConstraint(constraint.isLess(tp1, tp2))
397+
case _ =>
398+
false
399+
} ||
393400
isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || {
394401
if (canConstrain(tp1) && !approx.high)
395402
addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound
@@ -540,11 +547,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
540547
// widening in `fourthTry` before adding to the constraint.
541548
if (frozenConstraint) recur(tp1, bounds(tp2).lo)
542549
else isSubTypeWhenFrozen(tp1, tp2)
543-
alwaysTrue ||
544-
frozenConstraint && (tp1 match {
545-
case tp1: TypeParamRef => constraint.isLess(tp1, tp2)
546-
case _ => false
547-
}) || {
550+
alwaysTrue || {
548551
if (canConstrain(tp2) && !approx.low)
549552
addConstraint(tp2, tp1.widenExpr, fromBelow = true)
550553
else fourthTry

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package dotc
33
package core
44

55
import Contexts._, Types._, Symbols._, Names._, Flags._
6-
import Denotations._, SymDenotations._
6+
import SymDenotations._
77
import util.Spans._
88
import util.Stats
99
import NameKinds.DepParamName

tests/pos/i11234.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
trait Foo[A <: Foo[A]]
2+
trait FooCreator[A <: Foo[A]] {
3+
def createFoo(): A
4+
}
5+
6+
trait FooWrapper {
7+
type A <: Foo[A]
8+
def foo: A
9+
}
10+
11+
object FooWrapper {
12+
def apply[A0 <: Foo[A0]](toWrap: A0): FooWrapper { type A = A0 } = new FooWrapper {
13+
type A = A0
14+
def foo: A0 = toWrap
15+
}
16+
}
17+
18+
def error(fooWrapper: FooWrapper, processor: [A <: Foo[A]] => A => A): FooWrapper =
19+
FooWrapper(processor(fooWrapper.foo))

0 commit comments

Comments
 (0)