Skip to content

Commit c529a48

Browse files
authored
Drop special treatment of function types in overloading resolution (#19654)
Fixes #19641 How we got here: Originally, overloading resolution for types that were not applied was handled like this: ```scala case defn.FunctionOf(args, resultType, _) => narrowByTypes(alts, args, resultType) case pt => val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false)) if (compat.isEmpty) /* * the case should not be moved to the enclosing match * since SAM type must be considered only if there are no candidates * For example, the second f should be chosen for the following code: * def f(x: String): Unit = ??? * def f: java.io.OutputStream = ??? * new java.io.ObjectOutputStream(f) */ pt match { case SAMType(mtp, _) => narrowByTypes(alts, mtp.paramInfos, mtp.resultType) case _ => // pick any alternatives that are not methods since these might be convertible // to the expected type, or be used as extension method arguments. val convertible = alts.filterNot(alt => normalize(alt, IgnoredProto(pt)).widenSingleton.isInstanceOf[MethodType]) if convertible.length == 1 then convertible else compat } else compat ``` Note the warning comment that the case for SAM types should not be moved out, yet we do exactly the same thing for plain function types. I believe this was simply wrong, but it was not discovered in a test. Then in #16507 we changed the `defn.FunctionOf` extractor so that aliases of function types were matched by it. This triggered test failures since we now hit the wrong case with aliases of function types. In #18286, we moved the extractor test around, but that was not enough, as #19641 shows. Instead the test for `FunctionOf` should be aligned with the test for SAM case. But it turns out that's not even necessary since the preceding `val compat = ...` handles function prototypes correctly by simulating an eta expansion. So in the end we could simply delete the problematic case.
2 parents a0ea484 + 0337efd commit c529a48

File tree

3 files changed

+40
-28
lines changed

3 files changed

+40
-28
lines changed

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

+21-28
Original file line numberDiff line numberDiff line change
@@ -2123,34 +2123,27 @@ trait Applications extends Compatibility {
21232123
else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1)
21242124

21252125
case pt =>
2126-
val compat0 = pt.dealias match
2127-
case defn.FunctionNOf(args, resType, _) =>
2128-
narrowByTypes(alts, args, resType)
2129-
case _ =>
2130-
Nil
2131-
if (compat0.isEmpty) then
2132-
val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false))
2133-
if (compat.isEmpty)
2134-
/*
2135-
* the case should not be moved to the enclosing match
2136-
* since SAM type must be considered only if there are no candidates
2137-
* For example, the second f should be chosen for the following code:
2138-
* def f(x: String): Unit = ???
2139-
* def f: java.io.OutputStream = ???
2140-
* new java.io.ObjectOutputStream(f)
2141-
*/
2142-
pt match {
2143-
case SAMType(mtp, _) =>
2144-
narrowByTypes(alts, mtp.paramInfos, mtp.resultType)
2145-
case _ =>
2146-
// pick any alternatives that are not methods since these might be convertible
2147-
// to the expected type, or be used as extension method arguments.
2148-
val convertible = alts.filterNot(alt =>
2149-
normalize(alt, IgnoredProto(pt)).widenSingleton.isInstanceOf[MethodType])
2150-
if convertible.length == 1 then convertible else compat
2151-
}
2152-
else compat
2153-
else compat0
2126+
val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false))
2127+
if compat.isEmpty then
2128+
pt match
2129+
case SAMType(mtp, _) =>
2130+
// If we have a SAM type as expected type, treat it as if the expression was eta-expanded
2131+
// Note 1: No need to do that for function types, the previous normalizedCompatible test already
2132+
// handles those.
2133+
// Note 2: This case should not be moved to the enclosing match
2134+
// since fSAM types must be considered only if there are no candidates.
2135+
// For example, the second f should be chosen for the following code:
2136+
// def f(x: String): Unit = ???
2137+
// def f: java.io.OutputStream = ???
2138+
// new java.io.ObjectOutputStream(f)
2139+
narrowByTypes(alts, mtp.paramInfos, mtp.resultType)
2140+
case _ =>
2141+
// pick any alternatives that are not methods since these might be convertible
2142+
// to the expected type, or be used as extension method arguments.
2143+
val convertible = alts.filterNot(alt =>
2144+
normalize(alt, IgnoredProto(pt)).widenSingleton.isInstanceOf[MethodType])
2145+
if convertible.length == 1 then convertible else compat
2146+
else compat
21542147
}
21552148

21562149
/** The type of alternative `alt` after instantiating its first parameter

tests/run/i19641.scala

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
type DiagnosticConsturctor = (Int) => DiagnosticSet
2+
3+
final class Diagnostic
4+
5+
final class DiagnosticSet(elements: List[Diagnostic] = List())
6+
7+
enum Result:
8+
case Success extends Result
9+
case Failure(diagnose: DiagnosticConsturctor) extends Result
10+
def diagnose(n: Int): DiagnosticSet =
11+
this match
12+
case Success => DiagnosticSet()
13+
case Failure(d) => d(n)
14+
15+
@main def Test(): Unit =
16+
val r : Result = Result.Failure((n) => DiagnosticSet(List(Diagnostic())))
17+
r.diagnose(1)

tests/run/i4364a.scala

+2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ object Test {
66
def f(x: String): Unit = ()
77

88
def foo(c: Consumer[String]) = c.accept("")
9+
def bar(c: String => Unit) = c("")
910

1011
def main(args: Array[String]) = {
1112
foo(f)
13+
bar(f)
1214
}
1315
}

0 commit comments

Comments
 (0)