Finally with the release of 3.4.0 I was able to test SIP 54 - Multi-Source Extension Overloads (aka relaxed extension methods) on my library (until very recently was blocked by regressions, so nightly version was not eligible).
It seems that SIP 54 was a good step forward, however not enough IMO.
Consider the following example (scastie link):
class Foo[T]
object Lib:
extension (foo: Foo[Int])
def bar: Unit = {}
import Lib.*
extension (foo: Foo[String])
def bar: Unit = {}
val f = Foo[Int]()
f.bar //error
If a library defines an extension method on one type and I define a different extension method with the same name in my own code on a different type, the overloading mechanism chooses the method according to the classic scoping mechanism instead of choosing the best method for the extended type.
No error is generated and everything works as expected if we use implicit classes (scastie link):
class Foo[T]
object Lib:
implicit class FooIntExt(foo: Foo[Int]):
def bar: Unit = {}
import Lib.*
implicit class FooStringExt(foo: Foo[String]):
def bar: Unit = {}
val f = Foo[Int]()
f.bar //works!
I think we’ve gotten used to implicit class semantics, and this should be preserved.
- What happens if we convert the standard library to Scala 3 to use extension methods instead of implicit classes?
- What happens if for the upcoming Named Tuples feature a library creates an extension method for its named tuples and its users have their own extension methods on different named tuples, but with the same name?
I think in both cases we get ambiguities or errors that can be a hassle to workaround. SIP 54 already special-cased extension methods for overloading. We need to take this even further and for extension methods apply priorities according to the extended type match, and only then apply the scoping priorities.