Skip to content

Commit 5ca1b01

Browse files
Freeze constraints while calculating GADT full bounds (#18222)
While studying a bug, I discovered that the code I'd written to avoid bidirectionalily in GADT fullBounds was inadvertently recording GADT constraints, assisted by the fact that, when running without -Yno-deep-subtypes, TypeComparer's escape hatch for deep recursions avoids it crashing. For instance a GADT symbol was compared >: Nothing and thus recorded as having Nothing as a lower bound. So I added freezing, and while I was at it I updated the docs I hadn't changed last time, and I made the subtyping check in the only direction necessary.
2 parents fd95c55 + 072d38e commit 5ca1b01

File tree

1 file changed

+15
-12
lines changed

1 file changed

+15
-12
lines changed

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,28 +73,31 @@ class GadtConstraint private (
7373

7474
def fullLowerBound(param: TypeParamRef)(using Context): Type =
7575
val self = externalize(param)
76-
constraint.minLower(param).foldLeft(nonParamBounds(param).lo) { (acc, p) =>
77-
externalize(p) match
76+
constraint.minLower(param).foldLeft(nonParamBounds(param).lo) { (acc, loParam) =>
77+
externalize(loParam) match
7878
// drop any lower param that is a GADT symbol
79-
// and is upper-bounded by a non-Any super-type of the original parameter
80-
// e.g. in pos/i14287.min
79+
// and is upper-bounded by the original parameter
80+
// e.g. in pos/i14287.min:
81+
// case Foo.Bar[B$1](Foo.Bar[B$2](x)) => Foo.Bar(x)
82+
// after pattern type constraining:
8183
// B$1 had info <: X and fullBounds >: B$2 <: X, and
8284
// B$2 had info <: B$1 and fullBounds <: B$1
83-
// We can use the info of B$2 to drop the lower-bound of B$1
85+
// If we keep these fullBounds, it would be a bidirectional definition.
86+
// So instead we can use the info of B$2 to drop the lower-bound of B$1
8487
// and return non-bidirectional bounds B$1 <: X and B$2 <: B$1.
85-
case tp: TypeRef if tp.symbol.isPatternBound && self =:= tp.info.hiBound => acc
86-
case tp => acc | tp
88+
case lo: TypeRef if lo.symbol.isPatternBound && lo.info.hiBound.frozen_<:<(self) => acc
89+
case lo => acc | lo
8790
}
8891

8992
def fullUpperBound(param: TypeParamRef)(using Context): Type =
9093
val self = externalize(param)
91-
constraint.minUpper(param).foldLeft(nonParamBounds(param).hi) { (acc, u) =>
92-
externalize(u) match
93-
case tp: TypeRef if tp.symbol.isPatternBound && self =:= tp.info.loBound => acc // like fullLowerBound
94-
case tp =>
94+
constraint.minUpper(param).foldLeft(nonParamBounds(param).hi) { (acc, hiParam) =>
95+
externalize(hiParam) match
96+
case hi: TypeRef if hi.symbol.isPatternBound && self.frozen_<:<(hi.info.loBound) => acc // like fullLowerBound
97+
case hi =>
9598
// Any as the upper bound means "no bound", but if F is higher-kinded,
9699
// Any & F = F[_]; this is wrong for us so we need to short-circuit
97-
if acc.isAny then tp else acc & tp
100+
if acc.isAny then hi else acc & hi
98101
}
99102

100103
def externalize(tp: Type, theMap: TypeMap | Null = null)(using Context): Type = tp match

0 commit comments

Comments
 (0)