Well, that could be, but isn’t this conceptually the cleanest solution? What, realistically, are the options? Especially in a way that is TASTY-compatible?
(1) Status quo: extension methods effectively have no namespace. They clobber each other willy-nilly like everything without a namespace always does, getting quadratically bad the more they are used. Solution: style guide says “never use extension methods instead of implicit classes when writing libraries”. For something that was supposed to replace implicit classes (and has considerably nicer syntax), this is pretty bad.
(2) Generate a synthetic erased implicit class, and use the regular implicit mechanism: when you see x.foo
it is code for (new org.whomever.package$Filename$4(x)).foo
, where this is the 4th extension in package package
from file Filename
. Or something like that. Anyway, then you have replaced the implicit class mechanism, which works, with a better-hidden variant of itself.
(3) Allow ad-hoc overloading based on imports at least for extension methods, if not everything. The compiler already figures out the options to generate a workable error message. So, yes, you have a new idea of overloaded dispatch where foo(x)
might be bar.baz.foo(x)
or bippy.quux.foo(x)
depending on whether the type of x
is knowable and resolves to one or the other. If you want to fit it into the existing MultiDenotation
framework, then you would need to create a synthetic type equivalent to
trait SyntheticMultiDenotation$8 {
inline def foo(x: String): Int = bar.baz.foo(x)
inline def foo(f: Float): Long = bippy.quux.foo(x)
}
and then import of the two extension methods with foo
would be equivalent to asking for the two existing foo
methods to not be imported, and instead creating an instance of such a trait (or an equivalent object
which forwards the extensions) and importing those foo
s instead. If the synthetic trait or object could not be created, then you’d get a compiler error.
(4) Allow more of the namespace to be specified. Very clunky, but at least you can still use method notation. So, x.foo
doesn’t work, but x.baz.foo
and x.quux.foo
do (assuming no ambiguity on baz
or quux
). Unfortunately, this would introduce a source of name clashes–full paths vs. method names–that never used to exist. But since it would only be invoked when the class itself didn’t have the method, maybe it would be okay.
(5) Exploit the targetName
mechanism for disambiguation, with part of the desugaring of x.foo
being resolving foo(x)
to stringOverloadFoo(x)
. I’m not sure that this is actually a solution, because to me it seems like all it does is allow overloads with types that are different when seen by Scala (which should be okay) but not Java (oops), so maybe the compiler can’t use this to invent overloads where none exist.
Maybe there are other options. But to me, it seems like ad-hoc overloading (for extension methods if nothing else) is conceptually the cleanest. There is one place it doesn’t work: when the extensions are defined in different files in the same namespace. Then there is literally no way to disambiguate the two. But that could be worked around with either @targetName
or simply saying: sorry guys, but if you really want to do that, you’ve got to stuff it all in the same file. It’s your namespace, so you ought to have that much control, even if it isn’t the nicest organization. (We already have compromises to make in that regard with things in objects, though actually export
reduces that problem substantially.)