Skip to content

Commit 93af7b8

Browse files
authored
Make overload pruning based on result types less aggressive (#21744)
`adaptByResult` was introduced in 2015 in 54835b6 as a last step in overloading resolution: > Take expected result type into account more often for overloading resolution > > Previously, the expected result type of a FunProto type was ignored and taken into > account only in case of ambiguities. arrayclone-new.scala shows that this is not enough. > In a case like > > val x: Array[Byte] = Array(1, 2) > > we typed 1, 2 to be Int, so overloading resulution would give the Array.apply of > type (Int, Int*)Array[Int]. But that's a dead end, since Array[Int] is not a subtype > of Array[Byte]. > > This commit proposes the following modified rule for overloading resulution: > > A method alternative is applicable if ... (as before), and if its result type > is copmpatible with the expected type of the method application. > > The commit does not pre-select alternatives based on comparing with the expected > result type. I tried that but it slowed down typechecking by a factor of at least 4. > Instead, we proceed as usual, ignoring the result type except in case of > ambiguities, but check whether the result of overloading resolution has a > compatible result type. If that's not the case, we filter all alternatives > for result type compatibility and try again. In i21410.scala this means we end up checking: F[?U] <:< Int (where ?U is unconstrained, because the check is done without looking at the argument types) The problem is that the subtype check returning false does not mean that there is no instantiation of `?U` that would make this check return true, just that type inference was not able to come up with one. This could happen for any number of reason but commonly will happen with match types since inference cannot do much with them. We cannot avoid this by taking the argument types into account, because this logic was added precisely to handle cases where the argument types mislead you because adaptation isn't taken into account. Instead, we can approximate type variables in the result type to trade false negatives for false positives which should be less problematic here. Fixes #21410.
2 parents 7d79c56 + 32ac2e6 commit 93af7b8

File tree

4 files changed

+48
-3
lines changed

4 files changed

+48
-3
lines changed

compiler/src/dotty/tools/dotc/typer/Applications.scala

+15-3
Original file line numberDiff line numberDiff line change
@@ -2118,16 +2118,27 @@ trait Applications extends Compatibility {
21182118
def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] =
21192119
record("resolveOverloaded")
21202120

2121-
/** Is `alt` a method or polytype whose result type after the first value parameter
2121+
/** Is `alt` a method or polytype whose approximated result type after the first value parameter
21222122
* section conforms to the expected type `resultType`? If `resultType`
21232123
* is a `IgnoredProto`, pick the underlying type instead.
2124+
*
2125+
* Using an approximated result type is necessary to avoid false negatives
2126+
* due to incomplete type inference such as in tests/pos/i21410.scala and tests/pos/i21410b.scala.
21242127
*/
21252128
def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(using Context): Boolean =
21262129
resultType.revealIgnored match {
21272130
case resultType: ValueType =>
21282131
altType.widen match {
2129-
case tp: PolyType => resultConforms(altSym, instantiateWithTypeVars(tp), resultType)
2130-
case tp: MethodType => constrainResult(altSym, tp.resultType, resultType)
2132+
case tp: PolyType => resultConforms(altSym, tp.resultType, resultType)
2133+
case tp: MethodType =>
2134+
val wildRes = wildApprox(tp.resultType)
2135+
2136+
class ResultApprox extends AvoidWildcardsMap:
2137+
// Avoid false negatives by approximating to a lower bound
2138+
variance = -1
2139+
2140+
val approx = ResultApprox()(wildRes)
2141+
constrainResult(altSym, approx, resultType)
21312142
case _ => true
21322143
}
21332144
case _ => true
@@ -2499,6 +2510,7 @@ trait Applications extends Compatibility {
24992510
if t.exists && alt.symbol.exists then
25002511
val (trimmed, skipped) = trimParamss(t.stripPoly, alt.symbol.rawParamss)
25012512
val mappedSym = alt.symbol.asTerm.copy(info = t)
2513+
mappedSym.annotations = alt.symbol.annotations
25022514
mappedSym.rawParamss = trimmed
25032515
val (pre, totalSkipped) = mappedAltInfo(alt.symbol) match
25042516
case Some((pre, prevSkipped)) =>

tests/pos/i21410.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class A
2+
object Test:
3+
type F[X] <: Any = X match
4+
case A => Int
5+
6+
def foo[T](x: String): T = ???
7+
def foo[U](x: U): F[U] = ???
8+
9+
val x1 = foo(A())
10+
val y: Int = x1
11+
12+
val x2: Int = foo(A()) // error

tests/pos/i21410b.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
object Test:
2+
def foo[T](x: Option[T]): T = ???
3+
def foo[T <: Tuple](x: T): Tuple.Map[T, List] = ???
4+
5+
val tup: (Int, String) = (1, "")
6+
7+
val x = foo(tup)
8+
val y: (List[Int], List[String]) = x
9+
10+
val x2: (List[Int], List[String]) = foo(tup) // error

tests/pos/i21410c.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class AppliedPIso[A, B]()
2+
case class User(age: Int)
3+
4+
object Test:
5+
extension [From, To](from: From)
6+
def focus(): AppliedPIso[From, From] = ???
7+
transparent inline def focus(inline lambda: (From => To)): Any = ???
8+
9+
10+
val u = User(1)
11+
val ap: AppliedPIso[User, User] = u.focus(_.age) // error

0 commit comments

Comments
 (0)