Skip to content

Commit bda6763

Browse files
committed
Delay hard argument comparisons
When comparing arguments of two applied types, perform hard comparisons after easy ones. A comparison if hard if it entails a subtype test A <: B where A is an AndType or B is an OrType. Such comparisons need to perform an either, which might lose solutions. Fixes #19999
1 parent 3694d95 commit bda6763

File tree

2 files changed

+51
-5
lines changed

2 files changed

+51
-5
lines changed

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

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,19 +1683,43 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
16831683
* @param tparams2 The type parameters of the type constructor applied to `args2`
16841684
*/
16851685
def isSubArgs(args1: List[Type], args2: List[Type], tp1: Type, tparams2: List[ParamInfo]): Boolean = {
1686+
16861687
/** The bounds of parameter `tparam`, where all references to type paramneters
16871688
* are replaced by corresponding arguments (or their approximations in the case of
16881689
* wildcard arguments).
16891690
*/
16901691
def paramBounds(tparam: Symbol): TypeBounds =
16911692
tparam.info.substApprox(tparams2.asInstanceOf[List[Symbol]], args2).bounds
16921693

1693-
def recurArgs(args1: List[Type], args2: List[Type], tparams2: List[ParamInfo]): Boolean =
1694-
if (args1.isEmpty) args2.isEmpty
1694+
/** Test all arguments. Hard argument tests (according to isHard) are deferred in
1695+
* the first run and picked up in the second.
1696+
*/
1697+
def recurArgs(args1: List[Type], args2: List[Type], tparams2: List[ParamInfo],
1698+
canDefer: Boolean,
1699+
deferred1: List[Type], deferred2: List[Type], deferredTparams2: List[ParamInfo]): Boolean =
1700+
if args1.isEmpty then
1701+
args2.isEmpty
1702+
&& (deferred1.isEmpty
1703+
|| recurArgs(
1704+
deferred1.reverse, deferred2.reverse, deferredTparams2.reverse,
1705+
canDefer = false, Nil, Nil, Nil))
16951706
else args2.nonEmpty && tparams2.nonEmpty && {
16961707
val tparam = tparams2.head
16971708
val v = tparam.paramVarianceSign
16981709

1710+
/** An argument test is hard if it implies a comparison A <: B where
1711+
* A is an AndType or B is an OrType. In these cases we need to run an
1712+
* either, which can lose solutions if there are type variables involved.
1713+
* So we defer such tests to run last, on the chance that some other argument
1714+
* comparison will instantiate or constrain type variables first.
1715+
*/
1716+
def isHard(arg1: Type, arg2: Type): Boolean =
1717+
val arg1d = arg1.stripped
1718+
val arg2d = arg2.stripped
1719+
(v >= 0) && (arg1d.isInstanceOf[AndType] || arg2d.isInstanceOf[OrType])
1720+
||
1721+
(v <= 0) && (arg1d.isInstanceOf[OrType] || arg2d.isInstanceOf[AndType])
1722+
16991723
/** Try a capture conversion:
17001724
* If the original left-hand type `leftRoot` is a path `p.type`,
17011725
* and the current widened left type is an application with wildcard arguments
@@ -1781,10 +1805,26 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
17811805
else if v > 0 then isSubType(arg1, arg2)
17821806
else isSameType(arg2, arg1)
17831807

1784-
isSubArg(args1.head, args2.head)
1785-
} && recurArgs(args1.tail, args2.tail, tparams2.tail)
1808+
val arg1 = args1.head
1809+
val arg2 = args2.head
1810+
val rest1 = args1.tail
1811+
if !canDefer
1812+
|| rest1.isEmpty && deferred1.isEmpty
1813+
// skip the hardness test if this is the last argument and no previous arguments were hard
1814+
|| !isHard(arg1, arg2)
1815+
then
1816+
isSubArg(arg1, arg2)
1817+
&& recurArgs(
1818+
rest1, args2.tail, tparams2.tail, canDefer,
1819+
deferred1, deferred2, deferredTparams2)
1820+
else
1821+
recurArgs(
1822+
rest1, args2.tail, tparams2.tail, canDefer,
1823+
arg1 :: deferred1, arg2 :: deferred2, tparams2.head :: deferredTparams2)
1824+
}
1825+
1826+
recurArgs(args1, args2, tparams2, canDefer = true, Nil, Nil, Nil)
17861827

1787-
recurArgs(args1, args2, tparams2)
17881828
}
17891829

17901830
/** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where

tests/pos/i19999.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class InIntersection[I, A]
2+
3+
def derived[A, R0]: InIntersection[A & R0, A] = new InIntersection[A & R0, A]
4+
5+
var x: InIntersection[Int & String, Int] = derived
6+

0 commit comments

Comments
 (0)