-
Notifications
You must be signed in to change notification settings - Fork 134
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
[improvement] Ensure Optional#orElse argument is not method invocation #655
Conversation
I think compile time constant is too aggressive. We should just enforce it’s not an expression (a variable further up the function is totally appropriate as an argument to orElse). |
link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", | ||
linkType = BugPattern.LinkType.CUSTOM, | ||
severity = SeverityLevel.ERROR, | ||
summary = "Allow passing only compile-time constants to Optional#orElse.") |
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 you update this as well?
|
||
@AutoService(BugChecker.class) | ||
@BugPattern( | ||
name = "OptionalOrElseConstant", |
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 think this is a little stale, perhaps OptionalOrElseMethodInvocation
, OptionalOrElseValue
?
.setMessage("Optional#orElse uses a non-constant expression") | ||
.addFix(SuggestedFix.builder() | ||
.postfixWith(tree.getMethodSelect(), "Get") | ||
.prefixWith(orElseArg, "() -> ") |
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.
Could we pass a method reference instead of allocating a new lamba? i.e. .orElseGet(this::foo)
or orElseGet(foo)
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's not so easy to detect these things, and if we could, they should probably be done as their own check.
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.
link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", | ||
linkType = BugPattern.LinkType.CUSTOM, | ||
severity = SeverityLevel.ERROR, | ||
summary = "Ensure Optional#orElse argument does not invoke a ny methods.") |
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.
s/a ny/any
It's fairly obvious in this case, but it may be helpful to spell out exactly why orElseGet is better in the summary
Very excited about this check. Thanks for implementing the suggested fix, they've been invaluable for a certain large internal project! |
Addressed all comments, should be good to go |
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.
Could we add a test that shows we prevent foo + bar
as an argument to orElse?
Probably worth mentioning this on the README with the other error-prone checks too pls! https://github.com/palantir/gradle-baseline#baseline-error-prone-checks |
" Optional.of(\"hello\").orElse(\"constant\");", | ||
" Optional.of(\"hello\").orElse(compileTimeConstant);", | ||
" String string = f();", | ||
" Optional.of(\"hello\").orElse(string);", |
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.
this is missing test coverage for Optional.of(foo).orElseGet(() -> bar);
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 sure what you mean, you want this check to revert it to orElse(bar)
?
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 don't see a test verifying that Optional.of(foo).orElseGet(() -> bar)
is an acceptable thing to write
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.
sorry, I mean Optional.of(foo).orElseGet(() -> f());
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.
Ah, I've done that now
"import java.util.Optional;", | ||
"class Test {", | ||
" String f() { return \"hello\"; }", | ||
" // BUG: Diagnostic contains: non-constant expression", |
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.
what does "non-constant" mean? suspect that's not quite the right word. what you mean here is "method invocation"
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.
Well it is, but then as Mark says this doesn't cover operator expressions. Not sure what the right word is?
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 I understand the Matchers.contains(ExpressionTree.class, MethodMatchers.anyMethod())
thing correctly, then it errors iff a method is called anywhere inside the orElse(...)
. A reasonable description would be, for instance, Expression passed to Optional#orElse must not invoke a method
, or positively (not sure what the right one is), Expression passed to Optional#orElse invokes a method
.
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.
(or contains a method invocation
, if you prefer). To double-check: foo.orElse(bar + baz)
is allowed, ja?
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.
Well, it is currently but should it be allowed? Are operators ever sufficiently expensive for us to care about? If yes, then we need to think again about our matcher. Perhaps compileTimeConstant OR identifier.
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.
String concatenation using operators should be required to execute in a lambda. I don't mind the lambda allocation for math because the optional is already boxing numerical results with similar (avoidable) overhead to creating a lambda.
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.
.. then you'll likely see
String bar = boom + baz;
return foo.orElse(bar); // can't concat in here.
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 don't really mind the exact version of the check, as long as you make the text reflect the check. (that's what I was commenting on initially, sorry if this was unclear.)
Is this good to go now? |
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.
May want to consider in a separate PR the opposite of this — if you’re calling orElseGet when you could be calling orElse it’d be good to warn (IntelliJ has that as a refactor suggestion), but not worth blocking on
Didn't update the README 😢 |
@gatesn I think that this check may be overly aggressive since accessing any field on an immutable is considered a method invocation. In general, I feel like this check has a very large number of false positives so we may want to lower the severity to warn or reconsider the check entirely. |
@ferozco can you / @carterkozak enumerate the false positives in case we want to pick this back up? There are a few I disagree with, for instance I'm not convinced there's a difference in accessing a property of an immutable as A viable opposition would be for example |
It might be that we need an automated refactor of method calls into suppliers in order to make this work. Seems reasonable that you'd |
If that's the case, then I imagine it shouldn't be too hard to implement the logic as either in the check itself, or as part of a refaster rule |
So, we can now auto-apply error-prone fixes: https://github.com/palantir/gradle-baseline/pull/660/files Is this sufficient? Or do we want to block on having the |
Add a line for an error-prone check added in palantir#655, which is missing from the README.
Add a line for an error-prone check added in #655, which is missing from the README.
Before this PR
Run into
Optional#orElse
code three times now that was intended to be lazy.After this PR
Argument to
Optional#orElse
must not include a method invocation. Otherwise a fix is suggested to convert toOptional#orElseGet(Supplier)