-
Notifications
You must be signed in to change notification settings - Fork 135
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
Stream.mapMulti(Optional::ifPresent)
is more efficient than Stream.flatMap(Optional::stream)
#2996
Conversation
Generate changelog in
|
is more efficient than
Stream.flatMap(Optional::stream)Stream.mapMulti(Optional::ifPresent)
is more efficient than Stream.flatMap(Optional::stream)
baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/StreamFlatMapOptional.java
Outdated
Show resolved
Hide resolved
419c227
to
e07899f
Compare
e07899f
to
09c5689
Compare
baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/StreamFlatMapOptional.java
Outdated
Show resolved
Hide resolved
return fix(tree, state, receiver, receiver); | ||
} | ||
|
||
if (STREAM_MAP_GET.matches(tree, 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.
Unclear if this is the best fit for this check, vs a separate error-prone check (if it's uncommon outside of the previous automatic fix, we could even run it via excavator without enforcing every compilation).
Certainly easiest to roll out here, and given the current state of the world it probably makes the most sense
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.
yeah, this should be much less common after automated roll out, though folks may still write .filter(Optional::isPresent).map(Optional::get)
so this would keep that cleaned up.
boolean shouldQualifyType = | ||
args(elementType).findAny().isPresent() || receiverCount > 1 || (receiverCount == 0 && argCount > 0); |
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.
Worth running this on a couple larger internal projects to verify the results compile successfully. If there are edge cases we can't easily isolate, I think it should be safe to over-qualify.
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.
Yeah, this logic isn't quite right yet. I've tested out on a few local repos and there are some places were I'm not generating qualified types that need them.
I'm trying to avoid adding unnecessary qualifying type args, but there are at least a few cases I haven't quite nailed down that need qualification (e.g. some stream chains extracting layers of optionals).
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'm trying to avoid adding unnecessary qualifying type args, but there are at least a few cases I haven't quite nailed down that need qualification (e.g. some stream chains extracting layers of optionals).
Thinking about this a bit more, I think it's actually a good fit for the incredibly expensive SuggestedFixes.compilesWithFix
utility, because in the failing case, we can qualify the method reference, and needn't pay the cost again (unlike LambdaMethodReference
which used compilesWithFix
in the matching component, meaning it must run the test in every subsequent compilation).
baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/StreamFlatMapOptional.java
Outdated
Show resolved
Hide resolved
if (SuggestedFixes.compilesWithFix(fix, state)) { | ||
consumer.accept(fix); |
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 we should guarantee that we propose something after checking compilesWithFix
, this could make future compilations expensive if compilation doesn't succeed on any of the options
() -> getQualifiedSuggestedFix(tree, state, expressionTree, receiverType.getTypeArguments()), | ||
() -> getQualifiedSuggestedFix(tree, state, expressionTree, List.of(elementType))) |
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 these produce the same result in the tests, as long as one of the lines is present, everything seems to pass. Are there any known cases where we need both?
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 we can remove the latter
ExpressionTree methodSelect = tree.getMethodSelect(); | ||
ExpressionTree receiver = ASTHelpers.getReceiver(methodSelect); |
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.
ASTHelpers.getReceiver
follows MethodInvocationTree.getMethodSelect()
, we can pass tree
directly for the same result
ExpressionTree methodSelect = tree.getMethodSelect(); | |
ExpressionTree receiver = ASTHelpers.getReceiver(methodSelect); | |
ExpressionTree receiver = ASTHelpers.getReceiver(tree); |
} | ||
|
||
if (STREAM_MAP_GET.matches(tree, state)) { | ||
ExpressionTree mapTree = ASTHelpers.getReceiver(tree.getMethodSelect()); |
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.
ExpressionTree mapTree = ASTHelpers.getReceiver(tree.getMethodSelect()); | |
ExpressionTree mapTree = ASTHelpers.getReceiver(tree); |
SuggestedFix fix = Optional.of(getSuggestedFix(SuggestedFix.builder(), tree, state, expressionTree, "")) | ||
.filter(f -> SuggestedFixes.compilesWithFix(f, state)) | ||
.orElseGet( | ||
() -> getQualifiedSuggestedFix(tree, state, expressionTree, receiverType.getTypeArguments())); |
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 it's safe to replace receiverType.getTypeArguments()
with List.of(elementType)
, we can remove the receiver
argument to this function entirely. I'd imagine that should be safe, since we're pulling the resulting stream type of the original function (e.g. what's returned by .map(Optional::get)
) as opposed to the intermediate/input state of the stream such a function is invoked upon.
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, that updated and simplified things quite 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.
Looks great, thanks!
I've added do-not-merge in order to roll out the refactor via an excavator ahead of merging this into gradle-baseline, that way we decouple most of the code churn from the baseline upgrade. I've tagged you on the excavator which has largely identical error-prone code -- let me know if that rollout strategy seems reasonable
@schlosna Feel free to remove the do-not-merge label whenever you like, the lions share of these have successfully merged already |
Released 6.12.0 |
Before this PR
#2946 added a check to convert
Stream.flatMap(Optional::stream)
to more efficientStream.filter(Optional::isPresent).map(Optional::get)
. There was discussion in #2950 whether the autofix should preferOptional::orElseThrow
overOptional::get
and usingStream.mapMulti(Optional::ifPresent)
as a more concise and efficient alternative.After this PR
==COMMIT_MSG==
Converts
Stream.flatMap(Optional::stream)
,Stream.filter(Optional::isPresent).map(Optional::get)
, andfilter(Optional::isPresent).map(Optional::orElseThrow)
toStream.mapMulti(Optional::ifPresent)
.==COMMIT_MSG==
Possible downsides?