Open
Description
Compiler version
3.3.1
Minimized code
object Test:
opaque type One = String
object One:
inline def wrap(s: String): Test.One = s
extension (o: One)
inline def +(f: Float): Test.One = wrap((o: String) + f)
inline def +(d: Double): Test.One = wrap((o: String) + d)
opaque type Two = String
object Two:
inline def wrap(s: String): Test.Two = s
extension (t: Two)
inline def +(f: Float): Test.Two = wrap((t: String) + f)
inline def +(d: Double): Test.Two = wrap((t: String) + d)
extension (f: Float)
// If you have this...
@annotation.targetName("f_plus_one")
inline def +(o: Test.One): Test.One = o + f
// ... you can have either this, or...
@annotation.targetName("f_plus_two")
inline def +(t: Test.Two): Test.Two = t + f
extension (d: Double)
// ...this, but not both at the same time
@annotation.targetName("d_plus_one")
inline def +(o: Test.One): Test.One = o + d
object Run:
import Test.{given, _}
val o = One.wrap("one")
val t = Two.wrap("two")
def run(): Unit =
println(o + 5.0)
println(o + 5f)
println(t + 5.0)
println(t + 5f)
println(5f + o) // Point of failure when both are defined
println(5f + t)
println(5.0 + o)
Output
This is from Scastie: https://scastie.scala-lang.org/R914FtbrSV6e78auU176Mg
None of the overloaded alternatives of method + in class Float with types
(x: Double): Double
(x: Float): Float
(x: Long): Float
(x: Int): Float
(x: Char): Float
(x: Short): Float
(x: Byte): Float
(x: String): String
match arguments ((Playground.Run.o : Playground.Test.One))
The same bug exists when compiled locally (i.e. not with scastie).
Expectation
There are three instances of +
here:
(1) Float + One (String)
(2) Float + Two (String)
(3) Double + One (String)
Because 1 & 2 resolve correctly, and 1 & 3 resolve correctly, it is expected that if you have 1 & 2 & 3, that +
would resolve correctly.
Furthermore, if it doesn't resolve correctly, the expectation would be for it to say something about an ambiguity. Instead, it only talks about the built-in overloads of +
for Float
.
Note that a workaround is
extension (f: Float)
transparent inline def +(inline that: Test.One | Test.Two) = inline that match
case o: Test.One => o + f
case t: Test.Two => t + f
but having to do this explicitly is quite a pain.
(Yes, I realize that +
on strings shouldn't be symmetric, but it was easier to write this way.)