Skip to content

Commit 7a1f630

Browse files
committed
Merge pull request #230 from dotty-staging/fix/and-or-subtyping
Try to avoid overconstraining when comparing and/or types
2 parents 0635cae + f2743f7 commit 7a1f630

File tree

2 files changed

+74
-2
lines changed

2 files changed

+74
-2
lines changed

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

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ class TypeComparer(initctx: Context) extends DotClass {
699699
}
700700
compareRefined
701701
case OrType(tp21, tp22) =>
702-
isSubType(tp1, tp21) || isSubType(tp1, tp22) || fourthTry(tp1, tp2)
702+
eitherIsSubType(tp1, tp21, tp1, tp22) || fourthTry(tp1, tp2)
703703
case tp2 @ MethodType(_, formals2) =>
704704
def compareMethod = tp1 match {
705705
case tp1 @ MethodType(_, formals1) =>
@@ -799,13 +799,52 @@ class TypeComparer(initctx: Context) extends DotClass {
799799
finally pendingRefinedBases = saved
800800
} || needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, isSubType(tp1, _))
801801
case AndType(tp11, tp12) =>
802-
isNewSubType(tp11, tp2) || isNewSubType(tp12, tp2)
802+
eitherIsSubType(tp11, tp2, tp12, tp2)
803803
case JavaArrayType(elem1) =>
804804
tp2 isRef ObjectClass
805805
case _ =>
806806
false
807807
}
808808

809+
/** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time
810+
* to keep the constraint as wide as possible. Specifically, if
811+
*
812+
* tp11 <:< tp12 = true with post-constraint c1
813+
* tp12 <:< tp22 = true with post-constraint c2
814+
*
815+
* and c1 subsumes c2, then c2 is kept as the post-constraint of the result,
816+
* otherwise c1 is kept.
817+
*
818+
* This method is used to approximate a solution in one of the following cases
819+
*
820+
* T1 & T2 <:< T3
821+
* T1 <:< T2 | T3
822+
*
823+
* In the first case (the second one is analogous), we have a choice whether we
824+
* want to establish the subtyping judgement using
825+
*
826+
* T1 <:< T3 or T2 <:< T3
827+
*
828+
* as a precondition. Either precondition might constrain type variables.
829+
* The purpose of this method is to pick the precondition that constrains less.
830+
* The method is not complete, because sometimes there is no best solution. Example:
831+
*
832+
* A? & B? <: T
833+
*
834+
* Here, each precondition leads to a different constraint, and neither of
835+
* the two post-constraints subsumes the other.
836+
*/
837+
def eitherIsSubType(tp11: Type, tp21: Type, tp12: Type, tp22: Type) = {
838+
val preConstraint = constraint
839+
isSubType(tp11, tp21) && {
840+
val leftConstraint = constraint
841+
constraint = preConstraint
842+
if (isSubType(tp12, tp22) && !subsumes(leftConstraint, constraint, preConstraint))
843+
constraint = leftConstraint
844+
true
845+
} || isSubType(tp12, tp22)
846+
}
847+
809848
/** Like tp1 <:< tp2, but returns false immediately if we know that
810849
* the case was covered previously during subtyping.
811850
*/
@@ -1343,6 +1382,26 @@ class TypeComparer(initctx: Context) extends DotClass {
13431382
false
13441383
}
13451384

1385+
/** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have
1386+
* for all poly params `p` defined in `c2` as `p >: L2 <: U2`:
1387+
*
1388+
* c1 defines p with bounds p >: L1 <: U1, and
1389+
* L2 <: L1, and
1390+
* U1 <: U2
1391+
*
1392+
* Both `c1` and `c2` are required to derive from constraint `pre`, possibly
1393+
* narrowing it with further bounds.
1394+
*/
1395+
def subsumes(c1: Constraint, c2: Constraint, pre: Constraint): Boolean =
1396+
if (c2 eq pre) true
1397+
else if (c1 eq pre) false
1398+
else {
1399+
val saved = constraint
1400+
try
1401+
c2.forallParams(p => c1.contains(p) && isSubType(c1.bounds(p), c2.bounds(p)))
1402+
finally constraint = saved
1403+
}
1404+
13461405
/** A new type comparer of the same type as this one, using the given context. */
13471406
def copyIn(ctx: Context) = new TypeComparer(ctx)
13481407

tests/pos/subtyping.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,16 @@ class A {
44
implicitly[this.type <:< A]
55
}
66
}
7+
object test {
8+
9+
def tag1[T](x: T): String & T = ???
10+
def tag2[T](x: T): T & String = ???
11+
12+
val x1: Int & String = tag1(0)
13+
val x2: Int & String = tag2(0)
14+
val x3: String & Int = tag1(0)
15+
val x4: String & Int = tag2(0)
16+
17+
}
18+
19+

0 commit comments

Comments
 (0)