Skip to content

implicit extensions not found when sub typing of receiver gets involved #18675

Open
@Ichoran

Description

@Ichoran

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions