Skip to content

Conversation

@retronym
Copy link
Member

Otherwise, we can end up with a subtle source incompatibility with
the pre-SAM regime. Arguably we should phase out eta expansion to
Function0 as well, but I'll leave that for another day.

Review by @adriaanm

@scala-jenkins scala-jenkins added this to the 2.12.0-M4 milestone Oct 27, 2015
@retronym
Copy link
Member Author

I've also rebased #4767 (enable SAM by default) on top of this PR, and have triggered a build.

Otherwise, we can end up with a subtle source incompatibility with
the pre-SAM regime. Arguably we should phase out eta expansion to
Function0 as well, but I'll leave that for another day.
@retronym
Copy link
Member Author

That build showed a flaw in my first attempt, I've just pushed an improved fix. The enable-SAM-by-default build is running again on top of 639e52a.

@ilinum
Copy link

ilinum commented Oct 27, 2015

Do I understand correctly that the following code won't compile anymore?

object T {
  def bar(r: Runnable): Unit = r.run()
  def f() = () => println()
  bar(f)
}

Because in bar(f) f is a parameterless method type

I may be misunderstanding it, not very familiar with scalac sources :)

@retronym
Copy link
Member Author

That's right:

scala> object T {
     |   def bar(r: Runnable): Unit = r.run()
     |   def f() = () => println()
     |   bar(f)
     | }
<console>:14: error: type mismatch;
 found   : () => Unit
 required: Runnable
         bar(f)
             ^

@ilinum
Copy link

ilinum commented Oct 27, 2015

If only I knew this several months earlier. Spent a lot of time trying to support eta expansion of SAMs in Intellij IDEA. That makes it simpler. If you are interested, here are tests on SAM for IntelliJ IDEA.

Assuming this PR passes the tests, will this fix be backported to Scala 2.11?

@retronym
Copy link
Member Author

Thanks for that work, really great to have that done ahead of the 2.12 release! We hang out over at https://gitter.im/scala/contributors, so feel free to drop by with those sort of questions.

@retronym
Copy link
Member Author

Not sure about 2.11 yet, let's see how this proposal fares in 2.12.

@ilinum
Copy link

ilinum commented Oct 27, 2015

I will, thanks!

Hopefully this proposal makes it into both 2.11 and 2.12!

@soc
Copy link
Contributor

soc commented Oct 27, 2015

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f5f9eb54b92, pid=23094, tid=140048373556992
#
# JRE version: OpenJDK Runtime Environment (8.0_45-b14) (build 1.8.0_45-internal-b14)
# Java VM: OpenJDK 64-Bit Server VM (25.45-b02 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  [libc.so.6+0x80b92]
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /home/jenkins/workspace/scala-2.12.x-validate-test/hs_err_pid23094.log
Compiled method (c2)    7307 4186       4       scala.reflect.internal.Symbols$Symbol::newTermSymbol (84 bytes)
 total in heap  [0x00007f5f906c2950,0x00007f5f906c47c8] = 7800
 relocation     [0x00007f5f906c2a78,0x00007f5f906c2bb0] = 312
 main code      [0x00007f5f906c2bc0,0x00007f5f906c3820] = 3168
 stub code      [0x00007f5f906c3820,0x00007f5f906c3890] = 112
 oops           [0x00007f5f906c3890,0x00007f5f906c38a8] = 24
 metadata       [0x00007f5f906c38a8,0x00007f5f906c3978] = 208
 scopes data    [0x00007f5f906c3978,0x00007f5f906c4440] = 2760
 scopes pcs     [0x00007f5f906c4440,0x00007f5f906c4650] = 528
 dependencies   [0x00007f5f906c4650,0x00007f5f906c4678] = 40
 handler table  [0x00007f5f906c4678,0x00007f5f906c47b0] = 312
 nul chk table  [0x00007f5f906c47b0,0x00007f5f906c47c8] = 24
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
#

@retronym
Copy link
Member Author

I've modified that job to archive JVM crash logs as an build artifact to get a bit more information it if happens again.

/rebuild

