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

Parenless overloaded methods are used in ambiguous cases #9395

Closed
scabug opened this issue Jul 13, 2015 · 6 comments
Closed

Parenless overloaded methods are used in ambiguous cases #9395

scabug opened this issue Jul 13, 2015 · 6 comments

Comments

@scabug
Copy link

scabug commented Jul 13, 2015

This code:

class MyClass {
    def something(in: String): String = {
        in + "_z"
    }

    def something: (String) => String = {
        case _ => "Fixed"
    }
}

val my = new MyClass()

println(List("x", "y").map(my.something))

Prints List(Fixed, Fixed).

The type checker is calling the no-argument variant of my.something(), and then silently taking the return type and passing it to map(). This ignores the other matching one-argument version of my.something.

The below line (arguably correctly) produces a compile error about an "ambiguous reference to overloaded definition":

val fun: String => String = my.something(_)

Adding parentheses to the no-argument definition of the method removes the issue. Changing the return type of the no-argument method to be anything other than String => String removes the issue.

I think this is incorrect behavior; where there is a conflict between the result of an implicit conversion (the my.something() return value) and an unconverted correct type, either the unconverted type should win or a compile error should be thrown telling the user about ambiguity. In other words, the above code should either output List(x_z, y_z) or fail to compile. Currently the implicitly converted version is silently taking precedence.

@scabug
Copy link
Author

scabug commented Jul 13, 2015

Imported From: https://issues.scala-lang.org/browse/SI-9395?orig=1
Reporter: Bryan Jacobs (bryanjacobs)
Affected Versions: 2.11.4
See #8342, #8344, #8800

@scabug
Copy link
Author

scabug commented Jul 13, 2015

Ian Bellamy (imbellish) said (edited by @lrytz on Jul 15, 2015 6:16:55 AM UTC):
Possible debug script can be found here:

scala> class MyClass {
     | def something(in: String): String = {
     | in + "_z"
     | }
     | def something: (String) => String = {
     | case _ => "Fixed"
     | }
     | }

scala> val my = new MyClass()
my: MyClass = MyClass@155cf22

scala> println(List("x", "y").map(my.something))
List(Fixed, Fixed)

scala> class MyClass {
     | def something(in: String): String = {
     | in + "_z"
     | }
     | def somethingelse: (String) => String = {
     | case _ => "Fixed"
     | }
     | }
defined class MyClass

scala> println(List("x", "y").map(my.something))
List(Fixed, Fixed)

scala> val my = new MyClass()
my: MyClass = MyClass@15a4a5

scala> println(List("x", "y").map(my.something))
List(x_z, y_z)

scala> println(List("x", "y").map(my.somethingelse))
List(Fixed, Fixed)

@scabug
Copy link
Author

scabug commented Jul 15, 2015

@lrytz said (edited on Jul 15, 2015 6:49:52 AM UTC):
In my reading of the spec, this should be ambiguous. Shorter example:

object Test extends App {
  def f(s: String): String = "1"
  val f: (String) => String = s => "2"

  val t: String => String = f

  println(t("")) // 2
}

http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#overloading-resolution

The reference to f is not an application or a type application (last paragraph of overloading resolution). Both definitions of f are compatible to the expected type, the method via eta-expansion (http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#implicit-conversions), so the most specific version is chosen:

  • the method is as specific as the value because the value is applicable to arguments of the method's parameter types (case 1)
  • the value is as specific as the method (case 3)

If I'm wrong in reading the spec, it would be good to add an example to it explaining this case.

@som-snytt
Copy link

Dotty says 1.

[[syntax trees at end of                     typer]] // t9395.scala
package <empty> {
  final lazy module val Test: Test = new Test()
  final module class Test() extends Object(), App { this: Test.type =>
    def f(s: String): String = "1"
    val f: String => String =
      {
        def $anonfun(s: String): String = "2"
        closure($anonfun)
      }
    val t: String => String =
      {
        def $anonfun(s: String): String = Test.f(s)
        closure($anonfun)
      }
    println(Test.t.apply(""))
  }
}

Is this like the Monty Hall problem where you always pick the other door?

@som-snytt
Copy link

scala/scala3#18294

@SethTisue SethTisue added this to the Backlog milestone Sep 1, 2023
@som-snytt
Copy link

Sorry I did not note which version of Dotty was current two years ago.

Currently, 3.5.2 says 2.

Odersky says "as specified" scala/scala3#19654 (comment)

There is recent Dotty history of trying to make overload resolution with eta-expansion do something "intuitive". For some people, that may mean "f should resolve the same as f(_)". For others, "Do extra work to discover some interpretation of the code I wrote that compiles, and take that as correct."

The explanations here are about mechanisms and not specification:
scala/scala3#21727 (comment)
scala/scala3#21847 (review)

The spec says:

Implicit conversions can be applied to expressions whose type does not match their expected type, to qualifiers in selections, and to unapplied methods.

The f reference (nullary and matches its expected type) does not meet any of these criteria. That justifies doing the obvious thing: take the f that works before opening a can of worms to adapt alternatives.

Closing this ticket since further clarifications or progressions will take place at scala3.

@som-snytt som-snytt closed this as not planned Won't fix, can't repro, duplicate, stale Oct 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants