scalascala-3opaque-types

Same type after erasure in opaque types in Scala 3


I want to define two opaque type aliases which are implemented with the same underlying type double. I also want to define two extension methods on theses aliases which have the same name. The following compiles:

object MyMath1:
  object Logarithms1:
    opaque type Logarithm1 = Double
    extension (l1: Logarithm1) def foo: Unit = ()
  object Logarithms2:
    opaque type Logarithm2 = Double
    extension (l2: Logarithm2) def foo: Unit = ()
  import Logarithms1.Logarithm1
  import Logarithms2.Logarithm2

However I want these extension methods to be defined outside the scope of where the opaque type aliases are defined, i.e. in the MyMath object:

object MyMath2:
  object Logarithms1:
    opaque type Logarithm1 = Double
  object Logarithms2:
    opaque type Logarithm2 = Double
  import Logarithms1.Logarithm1
  import Logarithms2.Logarithm2
  extension (l1: Logarithm1) def foo: Unit = ()
  extension (l2: Logarithm2) def foo: Unit = ()

This gives the following compiler error:

Double definition:
def foo(l1: MyMath.Logarithms1.Logarithm1): Unit in object MyMath at line 52 and
def foo(l2: MyMath.Logarithms2.Logarithm2): Unit in object MyMath at line 53
have the same type after erasure.

Consider adding a @targetName annotation to one of the conflicting definitions
for disambiguation.

Adding a @targetName annotation does fix the issues.

My question: why does the first code snippet compile but the second one does not? Surely in the first snippet both methods also have the same type after erasure.


Solution

  • A compiled Java class must not have two methods whose signatures (name + argument types after type erasure) coincide.

    In your first snippet the methods are declared in different objects and are therefore compiled to methods of different classes, so there is no conflict.

    In the second case both methods are to be rendered as methods of the same class. Since opaque types are replaced by the underlying type in the compiled class, there is a conflict (same name foo, same erased argument type double).

    Applying @targetName annotation lets you rename one or both methods in compiled class, removing the ambiguity. See Relationship with Overriding.