diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index c2764ff9fa2..880f6fa4697 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -896,7 +896,17 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def warnTree = original orElse tree - def warnEtaSam() = { + def warnEtaZero(): Boolean = { + if (!settings.warnEtaZero) return true + context.warning(tree.pos, + s"""An unapplied 0-arity method was eta-expanded (due to the expected type $pt), rather than applied to `()`. + |Write ${Apply(warnTree, Nil)} to invoke method ${meth.decodedName}, or change the expected type.""".stripMargin, + WarningCategory.LintEtaZero) + true + } + + def warnEtaSam(): Boolean = { + if (!settings.warnEtaSam) return true val sam = samOf(pt) val samClazz = sam.owner // TODO: we allow a Java class as a SAM type, whereas Java only allows the @FunctionalInterface on interfaces -- align? @@ -906,6 +916,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper |even though $samClazz is not annotated with `@FunctionalInterface`; |to suppress warning, add the annotation or write out the equivalent function literal.""".stripMargin, WarningCategory.LintEtaSam) + true } // note that isFunctionProto(pt) does not work properly for Function0 @@ -915,24 +926,21 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case pt => pt }).dealiasWiden - // (4.3) condition for eta-expansion by -Xsource level + // (4.3) condition for eta-expansion by arity & -Xsource level // - // until 2.13: - // - for arity > 0: function or sam type is expected - // - for arity == 0: Function0 is expected -- SAM types do not eta-expand because it could be an accidental SAM scala/bug#9489 - // 3.0: - // - for arity > 0: unconditional - // - for arity == 0: a function-ish type of arity 0 is expected (including SAM) + // for arity == 0: + // - if Function0 is expected -- SAM types do not eta-expand because it could be an accidental SAM scala/bug#9489 + // for arity > 0: + // - 2.13: if function or sam type is expected + // - 3.0: unconditionally // // warnings: - // - 2.12: eta-expansion of zero-arg methods was deprecated (scala/bug#7187) - // - 2.13: deprecation dropped in favor of setting the scene for uniform eta-expansion in 3.0 - // (arity > 0) expected type is a SAM that is not annotated with `@FunctionalInterface` - // - 3.0: (arity == 0) expected type is a SAM that is not annotated with `@FunctionalInterface` + // - for arity == 0: eta-expansion of zero-arg methods was deprecated (scala/bug#7187) + // - for arity > 0: expected type is a SAM that is not annotated with `@FunctionalInterface` def checkCanEtaExpand(): Boolean = { def expectingSamOfArity = { val sam = samOf(ptUnderlying) - sam.exists && sam.info.params.lengthCompare(arity) == 0 + sam.exists && sam.info.params.lengthIs == arity } val expectingFunctionOfArity = { @@ -940,27 +948,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper (ptSym eq FunctionClass(arity)) || (arity > 0 && (ptSym eq FunctionClass(1))) // allowing for tupling conversion } - val doIt = - if (arity == 0) { - val doEtaZero = - expectingFunctionOfArity || sourceLevel3 && expectingSamOfArity - - if (doEtaZero && settings.warnEtaZero) { - val ptHelp = - if (expectingFunctionOfArity) pt - else s"$pt, which is SAM-equivalent to ${samToFunctionType(pt)}" - - context.warning(tree.pos, - s"""An unapplied 0-arity method was eta-expanded (due to the expected type $ptHelp), rather than applied to `()`. - |Write ${Apply(warnTree, Nil)} to invoke method ${meth.decodedName}, or change the expected type.""".stripMargin, - WarningCategory.LintEtaZero) - } - doEtaZero - } else sourceLevel3 || expectingFunctionOfArity || expectingSamOfArity - - if (doIt && !expectingFunctionOfArity && (currentRun.isScala3 || settings.warnEtaSam)) warnEtaSam() - - doIt + if (arity == 0) + expectingFunctionOfArity && warnEtaZero() + else + expectingFunctionOfArity || expectingSamOfArity && warnEtaSam() || sourceLevel3 } def matchNullaryLoosely: Boolean = { diff --git a/test/files/neg/t7187-3.check b/test/files/neg/t7187-3.check index 315687ad03b..6ea263f0f35 100644 --- a/test/files/neg/t7187-3.check +++ b/test/files/neg/t7187-3.check @@ -3,6 +3,16 @@ t7187-3.scala:13: error: type mismatch; required: () => Any val t1: () => Any = m1 // error ^ +t7187-3.scala:15: error: type mismatch; + found : Int + required: AcciSamZero + val t2AcciSam: AcciSamZero = m2 // error, nilary methods don't eta-expand to SAM types under -Xsource:3 + ^ +t7187-3.scala:16: error: type mismatch; + found : Int + required: SamZero + val t2Sam: SamZero = m2 // error, nilary methods don't eta-expand to SAM types under -Xsource:3 + ^ t7187-3.scala:27: error: Methods without a parameter list and by-name params can not be converted to functions as `m _`, write a function literal `() => m` instead val t7 = m1 _ // error: eta-expanding a nullary method ^ @@ -10,18 +20,5 @@ t7187-3.scala:14: warning: An unapplied 0-arity method was eta-expanded (due to Write m2() to invoke method m2, or change the expected type. val t2: () => Any = m2 // eta-expanded with lint warning ^ -t7187-3.scala:15: warning: An unapplied 0-arity method was eta-expanded (due to the expected type AcciSamZero, which is SAM-equivalent to () => Int), rather than applied to `()`. -Write m2() to invoke method m2, or change the expected type. - val t2AcciSam: AcciSamZero = m2 // eta-expanded with lint warning + sam warning - ^ -t7187-3.scala:15: warning: Eta-expansion performed to meet expected type AcciSamZero, which is SAM-equivalent to () => Int, -even though trait AcciSamZero is not annotated with `@FunctionalInterface`; -to suppress warning, add the annotation or write out the equivalent function literal. - val t2AcciSam: AcciSamZero = m2 // eta-expanded with lint warning + sam warning - ^ -t7187-3.scala:16: warning: An unapplied 0-arity method was eta-expanded (due to the expected type SamZero, which is SAM-equivalent to () => Int), rather than applied to `()`. -Write m2() to invoke method m2, or change the expected type. - val t2Sam: SamZero = m2 // eta-expanded with lint warning - ^ -4 warnings -2 errors +1 warning +4 errors diff --git a/test/files/neg/t7187-3.scala b/test/files/neg/t7187-3.scala index 989bb2d0c40..9216d09c579 100644 --- a/test/files/neg/t7187-3.scala +++ b/test/files/neg/t7187-3.scala @@ -12,8 +12,8 @@ class EtaExpand214 { val t1: () => Any = m1 // error val t2: () => Any = m2 // eta-expanded with lint warning - val t2AcciSam: AcciSamZero = m2 // eta-expanded with lint warning + sam warning - val t2Sam: SamZero = m2 // eta-expanded with lint warning + val t2AcciSam: AcciSamZero = m2 // error, nilary methods don't eta-expand to SAM types under -Xsource:3 + val t2Sam: SamZero = m2 // error, nilary methods don't eta-expand to SAM types under -Xsource:3 val t3: Int => Any = m3 // ok val t4 = m1 // apply diff --git a/test/files/neg/t7187-deprecation.check b/test/files/neg/t7187-deprecation.check index 6876bd8f46a..edbc44fe2b1 100644 --- a/test/files/neg/t7187-deprecation.check +++ b/test/files/neg/t7187-deprecation.check @@ -3,14 +3,19 @@ t7187-deprecation.scala:17: error: type mismatch; required: () => Any val t1: () => Any = m1 // error ^ +t7187-deprecation.scala:19: error: type mismatch; + found : Int + required: AcciSamZero + val t2AcciSam: AcciSamZero = m2 // error, nilary methods don't eta-expand to SAM types under -Xsource:3 + ^ +t7187-deprecation.scala:20: error: type mismatch; + found : Int + required: SamZero + val t2Sam: SamZero = m2 // error, nilary methods don't eta-expand to SAM types under -Xsource:3 + ^ t7187-deprecation.scala:31: error: Methods without a parameter list and by-name params can not be converted to functions as `m _`, write a function literal `() => m` instead val t7 = m1 _ // error: eta-expanding a nullary method ^ -t7187-deprecation.scala:19: warning: Eta-expansion performed to meet expected type AcciSamZero, which is SAM-equivalent to () => Int, -even though trait AcciSamZero is not annotated with `@FunctionalInterface`; -to suppress warning, add the annotation or write out the equivalent function literal. - val t2AcciSam: AcciSamZero = m2 // warn, eta-expanded to non @FunctionalInterface SAM - ^ t7187-deprecation.scala:24: warning: Auto-application to `()` is deprecated. Supply the empty argument list `()` explicitly to invoke method m2, or remove the empty argument list from its definition (Java-defined methods are exempt). In Scala 3, an unapplied method like this will be eta-expanded into a function. @@ -21,5 +26,5 @@ or remove the empty argument list from its definition (Java-defined methods are In Scala 3, an unapplied method like this will be eta-expanded into a function. a.boom // warning: apply, ()-insertion ^ -3 warnings -2 errors +2 warnings +4 errors diff --git a/test/files/neg/t7187-deprecation.scala b/test/files/neg/t7187-deprecation.scala index d7e51306456..156642ff287 100644 --- a/test/files/neg/t7187-deprecation.scala +++ b/test/files/neg/t7187-deprecation.scala @@ -16,8 +16,8 @@ class EtaExpand214 { val t1: () => Any = m1 // error val t2: () => Any = m2 // eta-expanded, only warns w/ -Xlint:eta-zero - val t2AcciSam: AcciSamZero = m2 // warn, eta-expanded to non @FunctionalInterface SAM - val t2Sam: SamZero = m2 // eta-expanded, only warns w/ -Xlint:eta-zero + val t2AcciSam: AcciSamZero = m2 // error, nilary methods don't eta-expand to SAM types under -Xsource:3 + val t2Sam: SamZero = m2 // error, nilary methods don't eta-expand to SAM types under -Xsource:3 val t3: Int => Any = m3 // ok val t4 = m1 // apply diff --git a/test/files/pos/t12006.scala b/test/files/pos/t12006.scala new file mode 100644 index 00000000000..a4abecfaf62 --- /dev/null +++ b/test/files/pos/t12006.scala @@ -0,0 +1,10 @@ +// scalac: -Xsource:3 + +// see https://github.com/scala/bug/issues/12006 +// java.io.InputStream looks like a SAM (read method), +// but u.openStream returns InputStream so don't eta-expand. +class C1(s: => java.io.InputStream) +class D1(u: java.net.URL) extends C1(u.openStream) // ok + +class C2(s: java.io.InputStream) +class D2(u: java.net.URL) extends C2(u.openStream) // ok