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 StreamMapTo{Double,Int,Long}Sum Refaster rules #497

Merged
merged 7 commits into from
Feb 21, 2023

Conversation

giovannizotta
Copy link
Contributor

@giovannizotta giovannizotta commented Feb 12, 2023

I came across this pattern during a code review:
.map(Stock::getQuantity).reduce(0, Integer::sum);

This felt more readable to me:
.mapToInt(Stock::getQuantity).sum();

It can also be extended to Long and Double. Let me know what you think! 🙂

Suggested commit message:

Introduce `StreamMapTo{Double,Int,Long}Sum` Refaster rules (#497)

As well as a new `IsLambdaExpressionOrMethodReference` matcher.

@github-actions
Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

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.

Adding the Double and Long variants makes sense to me :).

I added a commit; suggested commit message:

Introduce `StreamMapTo{Double,Int,Long}Sum` Refaster rules (#497)

@@ -379,4 +380,16 @@ boolean after(Stream<T> stream) {
return stream.allMatch(e -> test(e));
}
}

static final class StreamMapReduceIntSum<T> {
Copy link
Member

Choose a reason for hiding this comment

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

We try to name rules after the code in the @AfterTemplate, which in this case would yield:

Suggested change
static final class StreamMapReduceIntSum<T> {
static final class StreamMapToIntSum<T> {

Copy link
Member

Choose a reason for hiding this comment

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

(I was wondering whether this rule should be in the IntStreamRules class, but since IntStream is only an intermediate type, I suppose not.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was wondering the same, and came to the same conclusion 🙂

@github-actions
Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Stephan202 Stephan202 added this to the 0.9.0 milestone Feb 12, 2023
@Stephan202
Copy link
Member

Forgot to say: congrats and thanks for your first Error Prone Support contribution 🎉

@giovannizotta giovannizotta changed the title Introduce StreamMapReduceIntSum Refaster rule Introduce StreamMapTo{Double,Int,Long}Sum Refaster rules Feb 12, 2023
@giovannizotta giovannizotta added the WIP Work in progress label Feb 13, 2023
@github-actions
Copy link

  • Surviving mutants in this change: 11
  • Killed mutants in this change: 0
class surviving killed
🧟tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference 11 0

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

@giovannizotta
Copy link
Contributor Author

Thank you! 😊

I added the Matcher, but I couldn't figure out how to make a test for it from these tests. Do you have any pointers? 😅

@rickie
Copy link
Member

rickie commented Feb 15, 2023

I found a related example that we used during my thesis project: google/error-prone@master...PicnicSupermarket:error-prone:rossendrijver/return_type#diff-cd9d301320. The Matcher used there did some slightly different things and was "hacked together" a little 😬.

I would write the Matcher to be in the same setup/style as the other matchers. Having a Matcher DELEGATE like this:

  private static final Matcher<ExpressionTree> DELEGATE =
      anyOf(isSameType(CHAR_TYPE), isSameType(Character.class));

The DELEGATE can also invoke a method that you would have in IsLambdaExpressionOrMethodReference where you can do the analysis.

The IsLambdaExpressionOrMethodReferenceTest would have the following things;

  • Test code just like the other Matcher tests.
  • A TestMatcher that uses the AbstractTestMatcher to validate against the DELEGATE.

This comment combined with the things we discussed offline should be a pointer 😄.

@giovannizotta
Copy link
Contributor Author

Thank you for your time Rick! That's definitely a nice pointer, I'll take a look at it when I have some time :)

@giovannizotta
Copy link
Contributor Author

I'm having fun! 😄
This is the current state of the matcher, which I think resembles what you had in mind.

However, I am having a bit of trouble testing it in the same way as the other Matchers (like IsCharacter) are tested. Those are testing simple one-liners, while in this case, if I want to test that actual instances of Function<T, {Double, Integer, Long}> are matched, I need a multi-line test.

If I try something like this, it doesn't work because the matcher searches for matches in the whole test method, not only the .map(function).reduce(0, Integer::sum) part. Indeed, you can see that of course the matcher matches lambda expressions and method references, but not those that we are interested in.

Do you have any idea on how to proceed? I bet there's a way to be able to handle this by taking a look at, for example, ErrorProneTestHelperSourceFormatTest, but I do not completely understand what's going on. I pushed a commit with the test disabled. Thank you in advance for your time! 🙂

@github-actions
Copy link

