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
8026369: javac potentially ambiguous overload warning needs an improved scheme #12645
Conversation
👋 Welcome back archiecobbs! A progress list of the required criteria for merging this PR into |
@archiecobbs The following labels will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
In the |
Ah - my apologies. Those got added during development but were not needed once all the bugs were worked out. I've removed them and that will also eliminate /label remove client,i18n |
@archiecobbs The |
*/ | ||
public static final long POTENTIALLY_AMBIGUOUS = 1L<<48; | ||
public static final long UNUSED_1 = 1L<<48; |
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.
what about just adding the comment saying that this spot is free without declaring an unused flag?
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.
Works for me - as long as it's OK that this will cause the Flags
enum constant ordinals to renumber after that point.
Fixed in 0131b9b.
I think a CSR is needed as we will be generating more warnings and projects compiling with -Werror could see compilation errors |
Will do - thanks. |
* ambiguities for both forEachRemaining() and tryAdvance() (in both cases the | ||
* overloads are IntConsumer and Consumer<? super Integer>). So we only want | ||
* to "blame" a class when that class is itself responsible for creating the | ||
* ambiguity. So we declare that site is "responsible" for the ambigutity between |
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.
typo: ambigutity -> ambiguity
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.
Thanks - fixed.
* As an optimization, we first check if either method is declared in site and | ||
* does not override any other methods (in which case site is responsible). | ||
*/ | ||
void checkPotentiallyAmbiguousOverloads(JCClassDecl tree, Type site) { |
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.
general comments: overall looks correct but I think the code should be split a bit using helper methods, that will help with readability, I think. Side: I'm a bit worried that overuse of streams in this code could imply some performance hit. Of course if the corresponding lint warning is not enabled we will skip it but a lot of projects compile with -Xlint:all
nowadays.
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 the code should be split a bit using helper methods
OK - will do.
I'm a bit worried that overuse of streams in this code could imply some performance hit
I asked basically the same question (in a separate thread) two days ago and nobody replied with a definitive answer (not surprising).
However, note also that in that same thread Christoph reported no timing difference between Stream
vs. for()
loop (here), although there were more allocations. FWIW.
Sorry to go off on a tangent here, but I'm sure this issue will come up again and it would be nice to have some kind of (informal) policy to go on...
I generally try to follow the "measure first, optimize second" rule to avoid preemptive "optimizations" that come at the expense of code clarity for unproven meaningful benefit.
So I can de-Stream
the code but are we sure it's worth it? Are we going to have a no Stream
policy in the compiler code? Why did we develop Stream
's if they can't be used in a mainstream tool like javac
? Where does the madness end? :)
There is also the larger philosophical question as well, which is that if a Stream
is appreciably slower than the semantically-equivalent for()
loop, then isn't that a Hotspot problem, not a developer problem?
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'm not saying that we should de-stream the code, actually we can do that later on, in a separate issue, iff there is a performance related complain, but it is true that in the past, I have seen some performance issues and the final culprit have been streams. But you are right it could be that it is not worthy to affect the readability of the code.
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.
OK thanks, I'll leave it for now - but it would be nice to (someday) do some comprehensive testing so we have a better intuitive understanding of the performance impact of using a Stream
in any particular situation.
I wonder if there is some IDE tool that could automatically Stream
ify and/or de-Stream
ify loops. If so, we could apply it to the entire compiler and compare...
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.
yes that would be nice to have, although streams have different effects depending on how hot the code path is, so in general they are OK. Inference code is one of the places where I have seen that using streams is not a good idea for example
List<java.util.List<MethodSymbol>> methodsByName = StreamSupport.stream( | ||
types.membersClosure(site, false).getSymbols(new ClashFilter(site), RECURSIVE).spliterator(), false) | ||
.map(MethodSymbol.class::cast) | ||
.filter(m -> m.owner.type.tsym != syms.objectType.tsym) |
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.
nit: this filter could be folded with the one above, new ClashFilter(site)
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.
Should be fixed in 7d8a54c.
// Gather all supertype methods overridden by m, directly or indirectly | ||
java.util.List<MethodSymbol> overriddenMethods = list.stream() | ||
.filter(m2 -> m2 != m) | ||
.filter(m2 -> overrides.test(m, m2)) |
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.
can these two filters be folded?
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.
Should be fixed in 7d8a54c.
.stream() | ||
.sorted(Comparator.comparing(e -> e.getKey().toString())) | ||
.map(Map.Entry::getValue) | ||
.peek(Collections::reverse) // seems to help warning ordering |
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.
not sure about reversing the order here, it seems to me that if the order is reversed then the warning is shown in the first occurrence of a method instead of in the second which is the offending one. So for example for this code:
interface I1 {
void foo(Consumer<Integer> c);
void foo(IntConsumer c);
}
the warning is shown for the first method when I think it should be shown for the second which is the one introducing the ambiguity
EDIT, after your last commit, this comment now applies to: methodGroups.forEach(Collections::reverse);
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.
The reversing was originally done in order to maintain consistency with the existing regression test outputs. But you are right - it results in visiting the methods backwards.
Fixed in e25cece.
* As an optimization, we first check if either method is declared in C and does not override | ||
* any other methods; in this case the class is definitely responsible. | ||
*/ | ||
BiPredicate<MethodSymbol, MethodSymbol> buildResponsiblePredicate(Type site, |
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 method not only builds the predicate as its name indicates, it also removes methods from the methodGroups
, not saying that we should split this method but I think that this removing activity should be mentioned in the comment above
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.
Oops, my bad - previous refactoring was incomplete.
Should be fixed in e25cece.
Thanks again for the careful review!
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.
sure, thanks, nice refactoring
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.
looks good, thanks for fixing this!
@archiecobbs This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 122 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@vicente-romero-oracle) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
/integrate |
@archiecobbs |
/sponsor |
Going to push as commit 1e3c9fd.
Your commit was automatically rebased without conflicts. |
@vicente-romero-oracle @archiecobbs Pushed as commit 1e3c9fd. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
This bug relates to the "potentially ambiguous overload" warning which is enabled by
-Xlint:overloads
.The warning detects certain ambiguities that can cause problems for lambdas. For example, consider the interface
Spliterator.OfInt
, which declares these two methods:Both methods have the same name, same number of parameters, and take a lambda with the same "shape" in the same argument position. This causes an ambiguity in any code that wants to do this:
That code won't compile; instead, you'll get this error:
The problem reported by the bug is that the warning fails to detect ambiguities which are created purely by inheritance, for example:
The cause of the bug is that ambiguities are detected on a per-method basis, by checking whether a method is part of an ambiguity pair when we visit that method. So if the methods in an ambiguity pair are inherited from two distinct supertypes, we'll miss the ambiguity.
To fix the problem, we need to look for ambiguities on a per-class level, checking all pairs of methods. However, it's not that simple - we only want to "blame" a class when that class itself, and not some supertype, is responsible for creating the ambiguity. For example, any interface extending
Spliterator.OfInt
will automatically inherit the two ambiguities mentioned above, but these are not the interface's fault so to speak so no warning should be generated. Making things more complicated is the fact that methods can be overridden and declared in generic classes so they only conflict in some subtypes, etc.So we generate the warning when there are two methods m1 and m2 in a class C such that:
If either method is declared in C, we locate the warning there, but when both methods are inherited, there's no method declaration to point at so the warning is instead located at the class declaration.
I noticed a couple of other minor bugs; these are also being fixed here:
(1) For inherited methods, the method signatures were being reported as they are declared, rather than in the context of the class being visited. As a result, when a methods is inherited from a generic supertype, the ambiguity is less clear. Here's an example:
Currently, the error is reported as:
Reporting the method signatures in the context of the class being visited makes the ambiguity clearer:
(2) When a method is identified as part of an ambiguous pair, we were setting a
POTENTIALLY_AMBIGUOUS
flag on it. This caused it to be forever excluded from future warnings. For methods that are declared in the class we're visiting, this makes sense, but it doesn't make sense for inherited methods, because it disqualifies them from participating in the analysis of any other class that also inherits them.As a result, for a class like the one below, the compiler was only generating one warning instead of three:
With this patch the
POTENTIALLY_AMBIGUOUS
flag is no longer needed. I wasn't sure whether to renumber all the subsequent flags, or just leave an empty placeholder, so I chose the latter.Finally, this fix uncovers new warnings in
java.base
andjava.desktop
, so these are now suppressed in the patch.Progress
Issues
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/12645/head:pull/12645
$ git checkout pull/12645
Update a local copy of the PR:
$ git checkout pull/12645
$ git pull https://git.openjdk.org/jdk pull/12645/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 12645
View PR using the GUI difftool:
$ git pr show -t 12645
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/12645.diff