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

Inconsistence handling of varargs #2991

Closed
scabug opened this issue Jan 29, 2010 · 21 comments
Closed

Inconsistence handling of varargs #2991

scabug opened this issue Jan 29, 2010 · 21 comments

Comments

@scabug
Copy link

scabug commented Jan 29, 2010

There seems to be some inconsistent handling of varargs by Scala. Consider these two classes:

class X {
  def f(x: AnyRef) = x.toString
  def f(x: AnyRef, y: AnyRef*) = y.mkString(x.toString)
}

class Y {
  def f(x: Int) = x.toString
  def f(x: Int, y: Int*) = y.mkString(x.toString)
}

The ambiguity with f is resolved differently:

scala> new X
res0: X = X@442c76

scala> val x : AnyRef = "a"
x: AnyRef = a

scala> res0.f(x)
<console>:8: error: ambiguous reference to overloaded definition,
both method f in class X of type (x: AnyRef,y: AnyRef*)String
and  method f in class X of type (x: AnyRef)java.lang.String
match argument types (AnyRef)
       res0.f(x)
            ^

scala> new Y
res6: Y = Y@9ed91f

scala> res6.f(5)
res7: java.lang.String = 5

This problem was brought to my attention by Mockito's org.mockito.stubbing.OngoingStubbing interface, which overloads the method thenReturn to accept either an Object, or both an Object and a vararg of them.

@scabug
Copy link
Author

scabug commented Jan 29, 2010

Imported From: https://issues.scala-lang.org/browse/SI-2991?orig=1
Reporter: @dcsobral

@scabug
Copy link
Author

scabug commented Feb 1, 2010