  • Surviving mutants in this change: 10
  • Killed mutants in this change: 0
class surviving killed
🧟tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference 10 0

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

@Stephan202 Stephan202 force-pushed the giovannizotta/map-reduce-int-sum branch from 19dc1a2 to eaea479 Compare February 19, 2023 09:58
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.

Good question about how to test this stuff. The trick is so split the code over multiple lines. That said, this does indeed highlight a weakness of this kind of test: it's easy to over-match.

I rebased and added a commit with some improvements. Not yet done reviewing, but thought it'd be nice to show this intermediate state :)

(Functionally I think this now looks very good; will look a bit into minimizing the test without impacting coverage.)

@@ -138,4 +139,28 @@ ImmutableSet<Boolean> testStreamAllMatch() {
boolean testStreamAllMatch2() {
return Stream.of("foo").noneMatch(s -> !s.isBlank());
}

ImmutableSet<Integer> testStreamMapToIntSum() {
Function<String, Integer> parseIntFunction = (String s) -> Integer.parseInt(s);
Copy link
Member

Choose a reason for hiding this comment

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

Just because it's shorter:

Suggested change
Function<String, Integer> parseIntFunction = (String s) -> Integer.parseInt(s);
Function<String, Integer> parseIntFunction = Integer::parseInt;

public static <T extends Tree> Matcher<T> isMethodReference() {
return (tree, state) ->
tree instanceof MemberReferenceTree
&& ((MemberReferenceTree) tree).getMode().equals(INVOKE);
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 also allow ::new, so the mode check can be omitted :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. I was thinking about the specific usage of this PR, but extending it to ::new it makes it more reusable indeed :)

public final class IsLambdaExpressionOrMethodReference implements Matcher<ExpressionTree> {
private static final long serialVersionUID = 1L;

private static final Matcher<ExpressionTree> DELEGATE =
Copy link
Member

Choose a reason for hiding this comment

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

While indeed usually we introduce such a DELEGATE constant for performance reasons, in this case there's no efficiency gain over inlining the logic.

@github-actions
Copy link

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference 0 5

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

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 one more commit. Very nice contribution @giovannizotta! 💪

Updated suggested commit message:

Introduce `StreamMapTo{Double,Int,Long}Sum` Refaster rules (#497)

As well as a new `IsLambdaExpressionOrMethodReference` matcher.

@github-actions
Copy link

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference 0 5

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

@giovannizotta
Copy link
Contributor Author

Thank you @Stephan202 for the cleanup! It was the first time I dove into these concepts but I really enjoyed it, even though you ended up doing most of the work 😄

Also nice to see the intermediate state to know how to solve this kind of issue the next time :)

@Stephan202
Copy link
Member

Rest assured: this workflow is how it goes every time 😄. It's in the nature of the project: tricky concepts, a large and unfamiliar API, and some rather opinionated maintainers ;)

If you're up for it, don't hesitate to contribute other changes if/when you observe them during code review 🚀.

@giovannizotta giovannizotta removed the WIP Work in progress label Feb 20, 2023
@rickie rickie self-requested a review February 20, 2023 17:58
@rickie rickie force-pushed the giovannizotta/map-reduce-int-sum branch from 214a0c7 to 3d73f2e Compare February 21, 2023 06:32
@github-actions
Copy link

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference 0 5

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

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.

@Stephan202, I'm curious, any particular reason to not add Javadoc for the Refaster rules?

Added a commit with some suggestions.

Nice contribution @giovannizotta, cool stuff 🚀 !

"",
" Predicate<String> positive1() {",
" // BUG: Diagnostic contains:",
" return str -> true;",
Copy link
Member

@rickie rickie Feb 21, 2023

Choose a reason for hiding this comment

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

I think it is also nice to have a lambda case with { }, just for completeness.

Function<String, Double> parseDoubleFunction = Double::parseDouble;
return ImmutableSet.of(
Stream.of(1).mapToDouble(i -> i * 2.0).sum(),
Stream.of("1").mapToDouble(Double::parseDouble).sum(),
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 use a different number or string per test case, see also further up in this file :). So let's change this.

@github-actions
Copy link

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

class surviving killed
🎉tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference 0 5

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

@Stephan202
Copy link
Member

@Stephan202, I'm curious, any particular reason to not add Javadoc for the Refaster rules?

Nope, just that the preceding rules also don't have any. :)

@rickie rickie merged commit 5bb1dd1 into master Feb 21, 2023
@rickie rickie deleted the giovannizotta/map-reduce-int-sum branch February 21, 2023 15:35
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