Skip to content

Commit 3c3b3db

Browse files
committed
Split TypeComparer#either and document both cases
1 parent 12b8999 commit 3c3b3db

File tree

1 file changed

+80
-15
lines changed

1 file changed

+80
-15
lines changed

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

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,17 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
12191219
fix(tp)
12201220
}
12211221

1222+
/** Returns true iff the result of evaluating either `op1` or `op2` is true and approximates resulting constraints.
1223+
*
1224+
* If we're _not_ in GADTFlexible mode, we try to keep the smaller of the two constraints.
1225+
* If we're _in_ GADTFlexible mode, we keep the smaller constraint if any, or no constraint at all.
1226+
*
1227+
* @see [[sufficientEither]] for the normal case
1228+
* @see [[necessaryEither]] for the GADTFlexible case
1229+
*/
1230+
private def either(op1: => Boolean, op2: => Boolean): Boolean =
1231+
if (ctx.mode.is(Mode.GADTflexible)) necessaryEither(op1, op2) else sufficientEither(op1, op2)
1232+
12221233
/** Returns true iff the result of evaluating either `op1` or `op2` is true,
12231234
* trying at the same time to keep the constraint as wide as possible.
12241235
* E.g, if
@@ -1247,13 +1258,79 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
12471258
*
12481259
* Here, each precondition leads to a different constraint, and neither of
12491260
* the two post-constraints subsumes the other.
1261+
*
1262+
* Note that to be complete when it comes to typechecking, we would instead need to backtrack
1263+
* and attempt to typecheck with the other constraint.
1264+
*
1265+
* Method name comes from the notion that we are keeping a constraint which is sufficient to satisfy
1266+
* one of subtyping relationships.
12501267
*/
1251-
private def either(op1: => Boolean, op2: => Boolean): Boolean = {
1268+
private def sufficientEither(op1: => Boolean, op2: => Boolean): Boolean = {
1269+
val preConstraint = constraint
1270+
op1 && {
1271+
val leftConstraint = constraint
1272+
constraint = preConstraint
1273+
if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) {
1274+
if (constr != noPrinter && !subsumes(constraint, leftConstraint, preConstraint))
1275+
constr.println(i"CUT - prefer $leftConstraint over $constraint")
1276+
constraint = leftConstraint
1277+
}
1278+
true
1279+
} || op2
1280+
}
1281+
1282+
/** Returns true iff the result of evaluating either `op1` or `op2` is true, keeping the smaller constraint if any.
1283+
* E.g., if
1284+
*
1285+
* tp11 <:< tp12 = true with constraint c1 and GADT constraint g1
1286+
* tp12 <:< tp22 = true with constraint c2 and GADT constraint g2
1287+
*
1288+
* We keep:
1289+
* - (c1, g1) if c2 subsumes c1 and g2 subsumes g1
1290+
* - (c2, g2) if c1 subsumes c2 and g1 subsumes g2
1291+
* - neither constraint pair otherwise.
1292+
*
1293+
* Like [[sufficientEither]], this method is used to approximate a solution in one of the following cases:
1294+
*
1295+
* T1 & T2 <:< T3
1296+
* T1 <:< T2 | T3
1297+
*
1298+
* Unlike [[sufficientEither]], this method is used in GADTFlexible mode, when we are attempting to infer GADT
1299+
* constraints that necessarily follow from the subtyping relationship. For instance, if we have
1300+
*
1301+
* enum Expr[T] {
1302+
* case IntExpr(i: Int) extends Expr[Int]
1303+
* case StrExpr(s: String) extends Expr[String]
1304+
* }
1305+
*
1306+
* and `A` is an abstract type and we know that
1307+
*
1308+
* Expr[A] <: IntExpr | StrExpr
1309+
*
1310+
* (the case with &-type is analogous) then this may follow either from
1311+
*
1312+
* Expr[A] <: IntExpr or Expr[A] <: StrExpr
1313+
*
1314+
* Since we don't know which branch is true, we need to give up and not keep either constraint. OTOH, if one
1315+
* constraint pair is subsumed by the other, we know that it is necessary for both cases and therefore we can
1316+
* keep it.
1317+
*
1318+
* Like [[sufficientEither]], this method is not complete because sometimes, the necessary constraint
1319+
* is neither of the pairs. For instance, if
1320+
*
1321+
* g1 = { A = Int, B = String }
1322+
* g2 = { A = Int, B = Int }
1323+
*
1324+
* then the necessary constraint is { A = Int }, but correctly inferring that is, as far as we know, too expensive.
1325+
*
1326+
* Method name comes from the notion that we are keeping the constraint which is necessary to satisfy both
1327+
* subtyping relationships.
1328+
*/
1329+
private def necessaryEither(op1: => Boolean, op2: => Boolean): Boolean = {
12521330
val preConstraint = constraint
12531331

1254-
if (ctx.mode.is(Mode.GADTflexible)) {
12551332
val preGadt = ctx.gadt.fresh
1256-
// if GADTflexible mode is on, we always have a ProperGadtConstraint
1333+
// if GADTflexible mode is on, we expect to always have a ProperGadtConstraint
12571334
val pre = preGadt.asInstanceOf[ProperGadtConstraint]
12581335
if (op1) {
12591336
val leftConstraint = constraint
@@ -1284,18 +1361,6 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
12841361
true
12851362
}
12861363
} else op2
1287-
} else {
1288-
op1 && {
1289-
val leftConstraint = constraint
1290-
constraint = preConstraint
1291-
if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) {
1292-
if (constr != noPrinter && !subsumes(constraint, leftConstraint, preConstraint))
1293-
constr.println(i"CUT - prefer $leftConstraint over $constraint")
1294-
constraint = leftConstraint
1295-
}
1296-
true
1297-
} || op2
1298-
}
12991364
}
13001365

13011366
/** Does type `tp1` have a member with name `name` whose normalized type is a subtype of

0 commit comments

Comments
 (0)