Skip to content

Commit

Permalink
Merge pull request #9049 from dwijnand/no-eta-sam-3
Browse files Browse the repository at this point in the history
  • Loading branch information
lrytz committed Jun 11, 2020
2 parents 1844d75 + 71fe2b8 commit 0ff65a0
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 60 deletions.
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

0 comments on commit 0ff65a0

Please sign in to comment.