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

Improve Optional#orElse{,Get} support #1283

Merged
merged 5 commits into from
Sep 3, 2024

Conversation

Stephan202
Copy link
Member

@Stephan202 Stephan202 commented Aug 11, 2024

Suggested commit message:

Improve `Optional#orElse{,Get}` support (#1283)

Summary of changes:
- Consolidate the `OptionalOrElseGet` Refaster rule and the 
  `OptionalOrElse` bug checker into the latter, keeping the best of
  both.
- Rename the `OptionalOrElse` bug checker to `OptionalOrElseGet` to
  avoid confusion.
- Replace the `IsLikelyTrivialComputation` matcher with the inverse
  `RequiresComputation` variant.
- Introduce an `OptionalOrElse` Refaster rule that simplifies
  expressions in the "opposite direction".

@Stephan202 Stephan202 added this to the 0.19.0 milestone Aug 11, 2024
Copy link

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.RequiresComputation 0 12
🎉tech.picnic.errorprone.bugpatterns.OptionalOrElseGet 0 6

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.

Some context.

Comment on lines -94 to -104
/**
* Tells whether the given expression contains anything other than a literal or a (possibly
* dereferenced) variable or constant.
*/
private static boolean requiresComputation(ExpressionTree tree) {
return !(tree instanceof IdentifierTree
|| tree instanceof LiteralTree
|| (tree instanceof MemberSelectTree memberSelect
&& !requiresComputation(memberSelect.getExpression()))
|| ASTHelpers.constValue(tree) != null);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Instead of two not-quite-complimentary implementations, all logic now lives in the RequiresComputation matcher.

Comment on lines +37 to +39
// XXX: This rule may introduce a compilation error: the `value` expression may reference a
// non-effectively final variable, which is not allowed in the replacement lambda expression.
// Review whether a `@Matcher` can be used to avoid this.
Copy link
Member Author

Choose a reason for hiding this comment

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

This observation was only moved from the Refaster rule to here. Fixing it is out of scope.

Comment on lines 257 to 274
/**
* Prefer {@link Optional#orElseGet(Supplier)} over {@link Optional#orElse(Object)} if the
* fallback value is not the result of a trivial computation.
* Prefer {@link Optional#orElse(Object)} over {@link Optional#orElseGet(Supplier)} if the
* fallback value does not require non-trivial computation.
*/
// XXX: This rule may introduce a compilation error: the `value` expression may reference a
// non-effectively final variable, which is not allowed in the replacement lambda expression.
// Review whether a `@Matcher` can be used to avoid this.
// XXX: Once `MethodReferenceUsage` is "production ready", replace
// `@NotMatches(IsLikelyTrivialComputation.class)` with `@Matches(RequiresComputation.class)` (and
// reimplement the matcher accordingly).
static final class OptionalOrElseGet<T> {
// XXX: This rule is the counterpart to the `OptionalOrElseGet` bug checker. Once the
// `MethodReferenceUsage` bug checker is "production ready", that bug checker may similarly be
// replaced with a Refaster rule.
static final class OptionalOrElse<T> {
@BeforeTemplate
T before(Optional<T> optional, @NotMatches(IsLikelyTrivialComputation.class) T value) {
return optional.orElse(value);
T before(Optional<T> optional, @NotMatches(RequiresComputation.class) T value) {
return optional.orElseGet(() -> value);
}

@AfterTemplate
T after(Optional<T> optional, T value) {
return optional.orElseGet(() -> value);
return optional.orElse(value);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Rule replaced: the old definition is absorbed into the bug checker, while the new rule implements complementary logic.

Comment on lines 373 to 378
@SuppressWarnings({
"LexicographicalAnnotationAttributeListing" /* `key-*` entry must remain last. */,
"NestedOptionals" /* This violation will be rewritten. */,
"OptionalOrElse" /* Here `optional2` is a stand-in for expressions that may require computation. */,
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
})
Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this is getting messy. Maybe those key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict entries should start with zzz- or something.

@@ -27,6 +28,7 @@ void identification() {
" optional.orElse(string);",
" optional.orElse(this.string);",
" optional.orElse(Refaster.anyOf(\"constant\", \"another\"));",
" Optional.<Supplier<String>>empty().orElse(() -> \"constant\");",
Copy link
Member Author

Choose a reason for hiding this comment

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

New test for the case that triggered this PR, observed with Picnic-internal code.

Copy link
Member Author

Choose a reason for hiding this comment

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

To be clearer: previously the bug checker would not see the lambda expression as a trivial computation, while the Refaster rule would. In this context the latter was right.

Copy link

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.RequiresComputation 0 12
🎉tech.picnic.errorprone.bugpatterns.OptionalOrElseGet 0 6

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

1 similar comment
Copy link

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.RequiresComputation 0 12
🎉tech.picnic.errorprone.bugpatterns.OptionalOrElseGet 0 6

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.

More context.

Comment on lines +54 to +56
" OutputStream negative9() {",
" return System.out;",
Copy link
Member Author

@Stephan202 Stephan202 Aug 11, 2024

Choose a reason for hiding this comment

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

This new test is to cover existing code that was previously untested. I filed hcoles/pitest#1341 Doh.

" }",
"",
" int negative6() {",
" return 1 * 2;",
Copy link
Member Author

Choose a reason for hiding this comment

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

Previously the bug checker would see this as a trivial computation, but not the Refaster rule. The former was right.

@@ -27,6 +28,7 @@ void identification() {
" optional.orElse(string);",
" optional.orElse(this.string);",
" optional.orElse(Refaster.anyOf(\"constant\", \"another\"));",
" Optional.<Supplier<String>>empty().orElse(() -> \"constant\");",
Copy link
Member Author

Choose a reason for hiding this comment

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

To be clearer: previously the bug checker would not see the lambda expression as a trivial computation, while the Refaster rule would. In this context the latter was right.

Copy link

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.RequiresComputation 0 12
🎉tech.picnic.errorprone.bugpatterns.OptionalOrElseGet 0 6

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

Copy link

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.RequiresComputation 0 10
🎉tech.picnic.errorprone.bugpatterns.OptionalOrElseGet 0 6

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

Copy link
Contributor

@mohamedsamehsalah mohamedsamehsalah left a comment

Choose a reason for hiding this comment

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

🚀 🚀

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.

LGTM! Nice improvements.

Summary of changes:
- Consolidate the `OptionalOrElseGet` Refaster rule and the
  `OptionalOrElse` bug checker into the latter, keeping the best of
  both.
- Rename the `OptionalOrElse` bug checker to `OptionalOrElseGet` to
  avoid confusion.
- Replace the `IsLikelyTrivialComputation` matcher with the inverse
  `RequiresComputation` variant.
- Introduce an `OptionalOrElse` Refaster rule that simplifies
  expressions in the "opposite direction".
@rickie rickie force-pushed the sschroevers/optional-or-else-get-improvements branch from af624dc to 69d9d62 Compare September 3, 2024 13:35
Copy link

sonarqubecloud bot commented Sep 3, 2024

Copy link

github-actions bot commented Sep 3, 2024

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.RequiresComputation 0 10
🎉tech.picnic.errorprone.bugpatterns.OptionalOrElseGet 0 6

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

@rickie rickie merged commit c679a3f into master Sep 3, 2024
16 checks passed
@rickie rickie deleted the sschroevers/optional-or-else-get-improvements branch September 3, 2024 14:00
@pzygielo
Copy link

pzygielo commented Nov 1, 2024

After upgrading 0.18.0 -> 0.19.0, with error-prone 2.35.1, Java Temurin-21.0.5+11, Apache Maven 3.9.9, I'm getting:

[ERROR] error: An unhandled exception was thrown by the Error Prone static analysis plugin.

which seems to be related to this change.

@Stephan202
Copy link
Member Author

@pzygielo interesting. Do you have a stack trace/example code/reproducer? Happy to take a look!

@pzygielo
Copy link

pzygielo commented Nov 1, 2024

Unfortunately stack trace dumped by maven contains only maven calls, I can't see anything from javac there.

I'm in the middle of preparation of minimal reproducer from the bigger project I see this in. Will share of course.

@Stephan202
Copy link
Member Author

Sometimes passing -e and/or -X to Maven will help. Good luck! 🙏

@pzygielo
Copy link

pzygielo commented Nov 1, 2024

error: An unhandled exception was thrown by the Error Prone static analysis plugin.
     Please report this at https://github.com/google/error-prone/issues/new and include the following:
  
     error-prone version: 2.35.1
     BugPattern: (see stack trace)
     Stack Trace:
     java.lang.NoClassDefFoundError: tech/picnic/errorprone/refaster/matchers/RequiresComputation
  	at tech.picnic.errorprone.bugpatterns.OptionalOrElseGet.<clinit>(OptionalOrElseGet.java:54)
  	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
...

@pzygielo
Copy link

pzygielo commented Nov 1, 2024

Perhaps tech.picnic.error-prone-support:refaster-support dependency shall be promoted from provided scope.

@Stephan202
Copy link
Member Author

Perhaps tech.picnic.error-prone-support:refaster-support dependency shall be promoted from provided scope.

Exactly right! I filed #1387 with a fix. Thanks for the super clear reproducer @pzygielo!

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.

4 participants