Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -38,6 +38,7 @@
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import java.util.Iterator;
import java.util.List;
Expand Down Expand Up @@ -220,8 +221,6 @@ public MethodNameMatcher withNameMatching(Pattern pattern) {

@Override
public MethodSignatureMatcher withSignature(String signature) {
// TODO(cushon): build a way to match signatures (including varargs ones!) that doesn't
// rely on MethodSymbol#toString().
return append(
(m, s) ->
m.sym().getSimpleName().contentEquals(signature)
Expand Down Expand Up @@ -267,6 +266,32 @@ public ParameterMatcher withParametersOfType(Supplier<Type> first, Supplier<Type
return withParametersOfType(Lists.asList(first, rest));
}

@Override
public ParameterMatcher withParametersMatching(
ParameterPredicate first, ParameterPredicate... rest) {
return withParametersMatching(Lists.asList(first, rest));
}

@Override
public ParameterMatcher withParametersMatching(Iterable<ParameterPredicate> expected) {
return append(
(method, state) -> {
List<VarSymbol> actual = method.sym().getParameters();
if (actual.size() != Iterables.size(expected)) {
return false;
}
Iterator<VarSymbol> ax = actual.iterator();
Iterator<ParameterPredicate> bx = expected.iterator();
while (ax.hasNext()) {
VarSymbol parameter = ax.next();
if (!bx.next().matches(parameter, parameter.type, state)) {
return false;
}
}
return true;
});
}

@Override
public ConstructorClassMatcher forClass(TypePredicate predicate) {
return append((m, s) -> predicate.apply(m.ownerType(), s));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.predicates.TypePredicates;
import com.google.errorprone.suppliers.Supplier;
import com.sun.source.tree.ExpressionTree;
import com.sun.tools.javac.code.Type;
Expand Down Expand Up @@ -189,6 +190,24 @@ public interface MethodNameMatcher extends MethodMatcher {

/** Match methods whose formal parameters have the given types. */
ParameterMatcher withParametersOfType(Supplier<Type> first, Supplier<Type>... rest);

/**
* Match methods whose formal parameters have the given types.
*
* <p>Unlike other methods for matching on parameters which consider erased types, this method
* provides access to generic types. Note also that other methods like {@link
* TypePredicates#isExactType} still only compare erased types.
*/
ParameterMatcher withParametersMatching(ParameterPredicate first, ParameterPredicate... rest);

/**
* Match methods whose formal parameters have the given types.
*
* <p>Unlike other methods for matching on parameters which consider erased types, this method
* provides access to generic types. Note also that other methods like {@link
* TypePredicates#isExactType} still only compare erased types.
*/
ParameterMatcher withParametersMatching(Iterable<ParameterPredicate> parameters);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2026 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.errorprone.matchers.method;

import com.google.errorprone.VisitorState;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;

/** A predicate on a method or constructor parameter. */
public interface ParameterPredicate {

boolean matches(VarSymbol parameter, Type type, VisitorState state);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2026 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.errorprone.matchers.method;

import static com.google.common.base.Preconditions.checkState;

import com.google.errorprone.predicates.TypePredicate;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.TypeVariableSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTag;
import java.util.List;

/** Utility methods for creating {@link ParameterPredicate} instances. */
public final class ParameterPredicates {

public static ParameterPredicate of(TypePredicate predicate) {
return (parameter, type, state) -> predicate.apply(type, state);
}

public static ParameterPredicate varargsOf(ParameterPredicate predicate) {
return (parameter, type, state) -> {
MethodSymbol method = (MethodSymbol) parameter.owner;
if (!method.isVarArgs()) {
return false;
}
if (method.getParameters().getLast() != parameter) {
return false;
}
Type componentType = state.getTypes().elemtype(type);
return predicate.matches(parameter, componentType, state);
};
}

public static ParameterPredicate arrayOf(ParameterPredicate predicate) {
return (parameter, type, state) -> {
if (!type.hasTag(TypeTag.ARRAY)) {
return false;
}
Type componentType = state.getTypes().elemtype(type);
return predicate.matches(parameter, componentType, state);
};
}

// TODO: cushon - add methods like nthTypeParameter(2) or typeParameterNamed("X") as needed
public static ParameterPredicate onlyTypeParameter() {
return (parameter, type, state) -> {
MethodSymbol method = (MethodSymbol) parameter.owner;
List<TypeVariableSymbol> typeParameters = method.getTypeParameters();
checkState(
typeParameters.size() == 1,
"Expected method %s to have exactly one type parameter, but found %s",
method,
typeParameters);
return type.hasTag(TypeTag.TYPEVAR) && type.tsym == typeParameters.getFirst();
};
}

private ParameterPredicates() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,16 @@ public static Supplier<Type> typeFromClass(Class<?> inputClass) {

public static final Supplier<Type> LONG_TYPE = state -> state.getSymtab().longType;

public static final Supplier<Type> FLOAT_TYPE = state -> state.getSymtab().floatType;

public static final Supplier<Type> DOUBLE_TYPE = state -> state.getSymtab().doubleType;

public static final Supplier<Type> CHAR_TYPE = state -> state.getSymtab().charType;

public static final Supplier<Type> OBJECT_TYPE = state -> state.getSymtab().objectType;

public static final Supplier<Type> CLASS_TYPE = state -> state.getSymtab().classType;

public static final Supplier<Type> EXCEPTION_TYPE = state -> state.getSymtab().exceptionType;

public static final Supplier<Type> THROWABLE_TYPE = state -> state.getSymtab().throwableType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.suppliers.Suppliers.INT_TYPE;
import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;
import static com.google.errorprone.suppliers.Suppliers.arrayOf;

import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
Expand Down Expand Up @@ -46,10 +49,12 @@ public class ArrayFillIncompatibleType extends BugChecker implements MethodInvoc
anyOf(
staticMethod()
.onClass("java.util.Arrays")
.withSignature("fill(java.lang.Object[],java.lang.Object)"),
.named("fill")
.withParametersOfType(arrayOf(OBJECT_TYPE), OBJECT_TYPE),
staticMethod()
.onClass("java.util.Arrays")
.withSignature("fill(java.lang.Object[],int,int,java.lang.Object)"));
.named("fill")
.withParametersOfType(arrayOf(OBJECT_TYPE), INT_TYPE, INT_TYPE, OBJECT_TYPE));

@Override
public Description matchMethodInvocation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.method.ParameterPredicates.arrayOf;
import static com.google.errorprone.matchers.method.ParameterPredicates.onlyTypeParameter;
import static com.google.errorprone.util.ASTHelpers.getType;

import com.google.errorprone.BugPattern;
Expand Down Expand Up @@ -47,7 +49,10 @@ public class CollectionToArraySafeParameter extends BugChecker
implements MethodInvocationTreeMatcher {

private static final Matcher<ExpressionTree> TO_ARRAY_MATCHER =
instanceMethod().onDescendantOf("java.util.Collection").withSignature("<T>toArray(T[])");
instanceMethod()
.onDescendantOf("java.util.Collection")
.named("toArray")
.withParametersMatching(arrayOf(onlyTypeParameter()));

@Override
public Description matchMethodInvocation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import static com.google.errorprone.matchers.Matchers.toType;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static com.google.errorprone.suppliers.Suppliers.CLASS_TYPE;
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
import static com.google.errorprone.util.ASTHelpers.getUpperBound;
import static com.google.errorprone.util.ASTHelpers.isSubtype;
Expand Down Expand Up @@ -77,15 +78,10 @@ public class ExpectedExceptionChecker extends BugChecker implements MethodTreeMa
.withNameMatching(Pattern.compile("expect.*")));

static final Matcher<ExpressionTree> IS_A =
anyOf(
staticMethod()
.onClassAny(
"org.hamcrest.Matchers", "org.hamcrest.CoreMatchers", "org.hamcrest.core.Is")
.withSignature("<T>isA(java.lang.Class<T>)"),
staticMethod()
.onClassAny(
"org.hamcrest.Matchers", "org.hamcrest.CoreMatchers", "org.hamcrest.core.Is")
.withSignature("<T>isA(java.lang.Class<?>)"));
staticMethod()
.onClassAny("org.hamcrest.Matchers", "org.hamcrest.CoreMatchers", "org.hamcrest.core.Is")
.named("isA")
.withParametersOfType(CLASS_TYPE);

static final Matcher<StatementTree> FAIL_MATCHER =
anyOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.fixes.SuggestedFixes.qualifyType;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.matchers.method.ParameterPredicates.onlyTypeParameter;
import static com.google.errorprone.predicates.TypePredicates.isExactType;
import static com.google.errorprone.suppliers.Suppliers.CLASS_TYPE;
import static com.google.errorprone.util.ASTHelpers.getReceiver;
import static com.google.errorprone.util.ASTHelpers.getSymbol;
import static java.util.stream.Collectors.joining;
Expand All @@ -32,6 +35,7 @@
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.method.MethodMatchers;
import com.google.errorprone.matchers.method.ParameterPredicates;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
Expand All @@ -49,7 +53,9 @@ public class RobolectricShadowDirectlyOn extends BugChecker implements MethodInv
private static final Matcher<ExpressionTree> MATCHER =
MethodMatchers.staticMethod()
.onClass("org.robolectric.shadow.api.Shadow")
.withSignature("<T>directlyOn(T,java.lang.Class<T>)");
.named("directlyOn")
.withParametersMatching(
onlyTypeParameter(), ParameterPredicates.of(isExactType(CLASS_TYPE)));

@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static com.google.errorprone.suppliers.Suppliers.CLASS_TYPE;
import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
Expand Down Expand Up @@ -68,7 +70,8 @@ public class UnnecessarySetDefault extends BugChecker implements MethodInvocatio
private static final Matcher<ExpressionTree> SET_DEFAULT =
instanceMethod()
.onExactClass("com.google.common.testing.NullPointerTester")
.withSignature("<T>setDefault(java.lang.Class<T>,T)");
.named("setDefault")
.withParametersOfType(CLASS_TYPE, OBJECT_TYPE);

@VisibleForTesting
static final ImmutableMap<String, Matcher<ExpressionTree>> DEFAULTS =
Expand Down
Loading
Loading