I have been using opaque types a lot in my library and they are great, but I think the “implicit conversion” that occurs within the opaque source is far too lenient and in fact it caused me to have many hidden errors that I only discovered when I moved the opaque definition to a different file.
Consider the following simple example that compiles in Scala 3:
opaque type Foo[T] = Int val f1 : Foo = 1 val f2 : Foo = f1 opaque type Bar = Int val b1 : Bar = f1
The opaque has an invariant type argument which means that clearly it does not make sense that
Foo[T] =:= Foo[R] unless
T =:= R in any context, including within the scope where the opaque is defined. While I can understand the opaque concept should allow conversion
Int ==> Foo[T] and
Foo[T] ==> Int, I don’t think we should allow the transitive conversion of
Foo[T] ==> Foo[R]. If the opaque types conversion to/from their underlying types was actually based on the implicit conversion mechanism, then the transitive conversion would have never been allowed, and for a good reason. This is why I think we should modify the behavior of opaque types with type arguments and prevent the code above from compiling.
For the same reason, I think we should not allow the transitive conversion
Foo[T] ==> Bar even if they are defined in the same source file.