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 IsCharacter matcher and assorted test improvements #237

Merged
merged 2 commits into from
Sep 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,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.NotMatches;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.math.BigDecimal;
import java.math.BigInteger;
Expand All @@ -18,6 +19,7 @@
import org.assertj.core.api.AbstractLongAssert;
import org.assertj.core.api.AbstractShortAssert;
import org.assertj.core.api.NumberAssert;
import tech.picnic.errorprone.refaster.util.IsCharacter;

final class AssertJNumberTemplates {
private AssertJNumberTemplates() {}
Expand Down Expand Up @@ -226,9 +228,14 @@ AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> numberAssert) {
}
}

/**
* Prefer {@link AbstractLongAssert#isOdd()} over more contrived alternatives.
Copy link
Member

Choose a reason for hiding this comment

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

It's not just AbstractLongAssert#isOdd(). Would also argue that the original code isn't contrived, but rather that it yields a less informative error message.

*
* <p>Note that for {@link Character}s this rewrite would lead to non-compilable code.
Copy link
Member

Choose a reason for hiding this comment

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

It's not about being non-compilable, but that the alternative is not available.

That said, I still feel like this can be left out: it's implied, and people doubting can just try to see what happens if @NotMatches(IsCharacter.class) is omitted.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah correct, should've been more specific here.

*/
static final class AssertThatIsOdd {
@BeforeTemplate
AbstractIntegerAssert<?> before(int number) {
AbstractIntegerAssert<?> before(@NotMatches(IsCharacter.class) int number) {
return assertThat(number % 2).isEqualTo(1);
}

Expand All @@ -244,9 +251,14 @@ AbstractLongAssert<?> before(long number) {
}
}

/**
* Prefer {@link AbstractLongAssert#isEven()} over more contrived alternatives.
*
* <p>Note that for {@link Character}s this rewrite would lead to non-compilable code.
*/
static final class AssertThatIsEven {
@BeforeTemplate
AbstractIntegerAssert<?> before(int number) {
AbstractIntegerAssert<?> before(@NotMatches(IsCharacter.class) int number) {
return assertThat(number % 2).isEqualTo(0);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,14 @@ boolean after(boolean b) {
* Don't negate an equality test or use the ternary operator to compare two booleans; directly
* test for inequality instead.
*/
// XXX: Replacing `a ? !b : b` with `a != b` changes semantics if both `a` and `b` are boxed
// booleans.
static final class Negation {
@BeforeTemplate
boolean before(boolean a, boolean b) {
return Refaster.anyOf(!(a == b), a ? !b : b);
}

@BeforeTemplate
boolean before(long a, long b) {
return !(a == b);
}

@BeforeTemplate
boolean before(double a, double b) {
return !(a == b);
Expand All @@ -99,17 +96,14 @@ boolean after(boolean a, boolean b) {
* Don't negate an inequality test or use the ternary operator to compare two booleans; directly
* test for equality instead.
*/
// XXX: Replacing `a ? b : !b` with `a == b` changes semantics if both `a` and `b` are boxed
// booleans.
static final class IndirectDoubleNegation {
@BeforeTemplate
boolean before(boolean a, boolean b) {
return Refaster.anyOf(!(a != b), a ? b : !b);
}

@BeforeTemplate
boolean before(long a, long b) {
return !(a != b);
}

@BeforeTemplate
boolean before(double a, double b) {
return !(a != b);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ private PrimitiveTemplates() {}

/** Avoid contrived ways of expressing the "less than" relationship. */
static final class LessThan {
@BeforeTemplate
boolean before(long a, long b) {
return !(a >= b);
}

@BeforeTemplate
boolean before(double a, double b) {
return !(a >= b);
Expand All @@ -28,11 +23,6 @@ boolean after(long a, long b) {

/** Avoid contrived ways of expressing the "less than or equal to" relationship. */
static final class LessThanOrEqualTo {
@BeforeTemplate
boolean before(long a, long b) {
return !(a > b);
}

@BeforeTemplate
boolean before(double a, double b) {
return !(a > b);
Expand All @@ -46,11 +36,6 @@ boolean after(long a, long b) {

/** Avoid contrived ways of expressing the "greater than" relationship. */
static final class GreaterThan {
@BeforeTemplate
boolean before(long a, long b) {
return !(a <= b);
}

@BeforeTemplate
boolean before(double a, double b) {
return !(a <= b);
Expand All @@ -64,11 +49,6 @@ boolean after(long a, long b) {

/** Avoid contrived ways of expressing the "greater than or equal to" relationship. */
static final class GreaterThanOrEqualTo {
@BeforeTemplate
boolean before(long a, long b) {
return !(a < b);
}

@BeforeTemplate
boolean before(double a, double b) {
return !(a < b);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,27 @@ final class AssertJNumberTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(
assertThat((byte) 1 % 2).isEqualTo(1),
assertThat(Byte.valueOf((byte) 1) % 2).isEqualTo(1),
assertThat((char) 1 % 2).isEqualTo(1),
assertThat(Character.valueOf((char) 1) % 2).isEqualTo(1),
assertThat((short) 1 % 2).isEqualTo(1),
assertThat(Short.valueOf((short) 1) % 2).isEqualTo(1),
assertThat(1 % 2).isEqualTo(1),
assertThat(Integer.valueOf(1) % 2).isEqualTo(1),
assertThat(1L % 2).isEqualTo(1),
assertThat(Long.valueOf(1) % 2).isEqualTo(1),
assertThat((short) 1 % 2).isEqualTo(1),
assertThat(Short.valueOf((short) 1) % 2).isEqualTo(1));
assertThat(Long.valueOf(1) % 2).isEqualTo(1));
}

ImmutableSet<NumberAssert<?, ?>> testAssertThatIsEven() {
return ImmutableSet.of(
assertThat((byte) 1 % 2).isEqualTo(0),
assertThat(Byte.valueOf((byte) 1) % 2).isEqualTo(0),
assertThat((char) 1 % 2).isEqualTo(0),
assertThat(Character.valueOf((char) 1) % 2).isEqualTo(0),
assertThat((short) 1 % 2).isEqualTo(0),
assertThat(Short.valueOf((short) 1) % 2).isEqualTo(0),
assertThat(1 % 2).isEqualTo(0),
assertThat(Integer.valueOf(1) % 2).isEqualTo(0),
assertThat(1L % 2).isEqualTo(0),
assertThat(Long.valueOf(1) % 2).isEqualTo(0),
assertThat((short) 1 % 2).isEqualTo(0),
assertThat(Short.valueOf((short) 1) % 2).isEqualTo(0));
assertThat(Long.valueOf(1) % 2).isEqualTo(0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,27 @@ final class AssertJNumberTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(
assertThat((byte) 1).isOdd(),
assertThat(Byte.valueOf((byte) 1)).isOdd(),
assertThat((char) 1 % 2).isEqualTo(1),
assertThat(Character.valueOf((char) 1) % 2).isEqualTo(1),
assertThat((short) 1).isOdd(),
assertThat(Short.valueOf((short) 1)).isOdd(),
assertThat(1).isOdd(),
assertThat(Integer.valueOf(1)).isOdd(),
assertThat(1L).isOdd(),
assertThat(Long.valueOf(1)).isOdd(),
assertThat((short) 1).isOdd(),
assertThat(Short.valueOf((short) 1)).isOdd());
assertThat(Long.valueOf(1)).isOdd());
}

ImmutableSet<NumberAssert<?, ?>> testAssertThatIsEven() {
return ImmutableSet.of(
assertThat((byte) 1).isEven(),
assertThat(Byte.valueOf((byte) 1)).isEven(),
assertThat((char) 1 % 2).isEqualTo(0),
assertThat(Character.valueOf((char) 1) % 2).isEqualTo(0),
assertThat((short) 1).isEven(),
assertThat(Short.valueOf((short) 1)).isEven(),
assertThat(1).isEven(),
assertThat(Integer.valueOf(1)).isEven(),
assertThat(1L).isEven(),
assertThat(Long.valueOf(1)).isEven(),
assertThat((short) 1).isEven(),
assertThat(Short.valueOf((short) 1)).isEven());
assertThat(Long.valueOf(1)).isEven());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ boolean testDoubleNegation() {
return !!Boolean.TRUE;
}

@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<Boolean> testNegation() {
return ImmutableSet.of(
Boolean.TRUE ? !Boolean.FALSE : Boolean.FALSE,
!(Boolean.TRUE == Boolean.FALSE),
true ? !false : false,
!(true == false),
!((byte) 3 == (byte) 4),
!((char) 3 == (char) 4),
!((short) 3 == (short) 4),
!(3 == 4),
!(3L == 4L),
Expand All @@ -44,11 +46,13 @@ ImmutableSet<Boolean> testNegation() {
!(BoundType.OPEN == BoundType.CLOSED));
}

@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<Boolean> testIndirectDoubleNegation() {
return ImmutableSet.of(
Boolean.TRUE ? Boolean.FALSE : !Boolean.FALSE,
!(Boolean.TRUE != Boolean.FALSE),
true ? false : !false,
!(true != false),
!((byte) 3 != (byte) 4),
!((char) 3 != (char) 4),
!((short) 3 != (short) 4),
!(3 != 4),
!(3L != 4L),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ boolean testDoubleNegation() {
return Boolean.TRUE;
}

@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<Boolean> testNegation() {
return ImmutableSet.of(
Boolean.TRUE != Boolean.FALSE,
Boolean.TRUE != Boolean.FALSE,
true != false,
true != false,
(byte) 3 != (byte) 4,
(char) 3 != (char) 4,
(short) 3 != (short) 4,
3 != 4,
3L != 4L,
Expand All @@ -44,11 +46,13 @@ ImmutableSet<Boolean> testNegation() {
BoundType.OPEN != BoundType.CLOSED);
}

@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<Boolean> testIndirectDoubleNegation() {
return ImmutableSet.of(
Boolean.TRUE == Boolean.FALSE,
Boolean.TRUE == Boolean.FALSE,
true == false,
true == false,
(byte) 3 == (byte) 4,
(char) 3 == (char) 4,
(short) 3 == (short) 4,
3 == 4,
3L == 4L,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public ImmutableSet<?> elidedTypesAndStaticImports() {
ImmutableSet<Boolean> testLessThan() {
return ImmutableSet.of(
!((byte) 3 >= (byte) 4),
!((char) 3 >= (char) 4),
!((short) 3 >= (short) 4),
!(3 >= 4),
!(3L >= 4L),
Expand All @@ -23,6 +24,7 @@ ImmutableSet<Boolean> testLessThan() {
ImmutableSet<Boolean> testLessThanOrEqualTo() {
return ImmutableSet.of(
!((byte) 3 > (byte) 4),
!((char) 3 > (char) 4),
!((short) 3 > (short) 4),
!(3 > 4),
!(3L > 4L),
Expand All @@ -33,6 +35,7 @@ ImmutableSet<Boolean> testLessThanOrEqualTo() {
ImmutableSet<Boolean> testGreaterThan() {
return ImmutableSet.of(
!((byte) 3 <= (byte) 4),
!((char) 3 <= (char) 4),
!((short) 3 <= (short) 4),
!(3 <= 4),
!(3L <= 4L),
Expand All @@ -43,6 +46,7 @@ ImmutableSet<Boolean> testGreaterThan() {
ImmutableSet<Boolean> testGreaterThanOrEqualTo() {
return ImmutableSet.of(
!((byte) 3 < (byte) 4),
!((char) 3 < (char) 4),
!((short) 3 < (short) 4),
!(3 < 4),
!(3L < 4L),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,46 @@ public ImmutableSet<?> elidedTypesAndStaticImports() {

ImmutableSet<Boolean> testLessThan() {
return ImmutableSet.of(
(byte) 3 < (byte) 4, (short) 3 < (short) 4, 3 < 4, 3L < 4L, 3F < 4F, 3.0 < 4.0);
(byte) 3 < (byte) 4,
(char) 3 < (char) 4,
(short) 3 < (short) 4,
3 < 4,
3L < 4L,
3F < 4F,
3.0 < 4.0);
}

ImmutableSet<Boolean> testLessThanOrEqualTo() {
return ImmutableSet.of(
(byte) 3 <= (byte) 4, (short) 3 <= (short) 4, 3 <= 4, 3L <= 4L, 3F <= 4F, 3.0 <= 4.0);
(byte) 3 <= (byte) 4,
(char) 3 <= (char) 4,
(short) 3 <= (short) 4,
3 <= 4,
3L <= 4L,
3F <= 4F,
3.0 <= 4.0);
}

ImmutableSet<Boolean> testGreaterThan() {
return ImmutableSet.of(
(byte) 3 > (byte) 4, (short) 3 > (short) 4, 3 > 4, 3L > 4L, 3F > 4F, 3.0 > 4.0);
(byte) 3 > (byte) 4,
(char) 3 > (char) 4,
(short) 3 > (short) 4,
3 > 4,
3L > 4L,
3F > 4F,
3.0 > 4.0);
}

ImmutableSet<Boolean> testGreaterThanOrEqualTo() {
return ImmutableSet.of(
(byte) 3 >= (byte) 4, (short) 3 >= (short) 4, 3 >= 4, 3L >= 4L, 3F >= 4F, 3.0 >= 4.0);
(byte) 3 >= (byte) 4,
(char) 3 >= (char) 4,
(short) 3 >= (short) 4,
3 >= 4,
3L >= 4L,
3F >= 4F,
3.0 >= 4.0);
}

int testLongToIntExact() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package tech.picnic.errorprone.refaster.util;

import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isSameType;

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

/** A matcher of {@link Character}-typed expressions. */
public final class IsCharacter implements Matcher<ExpressionTree> {
Copy link
Member Author

Choose a reason for hiding this comment

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

Was doubting a bit about the name. We flag both char and Character 🤔. Not sure how to reflect it in the name, especially considering Refaster does auto-boxing by default. Therefore I went for this one.

Copy link
Contributor

Choose a reason for hiding this comment

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

Shall we "generify" 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.

The idea of a Matcher is to be specific in cases like this actually. We have to use this in a @Matches(value) or @NotMatches(value) annotation where value is:

Class<? extends Matcher<? super ExpressionTree>> value();

This setup limits the expressiveness of specifying what Matchers we want to use.

Good question though 😉.

Copy link
Member

@Stephan202 Stephan202 Sep 17, 2022

Choose a reason for hiding this comment

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

To be more explicit about the limitation we're facing here: we can't pass constructor parameters.

private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> DELEGATE =
anyOf(isSameType(Character.class), isSameType(s -> s.getSymtab().charType));
Copy link
Member

Choose a reason for hiding this comment

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

I suspect that the second case is more common. I.c.w. a predefined supplier we can do:

Suggested change
anyOf(isSameType(Character.class), isSameType(s -> s.getSymtab().charType));
anyOf(isSameType(CHAR_TYPE), isSameType(Character.class));


@Override
public boolean matches(ExpressionTree tree, VisitorState state) {
return DELEGATE.matches(tree, state);
}
}
Loading