-
Notifications
You must be signed in to change notification settings - Fork 39
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
Introduce IsInstanceLambdaUsage
check
#323
Conversation
import com.sun.source.tree.MemberSelectTree; | ||
import com.sun.source.tree.MethodInvocationTree; | ||
|
||
/** A {@link BugChecker} that aligns usages of T.class::isInstance. */ |
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't @link
this one 🤔 (AFAICS)
And I don't want to do @link Class#isInstance(Object)
as this is precisely one of the ones that we're getting rid of.
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.
Maybe {@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.
🤦
if (MEMBER_SELECT != methodInvocationTree.getMethodSelect().getKind() | ||
|| !((MemberSelectTree) methodInvocationTree.getMethodSelect()) | ||
.getIdentifier() | ||
.contentEquals("isInstance")) { | ||
return Description.NO_MATCH; | ||
} | ||
MemberSelectTree methodSelect = (MemberSelectTree) methodInvocationTree.getMethodSelect(); | ||
if (MEMBER_SELECT != methodSelect.getExpression().getKind() | ||
|| IDENTIFIER | ||
!= ((MemberSelectTree) methodSelect.getExpression()).getExpression().getKind()) { | ||
return Description.NO_MATCH; | ||
} | ||
IdentifierTree identifierTree = | ||
(IdentifierTree) ((MemberSelectTree) methodSelect.getExpression()).getExpression(); | ||
return constructDescription(tree, state, constructFix(tree, identifierTree.getName())); |
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.
That's the part I'm quite unhappy about... Looks super specific :/
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.
Tnx for filing the PR we discussed! I'm currently reviewing something else; will have a look at this one later :)
} else if (METHOD_INVOCATION == tree.getBody().getKind()) { | ||
return treatMethodInvocation(tree, (MethodInvocationTree) tree.getBody(), state); | ||
} |
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.
IIUC the i -> Integer.class.isInstance(i)
case will be flagged and fixed by MethodReferenceUsage, so it seems we can simplify this check by dropping support for that 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.
Oh, that would be awesome :D
Let me try to extend the test coverage of MethodReferenceUsage then ;)
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.
If there is no suitable/equivalent test yet (didn't check): cool!
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.
Yep exactly :)
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.
Added this test to MethodReferenceUsage
:
" // BUG: Diagnostic contains:",
" Stream.of(1).filter(v -> Integer.class.isInstance(v));",
And it was actually not flagged 🤔
I'm wondering if it's because of the .class.isInstance
🤔
But then it indeed feels like it should be MethodReferenceUsage
that should be extended there.
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.
Hahaha, yeah I actually got inspired from this code, and got slightly afraid 😅
I'll leave that out for now.
That being said, I'll check in the branch 👌
And then adapt this code.
Thanks for the quick feedback!
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 covered in the new branch, will drop an XXX on MethodReferenceUsage
and adapt the rest!
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.
Done, PTAL :)
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.
Tnx! On the review list it goes 😄
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 :D
005de9d
to
23fcdd6
Compare
return SuggestedFixes.compilesWithFix( | ||
fix, state, ImmutableList.of(), /* onlyInSameCompilationUnit= */ true) |
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.
Aye, compilesWithFix
is very convenient, but also very expensive. Unfortunately we'll have to find a way to avoid this. @Ptijohn which issue did you hit without this?
(In case you're wondering: indeed the MethodReferenceUsage
check also relies on compilesWithFix
, and this is why it hasn't been enabled yet for the internal code base. It's the primary reason for the work on this branch.)
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.
To be honest? It's mainly that I'm discovering the tool 😅 (and thus I didn't know about the limitations of this one)
I will dig to see what's best 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.
Oh so IIUC you just exact that we can just drop it right?
Will push it :)
I indeed thought for a sec that the compilexWithFix
was a mandatory step in the validation of such a mechanism, but makes sense that it's not the case was "simpler" changes like this one.
4b276c9
to
b223227
Compare
b223227
to
6c223b3
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.
Added a commit.
Left some questions and considerations :).
Cool check @Ptijohn 🚀 !
Suggested commit message (name subject to change):
Introduce `IsInstanceLambdaUsage` check (#323)
import com.sun.source.tree.MemberSelectTree; | ||
import com.sun.source.tree.MethodInvocationTree; | ||
|
||
/** A {@link BugChecker} that aligns usages of T.class::isInstance. */ |
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.
Maybe {@code ...}
?
" Flux.just(1).filter(Integer.class::isInstance);", | ||
" }", | ||
"}") | ||
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); |
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.
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); | |
.doTest(TestMode.TEXT_MATCH); |
We usually remove the qualifier here :).
@@ -106,6 +106,7 @@ private static Optional<SuggestedFix.Builder> constructMethodRef( | |||
} | |||
|
|||
// XXX: Replace nested `Optional` usage. | |||
// XXX: Cover for deeper method invocation like `T.class.isInstance(i)`. |
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.
// XXX: Cover for deeper method invocation like `T.class.isInstance(i)`. | |
// XXX: Cover for deeper method invocations like `T.class.isInstance(i)`. |
Right? 👀
return constructDescription( | ||
tree, constructFix(tree, ((InstanceOfTree) tree.getBody()).getType())); | ||
} | ||
return Description.NO_MATCH; |
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.
Two things:
(1) I think we can simplify this a bit by inlining these methods.
(2) We usually first handle the return Description.NO_MATCH;
case, so I'd suggest to invert this :).
link = BUG_PATTERNS_BASE_URL + "IsInstanceUsage", | ||
linkType = CUSTOM, | ||
severity = SUGGESTION, | ||
tags = STYLE) |
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.
Although I agree with STYLE
this could also be a SIMPLIFICATION
, WDYT?
Edit: One thing to consider is that it could make the Javadoc a bit more strong if we use simplification. Using "aligns" in the Javadoc would mean this is a bit of an opinionated check, while this is IMO an actual improvement (which in this case would mean a simplification).
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 see that MethodReferenceUsage
does also use STYLE
, I think in this case SIMPLIFICATION
would work too. (In the general case method references may be longer, and are perhaps not always "simpler".)
linkType = CUSTOM, | ||
severity = SUGGESTION, | ||
tags = STYLE) | ||
public final class IsInstanceUsage extends BugChecker implements LambdaExpressionTreeMatcher { |
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.
As this is quite specific to lambda's, we could update the name to reflect that. Maybe something like LambdaIsInstanceUage
or IsInstanceLambdaUsage
?
"import reactor.core.publisher.Flux;", | ||
"", | ||
"class A {", | ||
" void m() {", |
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.
It'd be nice to also add a non-Reactor related example.
Will push something.
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.
@rickie since you didn't push yet, let me push what I had (not flushed earlier because I had to leave the train).
|
||
@Override | ||
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) { | ||
if (LAMBDA_EXPRESSION == tree.getKind() && INSTANCE_OF == tree.getBody().getKind()) { |
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.
We generally do the NO_MATCH
case first, have the enum values on the RHS, and import Kind
. (Though I see we're not fully consistent on the latter 👀.
private Description constructDescription( | ||
LambdaExpressionTree tree, SuggestedFix.Builder fixBuilder) { | ||
return describeMatch(tree, fixBuilder.build()); | ||
} | ||
|
||
private static SuggestedFix.Builder constructFix(LambdaExpressionTree tree, Object target) { | ||
return SuggestedFix.builder().replace(tree, target + ".class::isInstance"); | ||
} |
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.
These can be inlined without impacting readability, IMHO. Will push a proposal.
@@ -106,6 +106,7 @@ private static Optional<SuggestedFix.Builder> constructMethodRef( | |||
} | |||
|
|||
// XXX: Replace nested `Optional` usage. | |||
// XXX: Cover for deeper method invocation like `T.class.isInstance(i)`. |
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.
Without diving into the root cause I'm not sure what the best place for this comment is; let's move it to the top of the class.
linkType = CUSTOM, | ||
severity = SUGGESTION, | ||
tags = STYLE) | ||
public final class IsInstanceUsage extends BugChecker implements LambdaExpressionTreeMatcher { |
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 MethodReferenceUsage
javadoc states:
A {@link BugChecker} that flags lambda expressions that can be replaced with method references.
By that logic we could collapse these two checks. But since the other one isn't active yet, let's keep his one separate and revisit this option later. Will add a comment :)
} | ||
|
||
private static SuggestedFix.Builder constructFix(LambdaExpressionTree tree, Object target) { | ||
return SuggestedFix.builder().replace(tree, target + ".class::isInstance"); |
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.
We can avoid the builder if we use SuggestedFix#replace
.
"class A {", | ||
" void m() {", | ||
" // BUG: Diagnostic contains:", | ||
" Flux.just(1).filter(i -> i instanceof Integer);", |
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.
Given the simplicity of the check I guess a single case suffices. Propose that we use a JDK API such as Stream
for this.
return describeMatch(tree, fixBuilder.build()); | ||
} | ||
|
||
private static SuggestedFix.Builder constructFix(LambdaExpressionTree tree, Object target) { |
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.
private static SuggestedFix.Builder constructFix(LambdaExpressionTree tree, Object target) { | |
private static SuggestedFix.Builder constructFix(LambdaExpressionTree tree, Tree target) { |
... and if we do that, we get an error that Tree#toString shouldn't be used
. For this we have SourceCode#treeToString
. :)
Will start resolving the conflicts 😄. For me the only things left are the naming discussion and the tag discussion. |
GMTA! (Will have a look at the open points in a bit.) |
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.
Added a commit and tweaked the suggested commit message :)
link = BUG_PATTERNS_BASE_URL + "IsInstanceUsage", | ||
linkType = CUSTOM, | ||
severity = SUGGESTION, | ||
tags = STYLE) |
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 see that MethodReferenceUsage
does also use STYLE
, I think in this case SIMPLIFICATION
would work too. (In the general case method references may be longer, and are perhaps not always "simpler".)
Wow thank you both for the reviews and extra commits @rickie and @Stephan202 💪 |
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.
Alright, can't approve myself, but got through the feedback, I learned a lot for next time :D
Thanks! 💪
IsInstanceLambdaUsage
check
That's the spirit 🚀 😄 @Ptijohn ! |
Creating as draft as I'm not super comfortable in the usage of
Expression
s and all the nitty gritty details of that...I'm not happy in the way I'm actually matching the second case of
isInstance(Object)
:/But at the same time I'm not sure I see a way to do that in a recursive way 🤔