Skip to content

Commit 990a01f

Browse files
committed
Overloading resolution: Handle SAM types more like Java and Scala 2
`resolveOverloaded` is called twice, first with implicit conversions off, then on. Before this commit, turning off implicit conversions also turned off SAM conversions, this behavior does not match Java or Scala 2 which means we could end up picking a different overload than they do (cf #11938). This commit enables SAM conversions in the first pass, _except_ for conversions to PartialFunction as that would break a lot of existing code and wouldn't match how either Java or Scala 2 pick overloads. This is an alternative to #11945 (which special-cased SAM types with an explicit `@FunctionalInterfaces` annotation) and #11990 (which special-cased SAM types that subtype a scala Function type). Special-casing PartialFunction instead seems more defensible since it's already special-cased in Scala 2 and is not considered a SAM type by Java. Fixes #11938.
1 parent ccfbf15 commit 990a01f

File tree

3 files changed

+63
-5
lines changed

3 files changed

+63
-5
lines changed

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -634,12 +634,32 @@ trait Applications extends Compatibility {
634634
// matches expected type
635635
false
636636
case argtpe =>
637-
def SAMargOK = formal match {
638-
case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined))
639-
case _ => false
640-
}
637+
val argtpe1 = argtpe.widen
638+
639+
def SAMargOK =
640+
defn.isFunctionType(argtpe1) && formal.match
641+
case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined))
642+
case _ => false
643+
641644
isCompatible(argtpe, formal)
642-
|| ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK
645+
// Only allow SAM-conversion to PartialFunction if implicit conversions
646+
// are enabled. This is necessary to avoid ambiguity between an overload
647+
// taking a PartialFunction and one taking a Function1 because
648+
// PartialFunction extends Function1 but Function1 is SAM-convertible to
649+
// PartialFunction. Concretely, given:
650+
//
651+
// def foo(a: Int => Int): Unit = println("1")
652+
// def foo(a: PartialFunction[Int, Int]): Unit = println("2")
653+
//
654+
// - `foo(x => x)` will print 1, because the PartialFunction overload
655+
// won't be seen as applicable in the first call to
656+
// `resolveOverloaded`, this behavior happens to match what Java does
657+
// since PartialFunction is not a SAM type according to Java
658+
// (`isDefined` is abstract).
659+
// - `foo { case x if x % 2 == 0 => x }` will print 2, because both
660+
// overloads are applicable, but PartialFunction is a subtype of
661+
// Function1 so it's more specific.
662+
|| (!formal.isRef(defn.PartialFunctionClass) || ctx.mode.is(Mode.ImplicitsEnabled)) && SAMargOK
643663
|| argMatch == ArgMatch.CompatibleCAP
644664
&& {
645665
val argtpe1 = argtpe.widen

tests/neg/i11938.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import java.util.function.Function
2+
3+
object Test {
4+
def foo[V](v: V): Int = 1
5+
def foo[U](fn: Function[Int, U]): Int = 2
6+
7+
def main(args: Array[String]): Unit = {
8+
val f: Int => Int = x => x
9+
foo(f) // error
10+
// Like Scala 2, we emit an error here because the Function1 argument was
11+
// deemed SAM-convertible to Function, even though it's not a lambda
12+
// expression and therefore not convertible. If we wanted to support this,
13+
// we would have to tweak TestApplication#argOK to look at the shape of
14+
// `arg` and turn off SAM conversions when it's a non-closure tree.
15+
}
16+
}

tests/run/i11938.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import java.util.function.Function
2+
3+
object Test {
4+
def foo[V](v: V): Int = 1
5+
def foo[U](fn: Function[Int, U]): Int = 2
6+
7+
def foo2(a: Int => Int): Int = 1
8+
def foo2(a: PartialFunction[Int, Int]): Int = 2
9+
10+
def main(args: Array[String]): Unit = {
11+
assert(foo((x: Int) => x) == 2)
12+
val jf: Function[Int, Int] = x => x
13+
assert(foo(jf) == 2)
14+
15+
assert(foo2(x => x) == 1)
16+
val f: Int => Int = x => x
17+
assert(foo2(f) == 1)
18+
assert(foo2({ case x if x % 2 == 0 => x }) == 2)
19+
val pf: PartialFunction[Int, Int] = { case x if x % 2 == 0 => x }
20+
assert(foo2(pf) == 2)
21+
}
22+
}

0 commit comments

Comments
 (0)