Aaron Novstrup (anovstrup) said:
It should also be noted that Scala's behavior is also inconsistent with Java's, which hinders interoperability with Java libraries. There are at least two [http://stackoverflow.com/questions/2159248/spurious-ambiguous-reference-error-in-scala-2-7-7-compiler-interpreter/2172619 documented cases] where this has been a problem in practice -- the org.mockito.stubbing.OngoingStubbing.thenReturn method and the net.sf.oval.Validator.validate method.

@scabug
Copy link
Author

scabug commented Feb 2, 2010

@odersky said:
It's a potential implicit tuple conversion which makes the difference here. A single AnyRef can take all the other arguments as a tuple.

@scabug
Copy link
Author

scabug commented Feb 2, 2010

@dcsobral said:
Replying to [comment:3 odersky]:

It's a potential implicit tuple conversion which makes the difference here. A single AnyRef can take all the other arguments as a tuple.

I ask this to be reviewed, please, as this doesn't match what is happening. For example:

scala> class X {
     |   def f(x: AnyRef) = x.toString
     |   def f(x: AnyRef, y: AnyRef*) = y.mkString(x.toString)
     | }
defined class X

scala>

scala> new X
res0: X = X@1cdc190

scala> val x : AnyRef = "a"
x: AnyRef = a

scala> res0.f(x, x)
res1: String = a

If (x, x) being converted into a tuple caused problems, then the above wouldn't have worked. However, not only it worked, but it choose the vargargs definition, indicating no tuple conversion happened.

The problem only happens when a single parameter is passed, so I fail to see why would any conversion to tuple come into play, or why would it matter in selecting the function even if it did.

@scabug
Copy link
Author

scabug commented Feb 3, 2010

@lrytz said:
Replying to [comment:4 dcsobral]:

The problem only happens when a single parameter is passed, so I fail to see why would any conversion to tuple come into play, or why would it matter in selecting the function even if it did.

If you pass a single parameter then both methods are applicable, and the compiler tries to find out which one is more specific. It will find that none of the two is more specific than the other, both are applicable to parameters of types of the other (once via tuple conversion). see spec for more specific.

When you pass two parameters, only the second one is applicable. (The first one would work through tuple conversion, but this is only tired when no other alternative works).

@scabug
Copy link
Author

scabug commented Feb 3, 2010

@dcsobral said:
Replying to [comment:5 rytz]:

If you pass a single parameter then both methods are applicable, and the compiler tries to find out which one is more specific. It will find that none of the two is more specific than the other, both are applicable to parameters of types of the other (once via tuple conversion). see spec for more specific.

I assume that's a conversion into a Tuple1? Why would such a conversion be even considered, given that the type matches?

@scabug
Copy link
Author

scabug commented Feb 3, 2010

@lrytz said:
Replying to [comment:6 dcsobral]:

Replying to [comment:5 rytz]:

If you pass a single parameter then both methods are applicable, and the compiler tries to find out which one is more specific. It will find that none of the two is more specific than the other, both are applicable to parameters of types of the other (once via tuple conversion). see spec for more specific.

I assume that's a conversion into a Tuple1? Why would such a conversion be even considered, given that the type matches?

Sorry, let me try again.

object t {
  def f(x: AnyRef) = 1 // A
  def f(x: AnyRef, xs: AnyRef*) = 2 // B
}

if you call f("foo"), both A and B are applicable. Which one is more specific?

  • it is possible to call B with parameters of type (AnyRef), so A is as specific as B.
  • it is possible to call A with parameters of type (AnyRef, Seq[AnyRef]) thanks to tuple conversion, Tuple2[AnyRef, Seq[AnyRef]] conforms to AnyRef. So B is as specific as A.

Since both are as specific as the other, the reference to f is ambiguous.

@scabug
Copy link
Author

scabug commented Feb 3, 2010

@dcsobral said:
Ok, it makes perfect sense now. If you don't mind, I'll reproduce your explanation on Stack Overflow, on a question related to this.

It is unfortunate, however, that this causes an interoperability problem with Java.

@scabug
Copy link
Author

scabug commented Jul 22, 2010

@dcsobral said:
[http://stackoverflow.com/questions/3313929/how-do-i-disambiguate-in-scala-between-methods-with-vararg-and-without Another case] of interoperability problem has shown up. May I suggest this be added to the FAQ?

@scabug
Copy link
Author

scabug commented Nov 13, 2012

Steven Bethard (bethard) said:
Here's another instance of this problem:

http://stackoverflow.com/questions/13358705/force-single-argument-in-scala-varargs

+1 on adding something to the FAQ.

@scabug
Copy link
Author

scabug commented Jun 6, 2014

@Atry said (edited on Jun 6, 2014 3:22:28 AM UTC):

it is possible to call A with parameters of type (AnyRef, Seq[AnyRef]) thanks to tuple conversion, Tuple2[AnyRef, Seq[AnyRef]] conforms to AnyRef. So B is as specific as A.

It's wrong. The Scala Language Specification never said AnyRef* can be treated as Seq[AnyRef] when resolving overloaded methods.

@scabug
Copy link
Author

scabug commented Jun 6, 2014

@som-snytt said:
杨博 的 comment is right, but the outcome is the same. Instead of Seq[AnyRef], apply a series of applicability tests, with 0 to Inf of AnyRef.

@scabug
Copy link
Author

scabug commented Jun 27, 2014

Mikael Ståldal (mikaelstaldal) said:
Why is this closed as Not a Bug?

It causes serious interoperability issues with common Java libraries, and even JavaEE specifications. Look at the Configurable interface in JAX-RS 2.0:
https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/core/Configurable.html

It is not possible to invoke the one-arg register() method from Scala.

@scabug
Copy link
Author

scabug commented Jun 27, 2014

@Atry said (edited on Jun 27, 2014 12:52:12 PM UTC):
Even if it's a bug, the bug is not in the compiler implementation. Instead, it may be a bug about the specification, since the specification is counter-intuitive.

@scabug
Copy link
Author

scabug commented Dec 28, 2014

Hendy Irawan (ceefour) said:
I agree with [~mikaelstaldal], hopefully it can be fixed for 2.12, or at very least 2.13...

@scabug
Copy link
Author

scabug commented Jan 3, 2015

Arnaud Masson (amasson) said:
I had the same issue with JAX-RS register :(

@scabug
Copy link
Author

scabug commented Jul 4, 2015

@som-snytt said:
Reopening in hopes of appeasing the masses who are taking to the streets.

This PR offers options: scala/scala#4440

@scabug
Copy link
Author

scabug commented Jul 7, 2015

Aleksandr Panzin (jalexoid) said:
Considering that Scala's main "selling point" is interop with Java, why would this not be fixed in an explicit manner?

@scabug
Copy link
Author

scabug commented Jul 7, 2015

@paulp said:
It's a "selling point", not a "point".

@scabug
Copy link
Author

scabug commented Sep 22, 2015

Davi de Castro Reis (davi) said:
I just faced what I believe is the same problem in argparse4j setDefault. The argparse4s library single java file is a workaround for calling setDefault from scala:

https://github.com/mdekstrand/argparse4s/blob/master/src/main/java/net/elehack/argparse4s/ArgConfig.java

This is also the only reason I have java files in my codebase.

@scabug scabug closed this as completed Nov 20, 2015
@scabug
Copy link
Author

scabug commented Jan 6, 2016

@odersky said (edited on Jan 6, 2016 10:30:38 PM UTC):
Verified that the test works OK in Dotty. Dotty still uses auto-tupling but not as pervasively as nsc. In particular, auto-tupling is done after overloading resulution. It's a last effort attempt if things do not work out without it.

Here's the test that compiles.

class X {
  def f(x: AnyRef) = x.toString
  def f(x: AnyRef, y: AnyRef*) = y.mkString(x.toString)
}

class Y {
  def f(x: Int) = x.toString
  def f(x: Int, y: Int*) = y.mkString(x.toString)
}

object Test {
  val x: AnyRef = "a"
  val res0 = new X
  res0.f(x)
  val res1 = new Y
  res1.f(5)
}

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

1 participant