@lrytz
Copy link
Member

lrytz commented Oct 27, 2015

/rebuild - scaboooou only reads the headlines.

@lrytz
Copy link
Member

lrytz commented Nov 16, 2015

The Function0 example you provide in the ticket remains the same?

object Test {
  def foo(): () => String = () => ""
  val f: () => Any = foo

  def main(args: Array[String]): Unit = {
    println(f()) // <function0>
  }
}

Should we emit a deprecation in this case? It'd be good to add the example as a test case. LGTM otherwise, but I'll let @adriaanm take a look.

@retronym
Copy link
Member Author

I've added the test case to show that the status quo is preserved. If we have a consensus to deprecate let's do so under the banner of a new PR.

My vote is +1 unless we learn something from running the community build with the deprecation warning (ramped up to an error so we see where if/where it is used.)

@retronym
Copy link
Member Author

I'm taking another look at this failure to compile the library with the locker in the followup change, which enables SAM by default:

quick.lib:
    [mkdir] Created dir: /home/jenkins/workspace/scala-2.12.x-validate-publish-core/build/quick/classes/library
[quick.library] Compiling 584 files to /home/jenkins/workspace/scala-2.12.x-validate-publish-core/build/quick/classes/library
[quick.library] OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=512M; support was removed in 8.0
[quick.library] OpenJDK 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
[quick.library] /home/jenkins/workspace/scala-2.12.x-validate-publish-core/src/library/scala/collection/parallel/ParIterableLike.scala:737: error: recursive method bf2seq needs result type
[quick.library]   def scanLeft[S, That](z: S)(op: (S, T) => S)(implicit bf: CanBuildFrom[Repr, S, That]) = setTaskSupport(seq.scanLeft(z)(op)(bf2seq(bf)), tasksupport)
[quick.library]                                                                                                                               ^

@retronym
Copy link
Member Author

Looks like that is a general problem with the SAM implementation. In order to support let SAMs participate in overload resolution, isCompatible was changed to call isCompatibleSAM. This computes the set of members of the corresponding formal parameter type; if there is exactly one then and some other conditions hold, SAM conversion is tried.

This seems pretty heaviweight from a performance perspective, and as the build failure shows, it also treads into cycles.

We might be able to easily make this leaner by instead looking at the decls of all the base classes in the hope that we'll find more than one abstract method that would disqualify this as a SAM. Generalizing this, we could expose a variant of findMembers that returns an iterator of members.

@adriaanm
Copy link
Contributor

LGTM

@lrytz
Copy link
Member

lrytz commented Nov 18, 2015

