Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Not a Bug
    • Affects Version/s: None
    • Fix Version/s: None
    • Component/s: Misc Compiler
    • Labels:
    • Environment:

      vararg

      Description

      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.

        Activity

        Hide
        Aaron Novstrup added a comment -

        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.

        Show
        Aaron Novstrup added a comment - 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.
        Hide
        Martin Odersky added a comment -

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

        Show
        Martin Odersky added a comment - It's a potential implicit tuple conversion which makes the difference here. A single AnyRef can take all the other arguments as a tuple.
        Hide
        Daniel Sobral added a comment -

        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.

        Show
        Daniel Sobral added a comment - 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.
        Hide
        Lukas Rytz added a comment -

        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).

        Show
        Lukas Rytz added a comment - 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).
        Hide
        Daniel Sobral added a comment -

        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?

        Show
        Daniel Sobral added a comment - 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?
        Hide
        Lukas Rytz added a comment -

        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.

        Show
        Lukas Rytz added a comment - 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.
        Hide
        Daniel Sobral added a comment -

        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.

        Show
        Daniel Sobral added a comment - 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.
        Hide
        Daniel Sobral added a comment -
        Show
        Daniel Sobral added a comment - 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?
        Hide
        bethard added a comment -

        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.

        Show
        bethard added a comment - 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.

          People

          • Assignee:
            Unassigned
            Reporter:
            Daniel Sobral
            TracCC:
            Aaron Novstrup
          • Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development