Skip to content

Commit f20b11a

Browse files
committed
Narrow problematic constraint instead of widening it.
1 parent 55832b8 commit f20b11a

File tree

1 file changed

+22
-8
lines changed

1 file changed

+22
-8
lines changed

src/dotty/tools/dotc/core/ConstraintHandling.scala

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Types._, Contexts._, Symbols._
66
import Decorators._
77
import config.Config
88
import config.Printers._
9+
import collection.mutable
910

1011
/** Methods for adding constraints and solving them.
1112
*
@@ -234,8 +235,11 @@ trait ConstraintHandling {
234235
//checkPropagated(s"adding $description")(true) // DEBUG in case following fails
235236
checkPropagated(s"added $description") {
236237
addConstraintInvocations += 1
238+
val related = new mutable.ListBuffer[PolyParam]()
237239

238-
/** Drop all constrained parameters that occur at the toplevel in bound.
240+
/** Drop all constrained parameters that occur at the toplevel in bound
241+
* and add them to `related`, which means they will be handled by
242+
* `addLess` calls.
239243
* The preconditions make sure that such parameters occur only
240244
* in one of two ways:
241245
*
@@ -251,12 +255,18 @@ trait ConstraintHandling {
251255
* Tsi = T1 | ... | Tn (n >= 0)
252256
* Some of the Ti are constrained parameters
253257
*
254-
* In each case we cannot record the relationship as an isLess, because
255-
* of the outer |/&. But we should not leave it in the constraint either
256-
* because that would risk making a parameter a subtype or supertype of a bound
257-
* where the parameter occurs again at toplevel, which leads to cycles
258-
* in the subtyping test. So we intentionally loosen the constraint in order
259-
* to keep it safe. A test case that demonstrates the problem is i864.scala.
258+
* In each case we cannot leave the parameter in place,
259+
* because that would risk making a parameter later a subtype or supertype
260+
* of a bound where the parameter occurs again at toplevel, which leads to cycles
261+
* in the subtyping test. So we intentionally narrow the constraint by
262+
* recording an isLess relationship instead (even though this is not implied
263+
* by the bound).
264+
*
265+
* Narrowing a constraint is better than widening it, because narrowing leads
266+
* to incompleteness (which we face anyway, see for instance eitherIsSubType)
267+
* but widening leads to unsoundness.
268+
*
269+
* A test case that demonstrates the problem is i864.scala.
260270
* Turn Config.checkConstraintsSeparated on to get an accurate diagnostic
261271
* of the cycle when it is created.
262272
*/
@@ -266,15 +276,19 @@ trait ConstraintHandling {
266276
case bound: TypeVar if constraint contains bound.origin =>
267277
prune(bound.underlying)
268278
case bound: PolyParam if constraint contains bound =>
279+
related += bound
269280
if (fromBelow) defn.NothingType else defn.AnyType
270281
case _ =>
271282
bound
272283
}
284+
def addParamBound(bound: PolyParam) =
285+
if (fromBelow) addLess(bound, param) else addLess(param, bound)
273286
try bound match {
274287
case bound: PolyParam if constraint contains bound =>
275-
if (fromBelow) addLess(bound, param) else addLess(param, bound)
288+
addParamBound(bound)
276289
case _ =>
277290
val pbound = prune(bound)
291+
related.foreach(addParamBound)
278292
if (fromBelow) addLowerBound(param, pbound) else addUpperBound(param, pbound)
279293
}
280294
finally addConstraintInvocations -= 1

0 commit comments

Comments
 (0)