Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Under -Xsource:3, don't auto-eta-expand nilary methods to SAM types #9049

Merged
merged 1 commit into from Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 25 additions & 34 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -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?
Expand All @@ -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
Expand All @@ -915,52 +926,32 @@ 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 = {
val ptSym = ptUnderlying.typeSymbolDirect
(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 = {
Expand Down
27 changes: 12 additions & 15 deletions test/files/neg/t7187-3.check
Expand Up @@ -3,25 +3,22 @@ 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
^
t7187-3.scala:14: warning: An unapplied 0-arity method was eta-expanded (due to the expected type () => Any), rather than applied 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
4 changes: 2 additions & 2 deletions test/files/neg/t7187-3.scala
Expand Up @@ -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
Expand Down
19 changes: 12 additions & 7 deletions test/files/neg/t7187-deprecation.check
Expand Up @@ -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.
Expand All @@ -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
4 changes: 2 additions & 2 deletions test/files/neg/t7187-deprecation.scala
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions 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