cool! @adriaanm wdyt about deprecating the Function0 case (#4822 (comment))?

lrytz added a commit that referenced this pull request Nov 18, 2015
SI-9178 Don't eta expand to an Function0-like SAM expected type
@lrytz lrytz merged commit fe33616 into scala:2.12.x Nov 18, 2015
@adriaanm
Copy link
Contributor

Agreed with deprecating. @retronym, could you follow up with a PR for that?

@ilinum
Copy link

ilinum commented Nov 26, 2015

Also, SAM needs to be enabled by default?

@retronym
Copy link
Member Author

@ilinum I'm trying that in #4871

@adamvoss
Copy link

Is this why, under 2.12, the following code compiles:

package object mypackage {
  def render() = {}

  val handler: Runnable = () => render
}

yet this code does not?

package object mypackage {
  def render() = {}

  val handler: Runnable = render _
}
[error] /home/vossad01/dev/untitled/src/main/scala/mypackage/Main.scala:10: type mismatch;
[error]  found   : Unit
[error]  required: Runnable
[error]   val handler: Runnable = render _
[error]                           ^

Under 2.11, with the addition of an implicit conversion either form was valid.

Originally noticed in the context of Scala.js (scala-js/scala-js#2656)

@adriaanm
Copy link
Contributor

Yes, you have to write the function literal in full now in the zero-arg
case. Also, in 2.12, implicit conversion will not kick in when the expected
type is a SAM and the expression is a function literal. This conversion is
handled by SAM conversion. The release notes have more info and a pointer
to the spec.
On Sat, Nov 19, 2016 at 07:47 Adam Voss [email protected] wrote:

Is this why, under 2.12, the following code compiles:

package object mypackage {
def render() = {}

val handler: Runnable = () => render
}

yet this code does not?

package object mypackage {
def render() = {}

val handler: Runnable = render _
}

[error] /home/vossad01/dev/untitled/src/main/scala/mypackage/Main.scala:10: type mismatch;
[error] found : Unit
[error] required: Runnable
[error] val handler: Runnable = render _
[error] ^

Under 2.11, with the addition of an implicit conversion either form was
valid.

Originally noticed in the context of Scala.js (scala-js/scala-js#2656
scala-js/scala-js#2656)


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4822 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAFjy9sntoVBjrsVHtjEjRGsERva4Igzks5q_xoEgaJpZM4GWMra
.

@adamvoss
Copy link

adamvoss commented Dec 5, 2016

@adriaanm I have finally muddled my way through the spec. I will admit it is a little over my head, but this stackoverflow post on method type vs function type helped a lot. From what I read, it seems like the case I described above would be a bug.

package object mypackage {
  def render() = {}
  val handler: Function0[Any] = render _
  val handler2 = render _
  val handler3: Runnable = handler2
  val handler4: Runnable = render _


  implicit def runnable(f: () => Unit): Runnable =
    new Runnable() { def run() = f() }
}

of contention is how render _ it interpreted.

In the above code, only the line of handler4 fails to compile. Based on both the -Xprint:typer output and the error message, the compiler seems to have chosen to do function application on that line rather than to treat it as a method value

The spec says:

Expression forms are discussed subsequently in decreasing order of precedence.

Since function application is listed first, it would seem that if function application was appropriate for handler4 it should have been used for handler and handler2.

What I expect is render _ is treated as a method value. Looking at the spec:

Method Values
The expression e _ is well-formed if e is of method type or if e is a call-by-name parameter. If e is a method with parameters, e _ represents e converted to a function type by eta-expansion. [...]

Based on the this issue, I suppose it could be argued that because the method value explicitly triggers eta-expansion and Runnable is Function0-like it is covered by issue. However, that seems inconsistent with how #5327 treats method values as distinct from eta-expansion nor did I see how that is specified in the spec.

Continuing under the assumption that render _ is a method value, its function type would be () => Unit. Because it is a method value, it is not a anonymous function (lambda) per 6.23 and the SAM-conversion via translation (6.23.1) would not be applicable. () => Unit is not assignable to Runnable, but there are still Implicit Conversions to consider.

SAM conversion
An expression (p1, ..., pN) => body of function type (T1, ..., TN) => T is sam-convertible to the expected type S [...]

render _ has a function type suitable for the SAM conversion; however, render _ does not match the form (p1, ..., pN) => body

It would then advance onto View Application, for which there is a suitable implicit defined.

My interpretation that the implicit SAM conversion would not apply would seem consistent with the release note,

Note that SAM conversion only applies to lambda expressions, not to arbitrary expressions with Scala FunctionN types: [...]

@adriaanm
Copy link
Contributor

adriaanm commented Dec 6, 2016

Thank you for digging in here -- this is tricky stuff. I think you're right that something's iffy here. Must indeed be something to do with it being 0-ary. Both these examples compile:

class Ok1 {
  trait Sam1 { def apply(x: Any): Unit }
  def render(x: Any) = {}
  val handler: Sam1 = render _
}

class Ok2 {
  implicit def conv(x: Function0[Unit]): Runnable = () => x()
  def render() = {}
  val handler: Runnable = (render _): Function0[Unit] // suppress sam conversion by typing the method value `render _` as a function-type, which will then expand to `() => render()` via eta-expansion, which will ultimately be converted via the implicit view `conv` to the expected type `Runnable`
}

Could you file a ticket over at scala/scala-dev? I think the right behavior is that the nullary case should behave just like the unary case in Ok1. (I.e., sam conversion applies)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants