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 DirectReturn check #513

Merged
merged 5 commits into from
Mar 30, 2023

Conversation

Stephan202
Copy link
Member

@Stephan202 Stephan202 commented Feb 26, 2023

This PR replaces #1. I decided to pick this task up because the topic was raised after @rickie's recent Amsterdam JUG presentation. CC @SimonBaars.

Suggested commit message:

Introduce `DirectReturn` check (#513)

@Stephan202 Stephan202 added this to the 0.9.0 milestone Feb 26, 2023
@Stephan202 Stephan202 requested a review from rickie February 26, 2023 16:09
@github-actions
Copy link

Looks good. All 26 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.DirectReturn 0 26

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class DirectReturn extends BugChecker implements BlockTreeMatcher {
Copy link
Member Author

Choose a reason for hiding this comment

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

Naming-wise, "something something unnecessary assignment" may be nicer. But (a) we're not performing a flow analysis that flags all unnecessary variable assignments and (b) a name that accurately describes the subset covered would be quite unwieldy. Unless I'm just not creative enough, of course 🙃. Suggestions welcome.

@Stephan202
Copy link
Member Author

Just realized: ideally we do not suggest inlining if (a) the variable assigned to is of a different type than the method's return type and (b) this difference may influence the return value. Prime example here is Mockito's nullary mock() method (see also #454). I'll come back to this PR to see how we can best cover this case.

@Stephan202 Stephan202 force-pushed the sschroevers/inline-variable-return-statements branch from b3c41d7 to 120e418 Compare March 11, 2023 20:02
@Stephan202
Copy link
Member Author

Rebased and added a commit to cover this case. Did make it Mockito-specifc, because attempts at generalization became too contrived.

@github-actions
Copy link

Looks good. All 51 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers 0 5
🎉tech.picnic.errorprone.bugpatterns.DirectReturn 0 34
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 12

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link
Member Author

@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 one more commit.

@@ -49,7 +48,7 @@
public final class MockitoMockClassReference extends BugChecker
Copy link
Member Author

Choose a reason for hiding this comment

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

CC @Badbond: I renamed/moved some logic in this class.

Comment on lines +64 to +68
public static Optional<MethodTree> findMethodExitedOnReturn(VisitorState state) {
return Optional.ofNullable(state.findEnclosing(LambdaExpressionTree.class, MethodTree.class))
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I decided to move this out because it handles quite a gotcha; better to not duplicate this logic.

@github-actions
Copy link

Looks good. All 51 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers 0 5
🎉tech.picnic.errorprone.bugpatterns.DirectReturn 0 34
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 12

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@werli werli self-requested a review March 30, 2023 12:27
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.

Flushing one comment I had. Will continue later today.

.addSourceLines(
"A.java",
"class A {",
" void negative1(String a, Integer b) {}",
Copy link
Member

Choose a reason for hiding this comment

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

Might be good to add a test where one is a subtype of the other? (Will add this in a commit)

Copy link
Member

@werli werli left a comment

Choose a reason for hiding this comment

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

The code itself and the added test cases were rather straightforward, nice work @Stephan202 👍

One more point about the exhaustiveness of the mockito exclusion, but I wouldn't expect more changes here as part of this PR. Hence I'm approving.

Comment on lines +119 to +122
* <p>Inlining is generally safe, but in rare cases the operation may have a functional impact.
* The sole case considered here is the inlining of a Mockito mock or spy construction without an
* explicit type. In such a case the type created depends on context, such as the method's return
* type.
Copy link
Member

Choose a reason for hiding this comment

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

Great to have added this exclusion. Are we sure the exclusion is exhaustive however? My guess is: probably never fully, but this is a best-effort solution that should handle the majority of EPS users.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, this is not exhaustive, unfortunately. I did play with some more generic code, but it became way more complex than I could justify for this feature. So in the end gave up on that idea.

Copy link
Member Author

@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 the reviews!

Comment on lines +119 to +122
* <p>Inlining is generally safe, but in rare cases the operation may have a functional impact.
* The sole case considered here is the inlining of a Mockito mock or spy construction without an
* explicit type. In such a case the type created depends on context, such as the method's return
* type.
Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, this is not exhaustive, unfortunately. I did play with some more generic code, but it became way more complex than I could justify for this feature. So in the end gave up on that idea.

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.

Really cool check 🚀.

Have some small comments and an extra test case or two. Let me know what you think 😄.

" return variable;",
" }",
"",
" Object salienSpyTypeVariableDeclaration() {",
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
" Object salienSpyTypeVariableDeclaration() {",
" Object salientSpyTypeVariableDeclaration() {",

" // BUG: Diagnostic contains:",
" variable = \"foo\";",
" return variable;",
" }",
Copy link
Member

Choose a reason for hiding this comment

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

I think this is also an interesting case to add:

  Supplier<String> redundantAssignmentInLambda() {
    return () -> {
      // BUG: Diagnostic contains:
      String variable = toString();
      return variable;
    };

@@ -46,4 +50,33 @@ public static ImmutableList<MethodTree> findMethods(CharSequence methodName, Vis
public static boolean methodExistsInEnclosingClass(CharSequence methodName, VisitorState state) {
return !findMethods(methodName, state).isEmpty();
}

/**
* Returns the {@link MethodTree} from which control flow would exit if there would be a {@code
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
* Returns the {@link MethodTree} from which control flow would exit if there would be a {@code
* Returns the {@link MethodTree} from which the control flow would exit if there would be a {@code

Sounds a bit better to me 🤔.

@github-actions
Copy link

Looks good. All 51 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers 0 5
🎉tech.picnic.errorprone.bugpatterns.DirectReturn 0 34
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 12

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@rickie
Copy link
Member

rickie commented Mar 30, 2023

Aii, misclicked and merged instead of rebased 😅.

@github-actions
Copy link

Looks good. All 51 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers 0 5
🎉tech.picnic.errorprone.bugpatterns.DirectReturn 0 34
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 12

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link
Member Author

@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; changes LGTM otherwise

@@ -46,4 +50,33 @@ public static ImmutableList<MethodTree> findMethods(CharSequence methodName, Vis
public static boolean methodExistsInEnclosingClass(CharSequence methodName, VisitorState state) {
return !findMethods(methodName, state).isEmpty();
}

/**
* Returns the {@link MethodTree} from which the control flow would exit if there would be a
Copy link
Member Author

Choose a reason for hiding this comment

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

I think we should omit "the" here. (Cause this is not about a "definite" control flow.)

" }",
" }",
"",
" Supplier<String> redundantAssignmentInLambda() {",
Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
" Supplier<String> redundantAssignmentInLambda() {",
" Supplier<String> redundantAssignmentInsideLambda() {",

Also matches the method above.

@rickie rickie force-pushed the sschroevers/inline-variable-return-statements branch from 10ddbf3 to 2507a85 Compare March 30, 2023 18:42
@rickie
Copy link
Member

rickie commented Mar 30, 2023

Applied and rebased. Will merge once 🟢, thanks for taking a look so quickly.

@Stephan202
Copy link
Member Author

Let's gooooo 🚀

@github-actions
Copy link

Looks good. All 51 mutations in this change were killed.

class surviving killed
🎉tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers 0 5
🎉tech.picnic.errorprone.bugpatterns.DirectReturn 0 34
🎉tech.picnic.errorprone.bugpatterns.MockitoMockClassReference 0 12

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@rickie rickie merged commit 73cf28e into master Mar 30, 2023
@rickie rickie deleted the sschroevers/inline-variable-return-statements branch March 30, 2023 18:51
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.

3 participants