Skip to content

Commit e9497cd

Browse files
graememorganError Prone Team
authored and
Error Prone Team
committed
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)