diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index d8e9ebef4258..d45a0d0ad58a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -782,8 +782,10 @@ class Definitions { def GetterMetaAnnot(implicit ctx: Context) = GetterMetaAnnotType.symbol.asClass lazy val SetterMetaAnnotType = ctx.requiredClassRef("scala.annotation.meta.setter") def SetterMetaAnnot(implicit ctx: Context) = SetterMetaAnnotType.symbol.asClass - lazy val ShowAsInfixAnotType = ctx.requiredClassRef("scala.annotation.showAsInfix") - def ShowAsInfixAnnot(implicit ctx: Context) = ShowAsInfixAnotType.symbol.asClass + lazy val ShowAsInfixAnnotType = ctx.requiredClassRef("scala.annotation.showAsInfix") + def ShowAsInfixAnnot(implicit ctx: Context) = ShowAsInfixAnnotType.symbol.asClass + lazy val FunctionalInterfaceAnnotType = ctx.requiredClassRef("java.lang.FunctionalInterface") + def FunctionalInterfaceAnnot(implicit ctx: Context) = FunctionalInterfaceAnnotType.symbol.asClass // convenient one-parameter method types def methOfAny(tp: Type) = MethodType(List(AnyType), tp) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c74c0a1d05c8..0fb8a1ca5e06 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1430,7 +1430,21 @@ trait Applications extends Compatibility { self: Typer with Dynamic => narrowByTypes(alts, args, resultType) case pt => - alts filter (normalizedCompatible(_, pt)) + val alts1 = alts filter (normalizedCompatible(_, pt)) + if (alts1.isEmpty) { + // We only consider SAM types when there is no alternative. + // In the example below, the second overload of `bar` should + // be preferred over the first one: + // + // def foo(c: Consumer[String]) = ... + // def bar(x: String): Unit = ... + // def bar: Consumer[String] = ... + // foo(bar) + pt match { + case SAMType(mtp) => narrowByTypes(alts, mtp.paramInfos, mtp.resultType) + case _ => alts1 + } + } else alts1 } val found = narrowMostSpecific(candidates) if (found.length <= 1) found diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b367146e3ffc..08541f5747bf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2353,9 +2353,13 @@ class Typer extends Namer !tree.symbol.isConstructor && !tree.symbol.is(TransparentMethod) && !ctx.mode.is(Mode.Pattern) && - !(isSyntheticApply(tree) && !isExpandableApply)) + !(isSyntheticApply(tree) && !isExpandableApply)) { + if (!pt.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) && + !defn.isFunctionType(pt) && + SAMType.unapply(pt).isDefined) + ctx.warning(ex"${tree.symbol} is eta-expanded even though ${pt.classSymbol} does not have the @FunctionalInterface annotation.", tree.pos) simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) - else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol)) + } else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol)) readaptSimplified(tpd.Apply(tree, Nil)) else if (wtp.isImplicitMethod) err.typeMismatch(tree, pt) diff --git a/tests/neg-custom-args/fatal-warnings/i4364.scala b/tests/neg-custom-args/fatal-warnings/i4364.scala new file mode 100644 index 000000000000..5ec3f9cd169d --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i4364.scala @@ -0,0 +1,9 @@ +object Test { + def foo(c: java.util.function.Consumer[Integer]) = c.accept(0) + def f(x: Int): Unit = () + + def main(args: Array[String]) = { + foo(f) // Ok: Consumer is @FunctionalInterface + new java.io.ObjectOutputStream(f) // error: OutputStream is not @FunctionalInterface + } +} diff --git a/tests/neg/i2033.scala b/tests/neg/i2033.scala index b28a0d99e5be..c21bf80d35dc 100644 --- a/tests/neg/i2033.scala +++ b/tests/neg/i2033.scala @@ -3,7 +3,7 @@ import collection._ object Test { def check(obj: AnyRef): Unit = { val bos = new ByteArrayOutputStream() - val out = new ObjectOutputStream(println) // error + val out = new ObjectOutputStream(println) val arr = bos toByteArray () val in = (()) val deser = () diff --git a/tests/run/i4364a.scala b/tests/run/i4364a.scala new file mode 100644 index 000000000000..8bf62e545807 --- /dev/null +++ b/tests/run/i4364a.scala @@ -0,0 +1,13 @@ +import java.util.function.Consumer + +object Test { + def f(): Unit = assert(false) + def f(x: Int): Unit = assert(false) + def f(x: String): Unit = () + + def foo(c: Consumer[String]) = c.accept("") + + def main(args: Array[String]) = { + foo(f) + } +} diff --git a/tests/run/i4364b.scala b/tests/run/i4364b.scala new file mode 100644 index 000000000000..900d7d331434 --- /dev/null +++ b/tests/run/i4364b.scala @@ -0,0 +1,12 @@ +import java.util.function.Consumer + +object Test { + def f(x: String): Unit = assert(false) + def f: Consumer[String] = new Consumer { def accept(s: String) = () } + + def foo(c: Consumer[String]) = c.accept("") + + def main(args: Array[String]) = { + foo(f) + } +}