Skip to content

Commit e9497cd

Browse files
graememorganError Prone Team
authored andcommitted
Insert parens if the thing being substituted in may require them at the replacement site.
PiperOrigin-RevId: 733759318
1 parent 785b4b8 commit e9497cd

File tree

3 files changed

+109
-22
lines changed

3 files changed

+109
-22
lines changed

check_api/src/main/java/com/google/errorprone/util/OperatorPrecedence.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@
1717
package com.google.errorprone.util;
1818

1919
import com.sun.source.tree.Tree;
20+
import java.util.Optional;
2021

2122
/**
2223
* The precedence for an operator kind in the {@link com.sun.source.tree} API.
2324
*
2425
* <p>As documented at: http://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html
2526
*/
2627
public enum OperatorPrecedence {
27-
POSTFIX(13),
28-
UNARY(12),
28+
POSTFIX(14),
29+
UNARY(13),
30+
CAST(12),
2931
MULTIPLICATIVE(11),
3032
ADDITIVE(10),
3133
SHIFT(9),
@@ -50,20 +52,26 @@ public boolean isHigher(OperatorPrecedence other) {
5052
}
5153

5254
public static OperatorPrecedence from(Tree.Kind kind) {
55+
return optionallyFrom(kind)
56+
.orElseThrow(() -> new IllegalArgumentException("Unexpected operator kind: " + kind));
57+
}
58+
59+
public static Optional<OperatorPrecedence> optionallyFrom(Tree.Kind kind) {
5360
return switch (kind) {
54-
case POSTFIX_DECREMENT, POSTFIX_INCREMENT -> OperatorPrecedence.POSTFIX;
55-
case PREFIX_DECREMENT, PREFIX_INCREMENT -> OperatorPrecedence.UNARY;
56-
case MULTIPLY, DIVIDE, REMAINDER -> OperatorPrecedence.MULTIPLICATIVE;
57-
case PLUS, MINUS -> OperatorPrecedence.ADDITIVE;
58-
case RIGHT_SHIFT, UNSIGNED_RIGHT_SHIFT, LEFT_SHIFT -> OperatorPrecedence.SHIFT;
61+
case POSTFIX_DECREMENT, POSTFIX_INCREMENT -> Optional.of(OperatorPrecedence.POSTFIX);
62+
case PREFIX_DECREMENT, PREFIX_INCREMENT -> Optional.of(OperatorPrecedence.UNARY);
63+
case TYPE_CAST -> Optional.of(OperatorPrecedence.CAST);
64+
case MULTIPLY, DIVIDE, REMAINDER -> Optional.of(OperatorPrecedence.MULTIPLICATIVE);
65+
case PLUS, MINUS -> Optional.of(OperatorPrecedence.ADDITIVE);
66+
case RIGHT_SHIFT, UNSIGNED_RIGHT_SHIFT, LEFT_SHIFT -> Optional.of(OperatorPrecedence.SHIFT);
5967
case LESS_THAN, LESS_THAN_EQUAL, GREATER_THAN, GREATER_THAN_EQUAL, INSTANCE_OF ->
60-
OperatorPrecedence.RELATIONAL;
61-
case EQUAL_TO, NOT_EQUAL_TO -> OperatorPrecedence.EQUALITY;
62-
case AND -> OperatorPrecedence.AND;
63-
case XOR -> OperatorPrecedence.XOR;
64-
case OR -> OperatorPrecedence.OR;
65-
case CONDITIONAL_AND -> OperatorPrecedence.CONDITIONAL_AND;
66-
case CONDITIONAL_OR -> OperatorPrecedence.CONDITIONAL_OR;
68+
Optional.of(OperatorPrecedence.RELATIONAL);
69+
case EQUAL_TO, NOT_EQUAL_TO -> Optional.of(OperatorPrecedence.EQUALITY);
70+
case AND -> Optional.of(OperatorPrecedence.AND);
71+
case XOR -> Optional.of(OperatorPrecedence.XOR);
72+
case OR -> Optional.of(OperatorPrecedence.OR);
73+
case CONDITIONAL_AND -> Optional.of(OperatorPrecedence.CONDITIONAL_AND);
74+
case CONDITIONAL_OR -> Optional.of(OperatorPrecedence.CONDITIONAL_OR);
6775
case ASSIGNMENT,
6876
MULTIPLY_ASSIGNMENT,
6977
DIVIDE_ASSIGNMENT,
@@ -76,8 +84,8 @@ public static OperatorPrecedence from(Tree.Kind kind) {
7684
OR_ASSIGNMENT,
7785
RIGHT_SHIFT_ASSIGNMENT,
7886
UNSIGNED_RIGHT_SHIFT_ASSIGNMENT ->
79-
OperatorPrecedence.ASSIGNMENT;
80-
default -> throw new IllegalArgumentException("Unexpected operator kind: " + kind);
87+
Optional.of(OperatorPrecedence.ASSIGNMENT);
88+
default -> Optional.empty();
8189
};
8290
}
8391
}

core/src/main/java/com/google/errorprone/bugpatterns/inlineme/Inliner.java

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import static com.google.errorprone.util.ASTHelpers.stringContainsComments;
3030
import static com.google.errorprone.util.MoreAnnotations.getValue;
3131
import static com.google.errorprone.util.SideEffectAnalysis.hasSideEffect;
32+
import static java.lang.String.format;
3233
import static java.util.stream.Collectors.joining;
3334

3435
import com.google.auto.value.AutoValue;
@@ -49,13 +50,19 @@
4950
import com.google.errorprone.fixes.SuggestedFixes;
5051
import com.google.errorprone.matchers.Description;
5152
import com.google.errorprone.util.MoreAnnotations;
53+
import com.google.errorprone.util.OperatorPrecedence;
54+
import com.sun.source.tree.AssignmentTree;
5255
import com.sun.source.tree.ExpressionStatementTree;
5356
import com.sun.source.tree.ExpressionTree;
5457
import com.sun.source.tree.IdentifierTree;
5558
import com.sun.source.tree.MemberReferenceTree;
59+
import com.sun.source.tree.MemberSelectTree;
5660
import com.sun.source.tree.MethodInvocationTree;
5761
import com.sun.source.tree.NewClassTree;
5862
import com.sun.source.tree.Tree;
63+
import com.sun.source.tree.UnaryTree;
64+
import com.sun.source.tree.VariableTree;
65+
import com.sun.source.util.TreePath;
5966
import com.sun.source.util.TreeScanner;
6067
import com.sun.tools.javac.code.Attribute;
6168
import com.sun.tools.javac.code.Symbol.MethodSymbol;
@@ -65,6 +72,7 @@
6572
import com.sun.tools.javac.tree.JCTree;
6673
import java.util.ArrayList;
6774
import java.util.List;
75+
import java.util.Objects;
6876
import java.util.Optional;
6977
import java.util.function.BiConsumer;
7078
import java.util.regex.Matcher;
@@ -384,7 +392,12 @@ public int replaceTree(JCTree oldtree, JCTree newtree) {
384392
})
385393
.applyReplacements(replacementFixes.build());
386394

