Implicit conversion of composed opaque types

Hello everyone,
The code below compiles with Scala 3.1.0. The execution of the method test() results in stack overflow error. I would like to know whether this behaviour should be treated as a bug which should be reported.

object X:
  opaque type A = Double
  object A:
    def apply(d: Double): A = d

object Y:
  opaque type B = X.A
  object B:
    def apply(value: X.A): B = value

    given Conversion[B, Double] with
      def apply(b: B): Double = b // Why not?

def test(): Unit =
  val x: Double = Y.B(X.A(0))

Thank you very much!

It produces an SO because the conversion keeps applying to itself until it dies.

I would say this is a bug, since IMHO, an implicit conversion should not be available inside itself; or at least not implicitly, if you need a recursive implicit conversion be explicit about it.
However, I would guess making this exception would be hard since recursive definitions are always allowed.

In any case, implicit conversions are a bug on their own so /shrug.

But this happens only in the case of composed opaque types. The following code does not produce any errors.

object X:
  opaque type A = Double
  object A:
    def apply(d: Double): A = d

    given Conversion[A, Double] with
      def apply(a: A): Double = a

def test(): Unit =
  val x: Double = X.A(0.0)

In this case, the method test() works as expected.

Sure because in that case, the compiler doesn’t need to apply the conversion itself.

Note that what you are seeing has nothing to do with opaques, you can reproduce it quite easily like this:

given Conversion[String, Double] with
  def apply(str: String): Double = str

val x: Double = "foo"

This is very interesting. When your example is run, the executing thread just hangs. :astonished: I do not think that this should happen.
If the implicit conversion is omitted, the code does not compile because the type A is opaque.

scala> object X:
     |   opaque type A = Double
     |   object A:
     |     def apply(d: Double): A = d
     |
     | val x: Double = X.A(0)

-- Error:
6 |val x: Double = X.A(0)
  |                ^^^^^^
  |                Found:    X.A
  |                Required: Double

So, it seems that the conversion is used.

In Scala 2, -Xlint warns about an implicit resolving to an enclosing definition.

➜  scala -Xlint
Welcome to Scala 2.13.8 (OpenJDK 64-Bit Server VM, Java 17.0.1).
Type in expressions for evaluation. Or try :help.

scala> implicit def f(s: String): Int = s
                                        ^
       warning: Implicit resolves to enclosing method f
warning: 1 feature warning; for details, enable `:setting -feature` or `:replay -feature`
def f(s: String): Int
2 Likes