Skip to content

Commit

Permalink
Make overload pruning based on result types less aggressive
Browse files Browse the repository at this point in the history
`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 scala#21410.
  • Loading branch information
smarter committed Oct 9, 2024
1 parent 3408ed7 commit d4de105
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 3 deletions.
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2076,16 +2076,20 @@ trait Applications extends Compatibility {
def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] =
record("resolveOverloaded")

/** Is `alt` a method or polytype whose result type after the first value parameter
/** Is `alt` a method or polytype whose approximated result type after the first value parameter
* section conforms to the expected type `resultType`? If `resultType`
* is a `IgnoredProto`, pick the underlying type instead.
*
* Using approximated result types is necessary to avoid false negatives
* due to incomplete type inference such as in tests/pos/i21410.scala.
*/
def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(using Context): Boolean =
resultType.revealIgnored match {
case resultType: ValueType =>
altType.widen match {
case tp: PolyType => resultConforms(altSym, instantiateWithTypeVars(tp), resultType)
case tp: MethodType => constrainResult(altSym, tp.resultType, resultType)
case tp: PolyType => resultConforms(altSym, tp.resultType, resultType)
case tp: MethodType =>
constrainResult(altSym, wildApprox(tp.resultType), resultType)
case _ => true
}
case _ => true
Expand Down
12 changes: 12 additions & 0 deletions tests/pos/i21410.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class A
object Test:
type F[X] <: Any = X match
case A => Int

def foo[T](x: String): T = ???
def foo[U](x: U): F[U] = ???

val x1 = foo(A())
val y: Int = x1

val x2: Int = foo(A()) // error

0 comments on commit d4de105

Please sign in to comment.