Skip to content
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

Merged
merged 5 commits into from
Nov 4, 2022
Merged

Conversation

Ptijohn
Copy link
Contributor

@Ptijohn Ptijohn commented Oct 29, 2022

Creating as draft as I'm not super comfortable in the usage of Expressions 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 🤔

import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;

/** A {@link BugChecker} that aligns usages of T.class::isInstance. */
Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe {@code ...}?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦

Comment on lines 58 to 72
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()));
Copy link
Contributor Author

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 :/

Copy link
Member

@Stephan202 Stephan202 left a 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 :)

Comment on lines 49 to 51
} else if (METHOD_INVOCATION == tree.getBody().getKind()) {
return treatMethodInvocation(tree, (MethodInvocationTree) tree.getBody(), state);
}
Copy link
Member

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 :)

Copy link
Contributor Author

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 ;)

Copy link
Member

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!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep exactly :)

Copy link
Contributor Author

@Ptijohn Ptijohn Oct 29, 2022

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.

Copy link
Contributor Author

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!

Copy link
Contributor Author

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!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, PTAL :)

Copy link
Member

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 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks :D

@Ptijohn Ptijohn force-pushed the bdiederichs/rewrite-isinstance branch from 005de9d to 23fcdd6 Compare October 29, 2022 13:40
@Ptijohn Ptijohn marked this pull request as ready for review October 29, 2022 13:40
Comment on lines 48 to 49
return SuggestedFixes.compilesWithFix(
fix, state, ImmutableList.of(), /* onlyInSameCompilationUnit= */ true)
Copy link
Member

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.)

Copy link
Contributor Author

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 👌

Copy link
Contributor Author

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.

@rickie rickie self-requested a review October 31, 2022 07:16
@Ptijohn Ptijohn force-pushed the bdiederichs/rewrite-isinstance branch from 4b276c9 to b223227 Compare October 31, 2022 07:38
@rickie rickie force-pushed the bdiederichs/rewrite-isinstance branch from b223227 to 6c223b3 Compare November 3, 2022 20:42
Copy link
Member

@rickie rickie left a 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. */
Copy link
Member

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.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)`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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;
Copy link
Member

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)
Copy link
Member

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).

Copy link
Member

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 {
Copy link
Member

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() {",
Copy link
Member

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.

@rickie rickie added this to the 0.6.0 milestone Nov 3, 2022
Copy link
Member

@Stephan202 Stephan202 left a 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()) {
Copy link
Member

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 👀.

Comment on lines 43 to 50
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");
}
Copy link
Member

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)`.
Copy link
Member

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 {
Copy link
Member

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");
Copy link
Member

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);",
Copy link
Member

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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. :)

@rickie
Copy link
Member

rickie commented Nov 3, 2022

Will start resolving the conflicts 😄.
Okay we almost had the exact same changes so basically nothing is left haha.

For me the only things left are the naming discussion and the tag discussion.

@Stephan202
Copy link
Member

Okay we almost had the exact same changes so basically nothing is left haha.

GMTA!

(Will have a look at the open points in a bit.)

Copy link
Member

@Stephan202 Stephan202 left a 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)
Copy link
Member

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".)

@Ptijohn
Copy link
Contributor Author

Ptijohn commented Nov 4, 2022

Wow thank you both for the reviews and extra commits @rickie and @Stephan202 💪
I still need to go fully through them, but I think there's a lot of learning for me there :D
If you want to merge before that, don't hesitate, I'm quite full today so I don't know when I'll to it 🤔

Copy link
Contributor Author

@Ptijohn Ptijohn left a 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! 💪

@rickie rickie changed the title Align usages of Class::isInstance Introduce IsInstanceLambdaUsage check Nov 4, 2022
@rickie rickie merged commit 42e632e into master Nov 4, 2022
@rickie rickie deleted the bdiederichs/rewrite-isinstance branch November 4, 2022 10:47
@rickie
Copy link
Member

rickie commented Nov 4, 2022

That's the spirit 🚀 😄 @Ptijohn !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

3 participants