Skip to content

Commit a98a1c5

Browse files
cushonError Prone Team
authored andcommitted
Create alternatives to withSignature for varargs and type parameters
PiperOrigin-RevId: 886117793
1 parent a05765d commit a98a1c5

13 files changed

Lines changed: 256 additions & 62 deletions

File tree

check_api/src/main/java/com/google/errorprone/matchers/method/MethodMatcherImpl.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.google.errorprone.suppliers.Suppliers;
3939
import com.google.errorprone.util.ASTHelpers;
4040
import com.sun.source.tree.ExpressionTree;
41+
import com.sun.tools.javac.code.Symbol.VarSymbol;
4142
import com.sun.tools.javac.code.Type;
4243
import java.util.Iterator;
4344
import java.util.List;
@@ -220,8 +221,6 @@ public MethodNameMatcher withNameMatching(Pattern pattern) {
220221

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

269+
@Override
270+
public ParameterMatcher withParametersMatching(
271+
ParameterPredicate first, ParameterPredicate... rest) {
272+
return withParametersMatching(Lists.asList(first, rest));
273+
}
274+
275+
@Override
276+
public ParameterMatcher withParametersMatching(Iterable<ParameterPredicate> expected) {
277+
return append(
278+
(method, state) -> {
279+
List<VarSymbol> actual = method.sym().getParameters();
280+
if (actual.size() != Iterables.size(expected)) {
281+
return false;
282+
}
283+
Iterator<VarSymbol> ax = actual.iterator();
284+
Iterator<ParameterPredicate> bx = expected.iterator();
285+
while (ax.hasNext()) {
286+
VarSymbol parameter = ax.next();
287+
if (!bx.next().matches(parameter, parameter.type, state)) {
288+
return false;
289+
}
290+
}
291+
return true;
292+
});
293+
}
294+
270295
@Override
271296
public ConstructorClassMatcher forClass(TypePredicate predicate) {
272297
return append((m, s) -> predicate.apply(m.ownerType(), s));

check_api/src/main/java/com/google/errorprone/matchers/method/MethodMatchers.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.errorprone.matchers.Matcher;
2020
import com.google.errorprone.predicates.TypePredicate;
21+
import com.google.errorprone.predicates.TypePredicates;
2122
import com.google.errorprone.suppliers.Supplier;
2223
import com.sun.source.tree.ExpressionTree;
2324
import com.sun.tools.javac.code.Type;
@@ -189,6 +190,24 @@ public interface MethodNameMatcher extends MethodMatcher {
189190

190191
/** Match methods whose formal parameters have the given types. */
191192
ParameterMatcher withParametersOfType(Supplier<Type> first, Supplier<Type>... rest);
193+
194+
/**
195+
* Match methods whose formal parameters have the given types.
196+
*
197+
* <p>Unlike other methods for matching on parameters which consider erased types, this method
198+
* provides access to generic types. Note also that other methods like {@link
199+
* TypePredicates#isExactType} still only compare erased types.
200+
*/
201+
ParameterMatcher withParametersMatching(ParameterPredicate first, ParameterPredicate... rest);
202+
203+
/**
204+
* Match methods whose formal parameters have the given types.
205+
*
206+
* <p>Unlike other methods for matching on parameters which consider erased types, this method
207+
* provides access to generic types. Note also that other methods like {@link
208+
* TypePredicates#isExactType} still only compare erased types.
209+
*/
210+
ParameterMatcher withParametersMatching(Iterable<ParameterPredicate> parameters);
192211
}
193212

194213
/**
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2026 The Error Prone Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.errorprone.matchers.method;
18+
19+
import com.google.errorprone.VisitorState;
20+
import com.sun.tools.javac.code.Symbol.VarSymbol;
21+
import com.sun.tools.javac.code.Type;
22+
23+
/** A predicate on a method or constructor parameter. */
24+
public interface ParameterPredicate {
25+
26+
boolean matches(VarSymbol parameter, Type type, VisitorState state);
27+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2026 The Error Prone Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.errorprone.matchers.method;
18+
19+
import static com.google.common.base.Preconditions.checkState;
20+
21+
import com.google.errorprone.predicates.TypePredicate;
22+
import com.sun.tools.javac.code.Symbol.MethodSymbol;
23+
import com.sun.tools.javac.code.Symbol.TypeVariableSymbol;
24+
import com.sun.tools.javac.code.Type;
25+
import com.sun.tools.javac.code.TypeTag;
26+
import java.util.List;
27+
28+
/** Utility methods for creating {@link ParameterPredicate} instances. */
29+
public final class ParameterPredicates {
30+
31+
public static ParameterPredicate of(TypePredicate predicate) {
32+
return (parameter, type, state) -> predicate.apply(type, state);
33+
}
34+
35+
public static ParameterPredicate varargsOf(ParameterPredicate predicate) {
36+
return (parameter, type, state) -> {
37+
MethodSymbol method = (MethodSymbol) parameter.owner;
38+
if (!method.isVarArgs()) {
39+
return false;
40+
}
41+
if (method.getParameters().getLast() != parameter) {
42+
return false;
43+
}
44+
Type componentType = state.getTypes().elemtype(type);
45+
return predicate.matches(parameter, componentType, state);
46+
};
47+
}
48+
49+
public static ParameterPredicate arrayOf(ParameterPredicate predicate) {
50+
return (parameter, type, state) -> {
51+
if (!type.hasTag(TypeTag.ARRAY)) {
52+
return false;
53+
}
54+
Type componentType = state.getTypes().elemtype(type);
55+
return predicate.matches(parameter, componentType, state);
56+
};
57+
}
58+
59+
// TODO: cushon - add methods like nthTypeParameter(2) or typeParameterNamed("X") as needed
60+
public static ParameterPredicate onlyTypeParameter() {
61+
return (parameter, type, state) -> {
62+
MethodSymbol method = (MethodSymbol) parameter.owner;
63+
List<TypeVariableSymbol> typeParameters = method.getTypeParameters();
64+
checkState(
65+
typeParameters.size() == 1,
66+
"Expected method %s to have exactly one type parameter, but found %s",
67+
method,
68+
typeParameters);
69+
return type.hasTag(TypeTag.TYPEVAR) && type.tsym == typeParameters.getFirst();
70+
};
71+
}
72+
73+
private ParameterPredicates() {}
74+
}

check_api/src/main/java/com/google/errorprone/suppliers/Suppliers.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,16 @@ public static Supplier<Type> typeFromClass(Class<?> inputClass) {
130130

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

133+
public static final Supplier<Type> FLOAT_TYPE = state -> state.getSymtab().floatType;
134+
133135
public static final Supplier<Type> DOUBLE_TYPE = state -> state.getSymtab().doubleType;
134136

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

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

141+
public static final Supplier<Type> CLASS_TYPE = state -> state.getSymtab().classType;
142+
139143
public static final Supplier<Type> EXCEPTION_TYPE = state -> state.getSymtab().exceptionType;
140144

141145
public static final Supplier<Type> THROWABLE_TYPE = state -> state.getSymtab().throwableType;

core/src/main/java/com/google/errorprone/bugpatterns/ArrayFillIncompatibleType.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
2020
import static com.google.errorprone.matchers.Matchers.anyOf;
2121
import static com.google.errorprone.matchers.Matchers.staticMethod;
22+
import static com.google.errorprone.suppliers.Suppliers.INT_TYPE;
23+
import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;
24+
import static com.google.errorprone.suppliers.Suppliers.arrayOf;
2225

2326
import com.google.common.collect.Iterables;
2427
import com.google.errorprone.BugPattern;
@@ -46,10 +49,12 @@ public class ArrayFillIncompatibleType extends BugChecker implements MethodInvoc
4649
anyOf(
4750
staticMethod()
4851
.onClass("java.util.Arrays")
49-
.withSignature("fill(java.lang.Object[],java.lang.Object)"),
52+
.named("fill")
53+
.withParametersOfType(arrayOf(OBJECT_TYPE), OBJECT_TYPE),
5054
staticMethod()
5155
.onClass("java.util.Arrays")
52-
.withSignature("fill(java.lang.Object[],int,int,java.lang.Object)"));
56+
.named("fill")
57+
.withParametersOfType(arrayOf(OBJECT_TYPE), INT_TYPE, INT_TYPE, OBJECT_TYPE));
5358

5459
@Override
5560
public Description matchMethodInvocation(

core/src/main/java/com/google/errorprone/bugpatterns/CollectionToArraySafeParameter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
2020
import static com.google.errorprone.matchers.Description.NO_MATCH;
2121
import static com.google.errorprone.matchers.Matchers.instanceMethod;
22+
import static com.google.errorprone.matchers.method.ParameterPredicates.arrayOf;
23+
import static com.google.errorprone.matchers.method.ParameterPredicates.onlyTypeParameter;
2224
import static com.google.errorprone.util.ASTHelpers.getType;
2325

2426
import com.google.errorprone.BugPattern;
@@ -47,7 +49,10 @@ public class CollectionToArraySafeParameter extends BugChecker
4749
implements MethodInvocationTreeMatcher {
4850

4951
private static final Matcher<ExpressionTree> TO_ARRAY_MATCHER =
50-
instanceMethod().onDescendantOf("java.util.Collection").withSignature("<T>toArray(T[])");
52+
instanceMethod()
53+
.onDescendantOf("java.util.Collection")
54+
.named("toArray")
55+
.withParametersMatching(arrayOf(onlyTypeParameter()));
5156

5257
@Override
5358
public Description matchMethodInvocation(

core/src/main/java/com/google/errorprone/bugpatterns/ExpectedExceptionChecker.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import static com.google.errorprone.matchers.Matchers.toType;
2929
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
3030
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
31+
import static com.google.errorprone.suppliers.Suppliers.CLASS_TYPE;
3132
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
3233
import static com.google.errorprone.util.ASTHelpers.getUpperBound;
3334
import static com.google.errorprone.util.ASTHelpers.isSubtype;
@@ -77,15 +78,10 @@ public class ExpectedExceptionChecker extends BugChecker implements MethodTreeMa
7778
.withNameMatching(Pattern.compile("expect.*")));
7879

7980
static final Matcher<ExpressionTree> IS_A =
80-
anyOf(
81-
staticMethod()
82-
.onClassAny(
83-
"org.hamcrest.Matchers", "org.hamcrest.CoreMatchers", "org.hamcrest.core.Is")
84-
.withSignature("<T>isA(java.lang.Class<T>)"),
85-
staticMethod()
86-
.onClassAny(
87-
"org.hamcrest.Matchers", "org.hamcrest.CoreMatchers", "org.hamcrest.core.Is")
88-
.withSignature("<T>isA(java.lang.Class<?>)"));
81+
staticMethod()
82+
.onClassAny("org.hamcrest.Matchers", "org.hamcrest.CoreMatchers", "org.hamcrest.core.Is")
83+
.named("isA")
84+
.withParametersOfType(CLASS_TYPE);
8985

9086
static final Matcher<StatementTree> FAIL_MATCHER =
9187
anyOf(

core/src/main/java/com/google/errorprone/bugpatterns/RobolectricShadowDirectlyOn.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
2121
import static com.google.errorprone.fixes.SuggestedFixes.qualifyType;
2222
import static com.google.errorprone.matchers.Description.NO_MATCH;
23+
import static com.google.errorprone.matchers.method.ParameterPredicates.onlyTypeParameter;
24+
import static com.google.errorprone.predicates.TypePredicates.isExactType;
25+
import static com.google.errorprone.suppliers.Suppliers.CLASS_TYPE;
2326
import static com.google.errorprone.util.ASTHelpers.getReceiver;
2427
import static com.google.errorprone.util.ASTHelpers.getSymbol;
2528
import static java.util.stream.Collectors.joining;
@@ -32,6 +35,7 @@
3235
import com.google.errorprone.matchers.Description;
3336
import com.google.errorprone.matchers.Matcher;
3437
import com.google.errorprone.matchers.method.MethodMatchers;
38+
import com.google.errorprone.matchers.method.ParameterPredicates;
3539
import com.sun.source.tree.ExpressionTree;
3640
import com.sun.source.tree.MemberSelectTree;
3741
import com.sun.source.tree.MethodInvocationTree;
@@ -49,7 +53,9 @@ public class RobolectricShadowDirectlyOn extends BugChecker implements MethodInv
4953
private static final Matcher<ExpressionTree> MATCHER =
5054
MethodMatchers.staticMethod()
5155
.onClass("org.robolectric.shadow.api.Shadow")
52-
.withSignature("<T>directlyOn(T,java.lang.Class<T>)");
56+
.named("directlyOn")
57+
.withParametersMatching(
58+
onlyTypeParameter(), ParameterPredicates.of(isExactType(CLASS_TYPE)));
5359

5460
@Override
5561
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {

core/src/main/java/com/google/errorprone/bugpatterns/UnnecessarySetDefault.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import static com.google.errorprone.matchers.Description.NO_MATCH;
2222
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
2323
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
24+
import static com.google.errorprone.suppliers.Suppliers.CLASS_TYPE;
25+
import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;
2426

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

7376
@VisibleForTesting
7477
static final ImmutableMap<String, Matcher<ExpressionTree>> DEFAULTS =

0 commit comments

Comments
 (0)