387-
fixBuilder.replace(replacementStart, replacementEnd, fixedReplacement);
395+
fixBuilder.replace(
396+
replacementStart,
397+
replacementEnd,
398+
inliningRequiresParentheses(state.getPath(), replacementExpression)
399+
? format("(%s)", fixedReplacement)
400+
: fixedReplacement);
388401

389402
return maybeCheckFixCompiles(tree, state, fixBuilder.build(), api);
390403
}
@@ -397,6 +410,73 @@ private static List<? extends ExpressionTree> getArguments(Tree tree) {
397410
};
398411
}
399412

413+
/**
414+
* Checks whether an expression requires parentheses when substituting in.
415+
*
416+
* <p>{@code treePath} is the original path including the old tree at the tip; {@code replacement}
417+
* is the proposed replacement tree.
418+
*
419+
* <p>This was originally from {@link com.google.errorprone.util.ASTHelpers#requiresParentheses}
420+
* but is heavily specialised for this use case.
421+
*/
422+
private static boolean inliningRequiresParentheses(
423+
TreePath treePath, ExpressionTree replacement) {
424+
var originalExpression = treePath.getLeaf();
425+
var parent = treePath.getParentPath().getLeaf();
426+
427+
Optional<OperatorPrecedence> replacementPrecedence =
428+
OperatorPrecedence.optionallyFrom(replacement.getKind());
429+
Optional<OperatorPrecedence> parentPrecedence =
430+
OperatorPrecedence.optionallyFrom(parent.getKind());
431+
if (replacementPrecedence.isPresent() && parentPrecedence.isPresent()) {
432+
return parentPrecedence.get().isHigher(replacementPrecedence.get());
433+
}
434+
435+
// There are some locations, based on the parent path, where we never want to parenthesise.
436+
// This list is likely not exhaustive.
437+
switch (parent.getKind()) {
438+
case RETURN, EXPRESSION_STATEMENT -> {
439+
return false;
440+
}
441+
case VARIABLE -> {
442+
if (Objects.equals(((VariableTree) parent).getInitializer(), originalExpression)) {
443+
return false;
444+
}
445+
}
446+
case ASSIGNMENT -> {
447+
if (((AssignmentTree) parent).getExpression().equals(originalExpression)) {
448+
return false;
449+
}
450+
}
451+
case METHOD_INVOCATION, NEW_CLASS -> {
452+
if (getArguments(parent).contains(originalExpression)) {
453+
return false;
454+
}
455+
}
456+
default -> {
457+
// continue below
458+
}
459+
}
460+
switch (replacement.getKind()) {
461+
case IDENTIFIER,
462+
MEMBER_SELECT,
463+
METHOD_INVOCATION,
464+
ARRAY_ACCESS,
465+
PARENTHESIZED,
466+
NEW_CLASS,
467+
MEMBER_REFERENCE -> {
468+
return false;
469+
}
470+
default -> {
471+
// continue below
472+
}
473+
}
474+
if (replacement instanceof UnaryTree) {
475+
return parent instanceof MemberSelectTree;
476+
}
477+
return true;
478+
}
479+
400480
private Description maybeCheckFixCompiles(
401481
ExpressionTree tree, VisitorState state, SuggestedFix fix, Api api) {
402482
if (checkFixCompiles && fix.getImportsToAdd().isEmpty()) {
@@ -489,7 +569,7 @@ final String message() {
489569

490570
/** Returns {@code FullyQualifiedClassName#methodName}. */
491571
final String methodId() {
492-
return String.format("%s#%s", className(), methodName());
572+
return format("%s#%s", className(), methodName());
493573
}
494574

495575
/**
@@ -498,7 +578,7 @@ final String methodId() {
498578
*/
499579
final String shortName() {
500580
String humanReadableClassName = className().replaceFirst(packageName() + ".", "");
501-
return String.format("`%s.%s()`", humanReadableClassName, methodName());
581+
return format("`%s.%s()`", humanReadableClassName, methodName());
502582
}
503583

504584
/** Returns the simple class name (e.g., {@code ClassName}). */

core/src/test/java/com/google/errorprone/bugpatterns/inlineme/InlinerTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,7 +1226,7 @@ public void doTest() {
12261226
public final class Caller {
12271227
public void doTest() {
12281228
Client client = new Client();
1229-
int x = 1 + 2 * 3;
1229+
int x = (1 + 2) * 3;
12301230
int y = 1 + 2 + 3;
12311231
}
12321232
}
@@ -1877,13 +1877,12 @@ void test(String x) {
18771877
}
18781878
}
18791879
""")
1880-
// Broken! The inlined code requires parens.
18811880
.addOutputLines(
18821881
"Test.java",
18831882
"""
18841883
class Test {
18851884
void test(String x) {
1886-
String abn = x + "b".repeat(10);
1885+
String abn = (x + "b").repeat(10);
18871886
}
18881887
}
18891888
""")

0 commit comments

Comments
 (0)