bug
bug copied to clipboard
compiler silently ignores ambiguous implicits, pretends none exist (unless in -Xlog-implicits)
The appended error message is seen under -Ylog:typer, but under normal compilation scalac offers nothing.
Secondarily, it would be nice if this situation were recognized as the non-conflict which it is: the "conflicting" implicits are prefix-insensitive and the identical method implementation. This situation arises very easily when mixing implicits into package objects and trying to evolve codebases.
But at the very least how about we give people a hint what happened.
trait Impy { implicit def string2int(s: String): Int = s.length }
object o1 extends Impy
object o2 extends Impy
object Test {
import o1._
import o2._
val x: Int = "abc"
}
// [log typer] Import is genuinely ambiguous:
// types: o2.type =:= o1.type false members: true
// member type 1: (s: String)Int
// member type 2: (s: String)Int
// [log typer] implicit adapt failed: reference to string2int is ambiguous;
// it is imported twice in the same scope by
// import o2._
// and import o1._
Imported From: https://issues.scala-lang.org/browse/SI-7697?orig=1 Reporter: @paulp Affected Versions: 2.10.4, 2.11.5
@retronym said:
The terminology is confusing here. "Ambiguous implicits" describes competing, applicable implicits which score equally on the specificity test. Here, we have have an ambiguous binding. This is really an "invalid implicit", as diagnosed by -Xlog-implicits
qscalac -Xlog-implicits sandbox/test1.scala
sandbox/test1.scala:9: <string2int: error> is not a valid implicit value for String("abc") => Int because:
reference to string2int is ambiguous;
it is imported twice in the same scope by
import o2._
and import o1._
val x: Int = "abc"
^
sandbox/test1.scala:9: error: type mismatch;
found : String("abc")
required: Int
val x: Int = "abc"
^
one error found
Secondarily, it would be nice if this situation were recognized as the non-conflict which it is: the "conflicting" implicits are prefix-insensitive and the identical method implementation.
Given separate compilation, we can only base our decision on the type signature, not on the body of the method.
We should figure out how to emit the useful output of -Xlog-implicits when and where it is needed. I've never been able to figure out the right balance here, though.
@paulp said: I don't understand the relevance of separate compilation. I didn't mean go decompiling the bytecode to look at the the method, I meant they are literally the identical method, in the same sense that "Fido the dog" and "Fido the dog" are known to be equal because there is only one Fido the dog. They each have their own synthetic forwarder and each one exists for no reason but delivering Fido the dog.
@retronym said:
They could, however, predicate some logic on the identity of Impy.this.
@paulp said: Shoot. You got me.
@paulp said: I don't know if someone else ever reported this - guessing not - I'll just throw it in here as the closest thing to an applicable ticket. Fair warning, this is probably a slide in every talk I give until I die or it is changed, whichever comes first.
https://gist.github.com/paulp/36b67d27400a9f3be96a
class Bippy(override val toString: String)
trait A { implicit def lowPriority: Bippy = new Bippy("A") }
object B extends A { implicit def highPriority: Bippy = new Bippy("B") }
object C { implicit def highPriority: Bippy = new Bippy("C") }
object Test {
def main(args: Array[String]): Unit = {
import B._, C._
println( implicitly[Bippy] ) // Prints: A
}
}
@retronym said:
Yep, -Xlog-implicts tells you about that one, too.
qscalac -Xlog-implicits sandbox/test1.scala
sandbox/test1.scala:10: <highPriority: error> is not a valid implicit value for Bippy because:
reference to highPriority is ambiguous;
it is imported twice in the same scope by
import C._
and import B._
println( implicitly[Bippy] ) // Prints: A
^
But one man's trash is another man's treasure when it comes to these diagnostics. I don't think they are in a state to turn on by default.
@paulp said: It's good of -Xlog-implicits to note the ambiguity, but I might once have naively thought it would also fail the compile. It's quite interesting that it can echo a message including "<highPriority: error>" and "reference to highPriority is ambiguous" on its way to a successful compilation run.
Scala 3.6.3 accepts the code
Scala 3 rejects the slide example, since there is no more naming and shadowing.
|Ambiguous given instances: both method highPriority in object C and method lowPriority in trait A match type Bippy of parameter e of method implicitly in object Predef
It accepts the OP but:
import language.implicitConversions
trait Impy:
given Conversion[String, Int] = s => s.length + getClass.getSimpleName.takeWhile(_ == 'o').length
object o1 extends Impy
object oo2 extends Impy
@main def test = println:
import o1.given
import oo2.given
val x: Int = "abc"
x
is 5. Spooky. Not sure why, is it the "nesting level" of the imports, which means context nesting of the import as opposed to scope.