Skip to content

Commit

Permalink
Introduce StreamMapTo{Double,Int,Long}Sum Refaster rules (#497)
Browse files Browse the repository at this point in the history
As well as a new `IsLambdaExpressionOrMethodReference` matcher.
  • Loading branch information
giovannizotta authored Feb 21, 2023
1 parent fd6a45e commit 5bb1dd1
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Matches;
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
Expand All @@ -19,10 +20,14 @@
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference;

/** Refaster rules related to expressions dealing with {@link Stream}s. */
@OnlineDocumentation
Expand Down Expand Up @@ -379,4 +384,46 @@ boolean after(Stream<T> stream) {
return stream.allMatch(e -> test(e));
}
}

static final class StreamMapToIntSum<T> {
@BeforeTemplate
int before(
Stream<T> stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function<? super T, Integer> mapper) {
return stream.map(mapper).reduce(0, Integer::sum);
}

@AfterTemplate
int after(Stream<T> stream, ToIntFunction<T> mapper) {
return stream.mapToInt(mapper).sum();
}
}

static final class StreamMapToDoubleSum<T> {
@BeforeTemplate
double before(
Stream<T> stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function<? super T, Double> mapper) {
return stream.map(mapper).reduce(0.0, Double::sum);
}

@AfterTemplate
double after(Stream<T> stream, ToDoubleFunction<T> mapper) {
return stream.mapToDouble(mapper).sum();
}
}

static final class StreamMapToLongSum<T> {
@BeforeTemplate
long before(
Stream<T> stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function<? super T, Long> mapper) {
return stream.map(mapper).reduce(0L, Long::sum);
}

@AfterTemplate
long after(Stream<T> stream, ToLongFunction<T> mapper) {
return stream.mapToLong(mapper).sum();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.google.common.collect.Streams;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
Expand Down Expand Up @@ -138,4 +139,28 @@ ImmutableSet<Boolean> testStreamAllMatch() {
boolean testStreamAllMatch2() {
return Stream.of("foo").noneMatch(s -> !s.isBlank());
}

ImmutableSet<Integer> testStreamMapToIntSum() {
Function<String, Integer> parseIntFunction = Integer::parseInt;
return ImmutableSet.of(
Stream.of(1).map(i -> i * 2).reduce(0, Integer::sum),
Stream.of("2").map(Integer::parseInt).reduce(0, Integer::sum),
Stream.of("3").map(parseIntFunction).reduce(0, Integer::sum));
}

ImmutableSet<Double> testStreamMapToDoubleSum() {
Function<String, Double> parseDoubleFunction = Double::parseDouble;
return ImmutableSet.of(
Stream.of(1).map(i -> i * 2.0).reduce(0.0, Double::sum),
Stream.of("2").map(Double::parseDouble).reduce(0.0, Double::sum),
Stream.of("3").map(parseDoubleFunction).reduce(0.0, Double::sum));
}

ImmutableSet<Long> testStreamMapToLongSum() {
Function<String, Long> parseLongFunction = Long::parseLong;
return ImmutableSet.of(
Stream.of(1).map(i -> i * 2L).reduce(0L, Long::sum),
Stream.of("2").map(Long::parseLong).reduce(0L, Long::sum),
Stream.of("3").map(parseLongFunction).reduce(0L, Long::sum));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
Expand Down Expand Up @@ -137,4 +138,28 @@ ImmutableSet<Boolean> testStreamAllMatch() {
boolean testStreamAllMatch2() {
return Stream.of("foo").allMatch(s -> s.isBlank());
}

ImmutableSet<Integer> testStreamMapToIntSum() {
Function<String, Integer> parseIntFunction = Integer::parseInt;
return ImmutableSet.of(
Stream.of(1).mapToInt(i -> i * 2).sum(),
Stream.of("2").mapToInt(Integer::parseInt).sum(),
Stream.of("3").map(parseIntFunction).reduce(0, Integer::sum));
}

ImmutableSet<Double> testStreamMapToDoubleSum() {
Function<String, Double> parseDoubleFunction = Double::parseDouble;
return ImmutableSet.of(
Stream.of(1).mapToDouble(i -> i * 2.0).sum(),
Stream.of("2").mapToDouble(Double::parseDouble).sum(),
Stream.of("3").map(parseDoubleFunction).reduce(0.0, Double::sum));
}

ImmutableSet<Long> testStreamMapToLongSum() {
Function<String, Long> parseLongFunction = Long::parseLong;
return ImmutableSet.of(
Stream.of(1).mapToLong(i -> i * 2L).sum(),
Stream.of("2").mapToLong(Long::parseLong).sum(),
Stream.of("3").map(parseLongFunction).reduce(0L, Long::sum));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package tech.picnic.errorprone.refaster.matchers;

import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberReferenceTree;

/** A matcher of lambda expressions and method references. */
public final class IsLambdaExpressionOrMethodReference implements Matcher<ExpressionTree> {
private static final long serialVersionUID = 1L;

/** Instantiates a new {@link IsLambdaExpressionOrMethodReference} instance. */
public IsLambdaExpressionOrMethodReference() {}

@Override
public boolean matches(ExpressionTree tree, VisitorState state) {
return tree instanceof LambdaExpressionTree || tree instanceof MemberReferenceTree;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package tech.picnic.errorprone.refaster.matchers;

import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;

import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.bugpatterns.BugChecker;
import org.junit.jupiter.api.Test;

final class IsLambdaExpressionOrMethodReferenceTest {
@Test
void matches() {
CompilationTestHelper.newInstance(MatcherTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import com.google.common.base.Predicates;",
"import java.util.function.Function;",
"import java.util.function.Predicate;",
"",
"class A {",
" boolean negative1() {",
" return true;",
" }",
"",
" String negative2() {",
" return new String(new byte[0]);",
" }",
"",
" Predicate<String> negative3() {",
" return Predicates.alwaysTrue();",
" }",
"",
" Predicate<String> positive1() {",
" // BUG: Diagnostic contains:",
" return str -> true;",
" }",
"",
" Predicate<String> positive2() {",
" // BUG: Diagnostic contains:",
" return str -> {",
" return true;",
" };",
" }",
"",
" Predicate<String> positive3() {",
" // BUG: Diagnostic contains:",
" return String::isEmpty;",
" }",
"",
" Function<byte[], String> positive4() {",
" // BUG: Diagnostic contains:",
" return String::new;",
" }",
"}")
.doTest();
}

/** A {@link BugChecker} that simply delegates to {@link IsLambdaExpressionOrMethodReference}. */
@BugPattern(
summary = "Flags expressions matched by `IsLambdaExpressionOrMethodReference`",
severity = ERROR)
public static final class MatcherTestChecker extends AbstractMatcherTestChecker {
private static final long serialVersionUID = 1L;

// XXX: This is a false positive reported by Checkstyle. See
// https://github.com/checkstyle/checkstyle/issues/10161#issuecomment-1242732120.
@SuppressWarnings("RedundantModifier")
public MatcherTestChecker() {
super(new IsLambdaExpressionOrMethodReference());
}
}
}

0 comments on commit 5bb1dd1

Please sign in to comment.