-
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
Refine overriding pairs in RefChecks #12982
Conversation
We now exclude a pair of overriding / overridden symbols that have a common subclass parent of the base class only under the additional condition that their signatures also match. scala#12828 shows a case where this matters: ```scala trait Foo[A]: def foo(x: A): Unit trait Bar[A] extends Foo[A]: def foo(x: A & String): Unit = println(x.toUpperCase) object Baz extends Bar[Int] // error overriding foo: incompatible type @main def run() = Baz.foo(42) ``` When checking `Baz`, there is a common subclass parent (namely `Bar`), but the signatures of the two `foo` definitions as seen from `Bar` are different, so we cannot assume that the pair has already been checked. Fixes scala#12828
test performance please |
performance test scheduled: 1 job(s) in queue, 0 running. |
Before complicating OverridingPairs more I think it's worth considering how what we're doing differ from Scala 2 which is known to be more correct than us (#7551), namely we know that https://github.com/lampepfl/dotty/blob/master/tests/run/i5094.scala is compiling when it shouldn't because the final method SO is override by the method in SOIO. This was fixed in Scala 2 by scala/scala@cc55bd9 which says:
And indeed if we change |
Performance test finished successfully: Visit https://dotty-bench.epfl.ch/12982/ to see the changes. Benchmarks is based on merging with master (d96a500) |
test performance please |
performance test scheduled: 1 job(s) in queue, 0 running. |
Performance test finished successfully: Visit https://dotty-bench.epfl.ch/12982/ to see the changes. Benchmarks is based on merging with master (f34a3bb) |
@smarter Unfortunately, the situation between Scala 2 and 3 is not comparable since Scala 3 is signature based and Scala 2 isn't. So the algorithms are different. The problem with trait ordering in linearization can be fixed easily with another test in OverridingPairs. But it turns out that the duplicate checks in
Here, if we make But if Ii remove the early returnn in
At first glance, this looks like a correct error, and might need a fix stdlib213. EDIT: It's a correct error only if we decide that two members override if their signatures match. The last commits go into another direction: Decree that there's not a real override since the full parameter types do not match, and suppress the error message and also any bridge generations. |
Remove illogical conditions that suppressed certain checks. Replace them by a separate condition that certain override checks are suppressed if the types of the members do not match (i.e. it's just the signatures that match but not the types). In that case, we have arguably overshot with declaring an override. Bridge generation is also suppressed in this case. Scala 2 behaves effectively the same way, by never taking signatures into account.
Refchecks now decouples overriding from signature matching. A signature match is in essence a pre-check that two members might override, but if the two members are methods we also need to compare their parameter types. This has three consequences: 1. Before emitting an override error, test that parameter types match. If not, we have a false override and no error should be reported. 2. Before emitting a bridge, do the same test and omit the generation if the test fails 3. When checking whether a class is fully implemented, check that any implementation also agrees completely in its parameter types.
Refchecks now decouples overriding from signature matching. A signature match is in essence a pre-check that two members might override, but if the two members are methods we also need to compare their parameter types. This has three consequences:
|
Apply linearization filter only to refchecks, not to other overriding pairs cursors.
test performance please |
performance test scheduled: 1 job(s) in queue, 0 running. |
Performance test finished successfully: Visit https://dotty-bench.epfl.ch/12982/ to see the changes. Benchmarks is based on merging with master (79fae19) |
That was quite a rabbit hole. |
@@ -1021,7 +1021,7 @@ object Denotations { | |||
* erasure (see i8615b, i9109b), Erasure takes care of adding any necessary | |||
* bridge to make this work at runtime. | |||
*/ | |||
def matchesLoosely(other: SingleDenotation)(using Context): Boolean = | |||
def matchesLoosely(other: SingleDenotation, alwaysCompareParams: Boolean = false)(using Context): Boolean = |
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.
I think this should be called alwaysCompareTypes
since Type#matches can also check the result type if we're after erasure.
* If not we treat them as not a real override and don't issue certain | ||
* error messages. Also, bridges are not generated in this case. |
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.
Why not skip all error messages instead of only certain ones? Currently it looks like we still emit overrideAccessError and overrideTargetNameError but if these methods are not true override then I don't see why we need to.
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.
In fact it's just the comment that is wrong. All errors go to overrideError which does the check whether it's a true override.
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.
All errors go to overrideError which does the check whether it's a true override
In that case, why not immediately check for false overrides at the top of checkOverride before doing any of the checks?
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.
Because the false overrides check is expensive and override checks are common while override errors are rare.
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.
LGTM, great to see this on more solid footing!
@@ -0,0 +1,8 @@ | |||
-- Error: tests/neg/i12828.scala:7:7 ----------------------------------------------------------------------------------- |
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.
I think this file should be removed
We now exclude a pair of overriding / overridden symbols that
have a common subclass parent of the base class only under the
additional condition that their signatures also match when seen
as members of the parent class.
#12828 shows a case where this matters:
When checking
Baz
, there is a common subclass parent (namelyBar
),but the signatures of the two
foo
definitions as seen fromBar
aredifferent, so we cannot assume that the pair has already been checked.
Fixes #12828