-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support extension methods imported from different objects #17050
Conversation
test performance please |
1 similar comment
test performance please |
@mbovel it seems the benchmark server is down? |
test performance please |
1 similar comment
test performance please |
performance test scheduled: 1 job(s) in queue, 0 running. |
Fixed. |
Performance test finished successfully: Visit https://dotty-bench.epfl.ch/17050/ to see the changes. Benchmarks is based on merging with main (424da9f) |
test performance please |
performance test scheduled: 1 job(s) in queue, 0 running. |
Performance test finished successfully: Visit https://dotty-bench.epfl.ch/17050/ to see the changes. Benchmarks is based on merging with main (424da9f) |
This looks great! I just wonder if it's necessary to add a test where an import is re-imported at a deeper level to make sure the search for alternatives is maintained. Something like object ReimportSucceeds:
import One._
def test: Unit =
import Two._
import One._
"five".wow
5.wow The reason this is important is that people may want only some extensions in a broader context, but when they switch to a narrower context with more extensions, they may still wish to have the broader extensions available. For instance, one might want A bulk reimport is probably a reasonable balance between actual accidental collisions and requiring a bunch of extra boilerplate in order to get the desired functionality. Does it already work this way by default? Because I don't know how duplicate but more deeply-nested import statements are handled, I don't know whether this is actually a separate case that needs to be tested. |
@Ichoran Nested imports take precedence over outer ones, so the example you show should not be a problem. |
doesnt this require a SIP? or does this come under "inference", I notice this changes the reference |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be under experimental import until SIP approval
Yes, indeed. As much as I think this is a clear win, there is a reason we have a process: to allow eyes with other kinds of expertise than compiler engineering to see it first, to catch things we might miss. |
I agree that this could be a SIP, but I won't have the time to submit and defend it. Would one of you want to take that over? |
I can write the SIP, yes. |
For now this requires a language import import language.experimental.relaxedExtensionImports We will drop that requirement once the SIP committee has approved the change. |
@sjrd - I'm glad you're able to write the SIP! If it would help to have more motivating examples, I have plenty! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the other problems from the Contributor's thread is that this example still does not work:
import language.experimental.relaxedExtensionImports
import scala.annotation.targetName
trait Foo[+T]
object Ops:
extension (foo : Foo[Int])
@targetName("bazInt")
def baz : Unit = {}
import Ops.*
extension (foo : Foo[Double])
@targetName("bazDouble")
def baz : Unit = {}
@main def Test =
val f = new Foo[Int] {}
f.baz //error
In this case the bazDouble
is always chosen because it is not imported, but again surely from the user's point of view it makes sense to also try the valid Ops.bazInt
that was imported. To compare with the working Implicit Classes version:
import scala.annotation.targetName
trait Foo[+T]
object Ops:
implicit class FooOpsInt(foo : Foo[Int]):
@targetName("bazInt")
def baz : Unit = {}
import Ops.*
implicit class FooOpsDouble(foo : Foo[Double]):
@targetName("bazDouble")
def baz : Unit = {}
@main def Test =
val f = new Foo[Int] {}
f.baz //ok
Yes, but that's just wishful thinking. And catering to that would simply produce a huge ball of mud. The current change is already highly problematic for it non-orthogonality. But the argument was that this is an important use case and there was no way to re-arrange things to make it work without the change. By comparison, the latest example is a fringe use case, not worth complicating the rules even further. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a great improvement over the status quo - and the local scope version always being picked is something that can be overcome by application developers (move the extension to a separate object)
Since SIP 54 just got accepted, I'll remove the experimental language import. |
It's been accepted to be shipped as experimental, at this stage. ;) |
Ah indeed, it was labelled stage:design, not stage:implementation. |
Add a special case to name resolution so that when expanding an extension method from `e.m` to `m(e)` and `m` is imported by several imports on the same level, we try to typecheck under every such import and pick the successful alternative if it exists and is unambiguous. Fixes scala#16920
As long as this is not SIP approved, we need to put this under experimental.
Add a special case to name resolution so that when expanding an extension method from
e.m
tom(e)
andm
is imported by several imports on the same level, we try to typecheck under every such import and pick the successful alternative if it exists and is unambiguous.Fixes #16920