Skip to content

Commit

Permalink
Fix SAM conversion involving ()-insertion and overloading
Browse files Browse the repository at this point in the history
Without overloading, a function literal `() => 3` whose expected type is
a Unit-returning SAM gets typed as `() => { 3; () }` as expected, but
with overloading we first type the function literal without an expected
type and then compare it against the formal parameter type.

This commit makes this check succeed by replacing a result type of
`Unit` by `WildcardType` so we end up comparing
`() => Int <:< () => ?` instead of `() => Int <:< () => Unit`.

Fixes scala#13549.
  • Loading branch information
smarter committed Sep 22, 2021
1 parent 2784596 commit 0231d45
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 5 deletions.
12 changes: 9 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1779,15 +1779,21 @@ object Types {
* @pre this is a method type without parameter dependencies.
* @param dropLast The number of trailing parameters that should be dropped
* when forming the function type.
* @param unitToWildcard If true and the result type is Unit, use a wildcard
* as the function result type instead. Useful when
* checking if a function literal could be converted into
* this function type.
*/
def toFunctionType(isJava: Boolean, dropLast: Int = 0)(using Context): Type = this match {
def toFunctionType(isJava: Boolean, dropLast: Int = 0, unitToWildcard: Boolean = false)(using Context): Type = this match {
case mt: MethodType if !mt.isParamDependent =>
val formals1 = if (dropLast == 0) mt.paramInfos else mt.paramInfos dropRight dropLast
val isContextual = mt.isContextualMethod && !ctx.erasedTypes
val isErased = mt.isErasedMethod && !ctx.erasedTypes
val result1 = mt.nonDependentResultApprox match {
case res: MethodType => res.toFunctionType(isJava)
case res => res
case res: MethodType => res.toFunctionType(isJava, unitToWildcard = unitToWildcard)
case res =>
if unitToWildcard && res.isRef(defn.UnitClass) then WildcardType
else res
}
val funType = defn.FunctionOf(
formals1 mapConserve (_.translateFromRepeated(toArray = isJava)),
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,8 @@ trait Applications extends Compatibility {

def SAMargOK =
defn.isFunctionType(argtpe1) && formal.match
case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined))
case SAMType(sam) => argtpe <:< sam.toFunctionType(
isJava = formal.classSymbol.is(JavaDefined), unitToWildcard = true)
case _ => false

isCompatible(argtpe, formal)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3728,7 +3728,7 @@ class Typer extends Namer
if defn.isFunctionType(wtp) && !defn.isFunctionType(pt) =>
pt match {
case SAMType(sam)
if wtp <:< sam.toFunctionType(isJava = pt.classSymbol.is(JavaDefined)) =>
if wtp <:< sam.toFunctionType(isJava = pt.classSymbol.is(JavaDefined), unitToWildcard = true) =>
// was ... && isFullyDefined(pt, ForceDegree.flipBottom)
// but this prevents case blocks from implementing polymorphic partial functions,
// since we do not know the result parameter a priori. Have to wait until the
Expand Down
11 changes: 11 additions & 0 deletions tests/pos/i13549.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@FunctionalInterface
trait Executable {
def execute(): Unit
}

object Test {
def assertThrows(executable: Executable, message: String): Unit = ???
def assertThrows(executable: Executable, foo: Int): Unit = ???

assertThrows(() => 3, "This is a message")
}

0 comments on commit 0231d45

Please sign in to comment.