|
16 | 16 |
|
17 | 17 | package com.google.errorprone.bugpatterns;
|
18 | 18 |
|
| 19 | +import static com.google.common.base.Preconditions.checkArgument; |
19 | 20 | import static com.google.common.base.Suppliers.memoize;
|
| 21 | +import static com.google.common.collect.ImmutableList.toImmutableList; |
20 | 22 | import static com.google.common.collect.Multimaps.toMultimap;
|
21 | 23 | import static com.google.errorprone.fixes.SuggestedFix.delete;
|
22 |
| -import static com.google.errorprone.fixes.SuggestedFix.emptyFix; |
23 | 24 | import static com.google.errorprone.fixes.SuggestedFix.prefixWith;
|
24 | 25 | import static com.google.errorprone.matchers.Description.NO_MATCH;
|
25 | 26 | import static com.google.errorprone.matchers.Matchers.allOf;
|
|
35 | 36 | import static com.google.errorprone.util.ASTHelpers.getUpperBound;
|
36 | 37 | import static com.google.errorprone.util.ASTHelpers.isSubtype;
|
37 | 38 | import static com.google.errorprone.util.ASTHelpers.isVoidType;
|
| 39 | +import static com.sun.source.tree.Tree.Kind.EXPRESSION_STATEMENT; |
| 40 | +import static com.sun.source.tree.Tree.Kind.METHOD_INVOCATION; |
| 41 | +import static com.sun.source.tree.Tree.Kind.NEW_CLASS; |
38 | 42 | import static java.lang.String.format;
|
39 | 43 |
|
| 44 | +import com.google.common.collect.ImmutableList; |
40 | 45 | import com.google.common.collect.ImmutableListMultimap;
|
41 | 46 | import com.google.common.collect.ImmutableMap;
|
42 | 47 | import com.google.common.collect.ImmutableSet;
|
|
50 | 55 | import com.google.errorprone.bugpatterns.BugChecker.ReturnTreeMatcher;
|
51 | 56 | import com.google.errorprone.bugpatterns.threadsafety.ConstantExpressions;
|
52 | 57 | import com.google.errorprone.fixes.Fix;
|
| 58 | +import com.google.errorprone.fixes.SuggestedFix; |
53 | 59 | import com.google.errorprone.matchers.Description;
|
54 | 60 | import com.google.errorprone.matchers.Matcher;
|
55 | 61 | import com.google.errorprone.matchers.UnusedReturnValueMatcher;
|
56 |
| -import com.sun.source.tree.ExpressionStatementTree; |
57 | 62 | import com.sun.source.tree.ExpressionTree;
|
58 | 63 | import com.sun.source.tree.LambdaExpressionTree;
|
59 | 64 | import com.sun.source.tree.MemberReferenceTree;
|
@@ -208,39 +213,67 @@ protected boolean allowInExceptionThrowers() {
|
208 | 213 | protected Description describeReturnValueIgnored(
|
209 | 214 | MethodInvocationTree methodInvocationTree, VisitorState state) {
|
210 | 215 | return buildDescription(methodInvocationTree)
|
211 |
| - .addFix(makeFix(methodInvocationTree, state)) |
| 216 | + .addAllFixes(fixesAtCallSite(methodInvocationTree, state)) |
212 | 217 | .setMessage(getMessage(getSymbol(methodInvocationTree).getSimpleName()))
|
213 | 218 | .build();
|
214 | 219 | }
|
215 | 220 |
|
216 |
| - final Fix makeFix(MethodInvocationTree methodInvocationTree, VisitorState state) { |
217 |
| - Type returnType = getType(methodInvocationTree); |
| 221 | + final ImmutableList<Fix> fixesAtCallSite(ExpressionTree invocationTree, VisitorState state) { |
| 222 | + checkArgument( |
| 223 | + invocationTree.getKind() == METHOD_INVOCATION || invocationTree.getKind() == NEW_CLASS, |
| 224 | + "unexpected kind: %s", |
| 225 | + invocationTree.getKind()); |
| 226 | + |
| 227 | + Tree parent = state.getPath().getParentPath().getLeaf(); |
| 228 | + |
| 229 | + Type resultType = getType(invocationTree); |
218 | 230 | // Find the root of the field access chain, i.e. a.intern().trim() ==> a.
|
219 | 231 | /*
|
220 | 232 | * TODO(cpovirk): Enhance getRootAssignable to return array accesses (e.g., `x[y]`)? If we do,
|
221 | 233 | * then we'll also need to accept `symbol == null` (which is fine, since all we need the symbol
|
222 | 234 | * for is to check against `this`, and `x[y]` is not `this`.)
|
223 | 235 | */
|
224 |
| - ExpressionTree identifierExpr = getRootAssignable(methodInvocationTree); |
| 236 | + ExpressionTree identifierExpr = |
| 237 | + invocationTree.getKind() == METHOD_INVOCATION |
| 238 | + ? getRootAssignable((MethodInvocationTree) invocationTree) |
| 239 | + : null; // null root assignable for constructor calls (as well as some method calls) |
225 | 240 | Symbol symbol = getSymbol(identifierExpr);
|
226 | 241 | Type identifierType = getType(identifierExpr);
|
227 | 242 |
|
| 243 | + /* |
| 244 | + * A map from short description to fix instance (even though every short description ultimately |
| 245 | + * will become _part of_ a fix instance later). |
| 246 | + * |
| 247 | + * As always, the order of suggested fixes can matter. In practice, it probably matters mostly |
| 248 | + * just to the checker's own tests. But it also affects the order in which the fixes are printed |
| 249 | + * during compile errors, and it affects which fix is chosen for automatically generated fix CLs |
| 250 | + * (though those should be rare inside Google: b/244334502#comment13). |
| 251 | + * |
| 252 | + * Note that, when possible, we have separate code that suggests adding @CanIgnoreReturnValue in |
| 253 | + * preference to all the fixes below. |
| 254 | + * |
| 255 | + * The _names_ of the fixes probably don't actually matter inside Google: b/204435834#comment4. |
| 256 | + * Luckily, they're not a ton harder to include than plain code comments would be. |
| 257 | + */ |
| 258 | + ImmutableMap.Builder<String, SuggestedFix> fixes = ImmutableMap.builder(); |
228 | 259 | if (identifierExpr != null
|
229 | 260 | && symbol != null
|
230 | 261 | && !symbol.name.contentEquals("this")
|
231 |
| - && returnType != null |
232 |
| - && state.getTypes().isAssignable(returnType, identifierType)) { |
233 |
| - // Fix by assigning the result of the call to the root receiver reference. |
234 |
| - return prefixWith(methodInvocationTree, state.getSourceForNode(identifierExpr) + " = "); |
235 |
| - } else { |
236 |
| - // Unclear what the programmer intended. Delete since we don't know what else to do. |
237 |
| - Tree parent = state.getPath().getParentPath().getLeaf(); |
238 |
| - if (parent instanceof ExpressionStatementTree |
239 |
| - && constantExpressions.constantExpression(methodInvocationTree, state).isPresent()) { |
240 |
| - return delete(parent); |
| 262 | + && resultType != null |
| 263 | + && state.getTypes().isAssignable(resultType, identifierType)) { |
| 264 | + fixes.put( |
| 265 | + "Assign result back to variable", |
| 266 | + prefixWith(invocationTree, state.getSourceForNode(identifierExpr) + " = ")); |
| 267 | + } |
| 268 | + if (parent.getKind() == EXPRESSION_STATEMENT) { |
| 269 | + if (constantExpressions.constantExpression(invocationTree, state).isPresent()) { |
| 270 | + fixes.put("Delete call", delete(parent)); |
241 | 271 | }
|
242 | 272 | }
|
243 |
| - return emptyFix(); |
| 273 | + return fixes.buildOrThrow().entrySet().stream() |
| 274 | + .map( |
| 275 | + e -> SuggestedFix.builder().merge(e.getValue()).setShortDescription(e.getKey()).build()) |
| 276 | + .collect(toImmutableList()); |
244 | 277 | }
|
245 | 278 |
|
246 | 279 | /**
|
|
0 commit comments