Skip to content

Commit 85ab75f

Browse files
authored
Avoid inf recursion in provablyDisjointClasses (#22489)
2 parents 590691b + 4cd2a97 commit 85ab75f

File tree

5 files changed

+70
-10
lines changed

5 files changed

+70
-10
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+24-8
Original file line numberDiff line numberDiff line change
@@ -2983,11 +2983,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
29832983
*
29842984
* 1. Single inheritance of classes
29852985
* 2. Final classes cannot be extended
2986-
* 3. ConstantTypes with distinct values are non intersecting
2987-
* 4. TermRefs with distinct values are non intersecting
2986+
* 3. ConstantTypes with distinct values are non-intersecting
2987+
* 4. TermRefs with distinct values are non-intersecting
29882988
* 5. There is no value of type Nothing
29892989
*
2990-
* Note on soundness: the correctness of match types relies on on the
2990+
* Note on soundness: the correctness of match types relies on the
29912991
* property that in all possible contexts, the same match type expression
29922992
* is either stuck or reduces to the same case.
29932993
*
@@ -3206,22 +3206,38 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
32063206
end match
32073207
}
32083208

3209+
/** Are `cls1` and `cls1` provablyDisjoint classes, i.e., is `cls1 ⋔ cls2` true?
3210+
*
3211+
* Note that "class" where includes traits, module classes, and (in the recursive case)
3212+
* enum value term symbols.
3213+
*/
32093214
private def provablyDisjointClasses(cls1: Symbol, cls2: Symbol)(using Context): Boolean =
32103215
def isDecomposable(cls: Symbol): Boolean =
32113216
cls.is(Sealed) && !cls.hasAnonymousChild
32123217

32133218
def decompose(cls: Symbol): List[Symbol] =
3214-
cls.children.flatMap { child =>
3219+
cls.children.map: child =>
32153220
if child.isTerm then
3216-
child.info.classSymbols // allow enum vals to be decomposed to their enum class (then filtered out) and any mixins
3217-
else child :: Nil
3218-
}.filter(child => child.exists && child != cls)
3221+
// Enum vals with mixins, such as in i21860 or i22266,
3222+
// don't have a single class symbol.
3223+
// So instead of decomposing to NoSymbol
3224+
// (which leads to erroneously considering an enum type
3225+
// as disjoint from one of the mixin, eg. i21860.scala),
3226+
// or instead of decomposing to all the class symbols of
3227+
// the enum value (which leads to other mixins being decomposed,
3228+
// and infinite recursion, eg. i22266),
3229+
// we decompose to the enum value term symbol, and handle
3230+
// that within the rest of provablyDisjointClasses.
3231+
child.info.classSymbol.orElse(child)
3232+
else child
3233+
.filter(child => child.exists && child != cls)
32193234

32203235
def eitherDerivesFromOther(cls1: Symbol, cls2: Symbol): Boolean =
32213236
cls1.derivesFrom(cls2) || cls2.derivesFrom(cls1)
32223237

32233238
def smallestNonTraitBase(cls: Symbol): Symbol =
3224-
cls.asClass.baseClasses.find(!_.is(Trait)).get
3239+
val classes = if cls.isClass then cls.asClass.baseClasses else cls.info.classSymbols
3240+
classes.find(!_.is(Trait)).get
32253241

32263242
if (eitherDerivesFromOther(cls1, cls2))
32273243
false

Diff for: docs/_spec/03-types.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,7 @@ The following properties hold about ´⌈X⌉´ (we have paper proofs for those)
10711071
The "lower-bound rule" states that ´S <: T´ if ´T = q.X´ and ´q.X´ is a non-class type designator and ´S <: L´ where ´L´ is the lower bound of the underlying type definition of ´q.X´".
10721072
That rule is known to break transitivy of subtyping in Scala already.
10731073

1074-
Second, we define the relation ´⋔´ on *classes* (including traits and hidden classes of objects) as:
1074+
Second, we define the relation ´⋔´ on *classes* (including traits, hidden classes of objects, and enum terms) as:
10751075

10761076
- ´C ⋔ D´ if `´C ∉´ baseClasses´(D)´` and ´D´ is `final`
10771077
- ´C ⋔ D´ if `´D ∉´ baseClasses´(C)´` and ´C´ is `final`

Diff for: tests/pos/i22266.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
sealed trait NonPolygon
2+
sealed trait Polygon
3+
4+
sealed trait SymmetryAspect
5+
sealed trait RotationalSymmetry extends SymmetryAspect
6+
sealed trait MaybeRotationalSymmetry extends SymmetryAspect
7+
8+
enum Shape:
9+
case Circle extends Shape with NonPolygon with RotationalSymmetry
10+
case Triangle extends Shape with Polygon with MaybeRotationalSymmetry
11+
case Square extends Shape with Polygon with RotationalSymmetry
12+
13+
object Shape:
14+
15+
def hasPolygon(
16+
rotationalSyms: Vector[Shape & RotationalSymmetry],
17+
maybeSyms: Vector[Shape & MaybeRotationalSymmetry]
18+
): Boolean =
19+
val all = rotationalSyms.concat(maybeSyms)
20+
all.exists:
21+
case _: Polygon => true
22+
case _ => false

Diff for: tests/pos/i22266.unenum.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
sealed trait NonPolygon
2+
sealed trait Polygon
3+
4+
sealed trait SymmetryAspect
5+
sealed trait RotationalSymmetry extends SymmetryAspect
6+
sealed trait MaybeRotationalSymmetry extends SymmetryAspect
7+
8+
sealed abstract class Shape
9+
10+
object Shape:
11+
case object Circle extends Shape with NonPolygon with RotationalSymmetry
12+
case object Triangle extends Shape with Polygon with MaybeRotationalSymmetry
13+
case object Square extends Shape with Polygon with RotationalSymmetry
14+
15+
def hasPolygon(
16+
rotationalSyms: Vector[Shape & RotationalSymmetry],
17+
maybeSyms: Vector[Shape & MaybeRotationalSymmetry]
18+
): Boolean =
19+
val all = rotationalSyms.concat(maybeSyms)
20+
all.exists:
21+
case _: Polygon => true
22+
case _ => false

Diff for: tests/warn/i21860.unenum.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ sealed trait Corners { self: Figure => }
33

44
sealed abstract class Shape extends Figure
55
object Shape:
6-
case object Triange extends Shape with Corners
6+
case object Triangle extends Shape with Corners
77
case object Square extends Shape with Corners
88
case object Circle extends Shape
99
case object Ellipsis extends Shape

0 commit comments

Comments
 (0)