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
Fix capture conversion for method references #2321
Fix capture conversion for method references #2321
Conversation
@coehlrich : thanks for the patch. I see multiple commits there - are they needed, or just represent work in progress?If later, please squash all changes to one commit, rebase it on latest master state & force push to github. @srikanth-sankaran : I believe parts of changed code were written long time ago by you. Is this something you could review? |
4bb4f51
to
55ae901
Compare
I am woefully rusty on that code. @stephan-herrmann would be the best person to review this. Stephan, thanks for taking a look. |
If I'm to review this I will have to ask some nasty questions about how the change is backed by JLS. Should I? |
Asking questions about how the change is backed by JLS is very valid line of enquiry and is not nasty is my opinion :) |
In JLS §15.13.1:
This can be interpreted as with: public void reference(Object o) {}
interface A {
void run(String s);
} treat the reference in this: A a = this::reference; as if it were the invocation in this for the purpose of looking up which method to use: String s = "";
this.reference(s); which makes sense since later on With capture conversion if it applied only to the type arguments of the functional interface: public void test() {
B<?> b1 = new B<>();
B<?> b2 = new B<>();
A<B<?>> a = this::error; // This makes both B<?> parameters use the same capture variable
a.run(b1, b2); // which can be run as if it were `run(B<?> a, B<?> b)`
this.error(b1, b2); // This makes both B<?> parameters use seperate capture variables which because of the type variable T makes it not compile
}
public <T> void error(B<T> a, B<T> b) {}
interface A<T> {
void run(T t1, T t2);
}
class B<T> {} |
Let's forget about "nasty" for now 😄, as I'm gradually trying to understand the different ingredients to this issue. Do we agree that capturing So, with this original example:
inference instantiates In the debugger I can see that both occurrences of The interesting part happens after inferring With only the change in ReferenceExpression descriptorParametersAsArgumentExpressions() computes the argument type to I read this as saying that inference succeeds to unify the two arguments to So far all looks good, but some questions remain:
|
Regarding Regarding the assignment |
Another point in favor of accepting: ecj already accepts the same program when rewritten using lambda instead of method reference:
This underlines that indeed treatment for ReferenceExpression is to blame specifically, not our general approach to wildcard captures. |
Let's resolve those questions via #2419: a quick-and-dirty experiment for that issue indicates that accepting might actually be wrong, and a tiny fix could make ecj report the same errors that javac reports. |
The "interning" was why I created #2351. For question 2
is the reason for the change in |
Thanks, I hadn't seen that one. Let's see if #2419 can be folded into that one.
I still don't understand, how does the reference to §15.13.1 relate to the way how we apply capture? OTOH, I can see that bug 492939 followed in the foot steps of bug 432759, and if that one is due for revisiting any way, all these individual fixes might now be converging to a cleaner solution. |
The code modified in |
Please do help me understand your intention:
I can see that you thoroughly studied the issue and I don't want to block the merge longer than necessary. I just want to verify each change either against a test that would otherwise fail (not the case here) or against some other goal which I can understand 😄 |
JLS section §15.13.1 includes "In the first search, the method reference is treated as if it were an invocation with argument expressions of types P1, ..., Pn;" and is the same section as the one that is relevant for the change in I haven't managed to find any test cases for |
Seems like the The
In bug 492939 (which is the bug report where the change to
|
55ae901
to
6a5e896
Compare
I undid the changes 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.
Thanks @coehlrich with part of the proposed change reverted I think we are on firm grounds. The change in ReferenceExpression
looks good to me.
Still I appreciate that you looked also at the related code location in ConstraintExpressionFormula
. Due to the connection between bug 492939 and bug 432759 it is still possible your originally proposed change is good, too. Just it's harder to tell without a confirming test case. As type inference is good for unexpected butterfly effects, I prefer to avoid changes with unclear benefit. Perhaps we can conclude this part via #2351.
Also @mpalat or @jarthana should approve this after some stress tests.
Do we have any results from stress tests?
They may have waited for the dust to settle on this. @mpalat and @jarthana could you approve this after some testing please ?? TIA |
@mpalat/@jarthana is one of you looking into the request for stress testing this fix ?? Thanks in advance. |
What it does
Fixes #2302 by applying capture conversion at the parameter level.
If capture conversion were to apply only to type arguments then
would be invalid with
void method(A<B<?>> a);
due to capture conversion making the wildcard a capture.would be valid with
<T> void method(A<T> a, A<T> b)
which should be invalid due to the interface not requiring the type arguments ofa
andb
to be the same.and about "fanned out"
would be valid with
<T> void method(A<T> a, A<T> b)
andC<A<?>> c = this::method
which should be invalid due toc
not requiring the type arguments ofa
andb
to be the same.How to test
Attempt to compile:
Fails with
The type TestRecord does not define error(TestRecord.A<TestRecord.B<capture#1-of ?>>) that is applicable here
without this change but successfully compiles with this change.Author checklist
Additional information
While working on this I also found that
createCapturedWildcard
reuses the same capture binding for each wildcard of a method reference due to thecontextType
being the containing class along withstart
andend
being the start and end of the method reference so the same capture binding will be reused for each wildcard in the parameters of the method reference.