Exporting extension methods

export is great! It’s very nice to be able to make wrappers and not have to repeat all declarations on the wrapped type:

trait Foo {
  def method(a: Int): String
  def method2(a: Int, b: String): Unit = println(method(a) + b)
}

class FooImpl(val suffix: String) extends Foo {
  def method(a: Int): String = a.toString + suffix
}

class FooWrapper(foo: Foo) {
  export foo.*
}

I believe it’s the case that one can not forward methods defined in extensions. Certainly, the following doesn’t work

trait Foo {
  def method(a: Int): String
}
extension (foo: Foo) {
  def method2(a: Int, b: String): Unit = println(foo.method(a) + b)
}

class FooImpl(val suffix: String) extends Foo {
  def method(a: Int): String = a.toString + suffix
}


class FooWrapper(foo: Foo) {
  export foo.*
}
new FooWrapper(new FooImpl).method2(5, "f") // error

This may seem marginal, but it is particularly frustrating for typeclasses, where “member” functions are idiomatically defined using extension methods (IIUC):

trait Foo[T] {
  extension (t: T) {
    def method(a: Int): String
  }
}

class FooImpl(val suffix: String)

given Foo[FooImpl] with {
   extension (self: FooImpl) {
     def method(a: Int): String = a.toString + self.suffix
   }
}

new FooImpl("a").method(5) // works

class FooWrapper[T: Foo](foo: T) {
  export foo.* // does not export `method`
}

I think it’s reasonable that export doesn’t support this out of the box. I’m wondering if there’s a pattern out there I don’t know about or if there’s a chance that something like export foo.extension could work (as export foo.given does now). I know that last one is tricky because extension is not a hard keyword.

I see that export does not work in this case for two reasons:

  1. because the signature of method has to change from (t: T)(a: Int): String to (a: Int): String, and export does not drop arguments
  2. export foo.* only looks at members defined on the class of foo, which is Any in this case, export does not look into implicit scope to find extensions for foo

the best to do is manually make forwarders - not ideal:

class FooWrapper[T: Foo](foo: T) {
  def method(a: Int) = foo.method(a)
}

Future Work
For your case to work, then export would need to employ implicit search to find all possible extensions, and remap their signatures to be ordinary methods on the enclosing scope. This could be submitted as a SIP

This would mean that any change to imports might affect binary compatibility, in particular I’m not sure that incremental compilation would be able to deal with this correctly.

Yes, that is true. I think it would be a reasonable restriction that you have to specifically export the scopes in which you expect to search for extension methods (something like export Foo.extension above).