2010-01-29 3 views
7

Qualcuno può spiegare l'errore di compilazione sotto? È interessante notare che se modifico il tipo di ritorno del metodo get() su String, il codice viene compilato correttamente. Si noti che il metodo thenReturn ha due overload: un metodo unario e un metodo varargs che richiede almeno un argomento. Mi sembra che se l'invocazione qui è ambigua, allora sarebbe sempre ambigua.Errore di riferimento ambiguo spuri nel compilatore/interprete Scala 2.7.7?

Ancora più importante, c'è un modo per risolvere l'ambiguità?

import org.scalatest.mock.MockitoSugar 
import org.mockito.Mockito._ 

trait Thing { 
    def get(): java.lang.Object 
} 

new MockitoSugar { 
    val t = mock[Thing] 

    when(t.get()).thenReturn("a") 
} 

error: ambiguous reference to overloaded definition, both method thenReturn in trait OngoingStubbing of type
java.lang.Object,java.lang.Object*)org.mockito.stubbing.OngoingStubbing[java.lang.Object] and method thenReturn in trait OngoingStubbing of type (java.lang.Object)org.mockito.stubbing.OngoingStubbing[java.lang.Object] match argument types (java.lang.String) when(t.get()).thenReturn("a")

+0

Ho aperto un biglietto su questo, quando ho scoperto che Scala non era nemmeno coerente con se stessa. Biglietto https://lampsvn.epfl.ch/trac/scala/ticket/2991. –

+0

Il ticket è stato chiuso come non valido e ora c'è una spiegazione su cosa sta succedendo, che copierò sulla mia risposta. Al momento, non penso ci siano molte possibilità di un cambiamento in questo. –

risposta

9

Bene, è ambiguo. Suppongo che la semantica di Java lo consenta, e potrebbe meritare un ticket che richiede l'applicazione della semantica Java in Scala.

La fonte del ambiguitity è questo: un parametro vararg può ricevere un numero qualsiasi di argomenti, tra cui 0. Quindi, quando si scrive thenReturn("a"), cosa si intende per chiamare il thenReturn che riceve un singolo argomento, o vuoi dire a chiamare lo thenReturn che riceve un oggetto più un vararg, passando 0 argomenti al vararg?

Ora, che succede questo genere di cose, Scala cerca di trovare quale metodo è "più specifico". Chiunque sia interessato a dettagli dovrebbe guardare in alto che nelle specifiche di Scala, ma qui è la spiegazione di ciò che accade in questo caso particolare:

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.

Per quanto riguarda la cosa "tupla conversione", è uno dei più gli zuccheri sintattici più oscuri di Scala. Se si effettua una chiamata f(a, b), dove a e b hanno tipi A e B, e non c'è f accettare (A, B) ma c'è un f che accetta (Tuple2(A, B)), allora i parametri (a, b) saranno convertiti in una tupla.

Ad esempio:

scala> def f(t: Tuple2[Int, Int]) = t._1 + t._2 
f: (t: (Int, Int))Int 

scala> f(1,2) 
res0: Int = 3 

Ora, non c'è conversione tuple in corso quando thenReturn("a") si chiama. Non è questo il problema. Il problema è che, dato che è possibile la conversione di tuple, nessuna delle versioni di thenReturn è più specifica, perché qualsiasi parametro passato a uno potrebbe essere passato all'altro.

6

Ebbene, ho capito come risolvere l'ambiguità (sembra abbastanza ovvio in retrospettiva):

when(t.get()).thenReturn("a", Array[Object](): _*) 

Come osservato Andreas, se il metodo ambigua richiede riferimento null anziché un array vuoto, è possibile utilizzare qualcosa come

v.overloadedMethod(arg0, null.asInstanceOf[Array[Object]]: _*) 

per risolvere l'ambiguità.

+1

Questa è la risposta effettiva (anche se Daniel Sobral è ovviamente molto istruttivo). Sebbene, ho trovato che dovevo fornire una matrice del tipo restituito piuttosto che solo matrice [oggetto]. – gladed

4

Se si guardano le API della libreria standard si vedrà questo problema gestito in questo modo:

def meth(t1: Thing): OtherThing = { ... } 
def meth(t1: Thing, t2: Thing, ts: Thing*): OtherThing = { ... } 

In questo modo, nessuna chiamata (con almeno un parametro Thing) è ambigua, senza fluff extra, come Array[Thing](): _* .

+0

Sembra un modo migliore per scriverlo. Sfortunatamente, 'thenReturn' è definito in una libreria Java di terze parti (Mockito). Come ha sottolineato Daniel, Java risolve l'ambiguità del metodo non varargs, quindi non posso nemmeno chiamarlo bug nella libreria. –

+0

Ed è abbastanza probabile che sia vulnerabile all'ambiguità quando si passano due parametri, ora che capisco il problema. –

3

Ho avuto un problema simile utilizzando Oval (oval.sf.net) cercando di chiamare il metodo validate() -.

ovale definisce 2 validazione) metodi (:

public List<ConstraintViolation> validate(final Object validatedObject) 
public List<ConstraintViolation> validate(final Object validatedObject, final String... profiles) 

provare questo da Scala: validator.validate(value) produce il seguente compilatore errore:

both method validate in class Validator of type (x$1: Any,x$2: <repeated...>[java.lang.String])java.util.List[net.sf.oval.ConstraintViolation]               
and method validate in class Validator of type (x$1: Any)java.util.List[net.sf.oval.ConstraintViolation]                        
match argument types (T)                                             
     var violations = validator.validate(entity);                                      

ovale bisogno della varargs-parametro deve essere nullo , non un array vuoto, quindi ho finalmente capito che funziona:

validator.validate(value, null.asInstanceOf[Array[String]]: _*)

+0

Grazie per aver aggiunto il tuo caso, Andreas. L'ho documentato sul biglietto trac di Daniel (https://lampsvn.epfl.ch/trac/scala/ticket/2991). –

6

Nel caso specifico di Mockito, è possibile utilizzare i metodi API alternativi progettati per essere utilizzati con i metodi di vuoto:

doReturn("a").when(t).get() 

goffo, ma dovrete fare, come Martin et al don' Sembra probabile che comprometta Scala per supportare le vararg di Java.