From 924a0ee07fc3f1801f4519c3367b88d40c33d7fd Mon Sep 17 00:00:00 2001 From: Harsh Mehta Date: Fri, 19 Jun 2026 12:34:28 +0530 Subject: [PATCH] Fix matcher parenthesis-invariance by stripping ParenthesizedTree nodes Signed-off-by: Harsh Mehta --- .../bugpatterns/AssignmentExpression.java | 14 +- .../bugpatterns/DuplicateBranches.java | 6 +- .../bugpatterns/InlineTrivialConstant.java | 3 +- .../bugpatterns/LoopConditionChecker.java | 2 +- .../bugpatterns/NonAtomicVolatileUpdate.java | 7 +- .../PatternMatchingInstanceof.java | 3 +- .../bugpatterns/AssignmentExpressionTest.java | 151 ++++++++++++++++ .../bugpatterns/DuplicateBranchesTest.java | 125 ++++++++++++++ .../InlineTrivialConstantTest.java | 83 +++++++++ .../bugpatterns/LoopConditionCheckerTest.java | 163 ++++++++++++++++++ .../NonAtomicVolatileUpdateTest.java | 140 +++++++++++++++ .../PatternMatchingInstanceofTest.java | 122 +++++++++++++ 12 files changed, 808 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AssignmentExpression.java b/core/src/main/java/com/google/errorprone/bugpatterns/AssignmentExpression.java index 2b945778185..ab0a64018ad 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AssignmentExpression.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AssignmentExpression.java @@ -33,6 +33,7 @@ import com.sun.source.tree.ParenthesizedTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Symbol.VarSymbol; /** A BugPattern; see the summary. */ @@ -55,18 +56,23 @@ public Description matchAssignment(AssignmentTree tree, VisitorState state) { if (parent instanceof LambdaExpressionTree) { return Description.NO_MATCH; } + // Walk through any enclosing parentheses to find the effective parent context. + TreePath effectiveParentPath = state.getPath().getParentPath(); + while (effectiveParentPath.getLeaf() instanceof ParenthesizedTree) { + effectiveParentPath = effectiveParentPath.getParentPath(); + } + Tree effectiveParent = effectiveParentPath.getLeaf(); // Exempt the C-ism of (foo = getFoo()) != null, etc. - if (parent instanceof ParenthesizedTree - && state.getPath().getParentPath().getParentPath().getLeaf() instanceof BinaryTree) { + if (effectiveParent instanceof BinaryTree) { return Description.NO_MATCH; } // Detect duplicate assignments: a = a = foo() so that we can generate a fix. - if (isDuplicateAssignment(tree, parent)) { + if (isDuplicateAssignment(tree, effectiveParent)) { return describeMatch( tree, SuggestedFix.replace(tree, state.getSourceForNode(tree.getExpression()))); } // If we got here it's something like x = y = 0, which is odd but not disallowed. - if (parent instanceof AssignmentTree) { + if (effectiveParent instanceof AssignmentTree) { return Description.NO_MATCH; } return describeMatch(tree); diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/DuplicateBranches.java b/core/src/main/java/com/google/errorprone/bugpatterns/DuplicateBranches.java index 64a751d27d1..9e5beff0af1 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/DuplicateBranches.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/DuplicateBranches.java @@ -20,6 +20,7 @@ import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.util.ASTHelpers.getStartPosition; +import static com.google.errorprone.util.ASTHelpers.stripParentheses; import static java.util.stream.Collectors.joining; import com.google.errorprone.BugPattern; @@ -31,6 +32,7 @@ import com.google.errorprone.util.ErrorProneTokens; import com.sun.source.tree.BlockTree; import com.sun.source.tree.ConditionalExpressionTree; +import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IfTree; import com.sun.source.tree.Tree; @@ -66,7 +68,9 @@ private Description match(Tree tree, Tree thenTree, Tree elseTree, VisitorState // do exactly what we want here, which is to compare the syntax including of identifiers and // not their underlying symbols, and it would require a lot of case work to implement for all // AST nodes. - if (!thenTree.toString().equals(elseTree.toString())) { + Tree strippedThen = thenTree instanceof ExpressionTree et ? stripParentheses(et) : thenTree; + Tree strippedElse = elseTree instanceof ExpressionTree et ? stripParentheses(et) : elseTree; + if (!strippedThen.toString().equals(strippedElse.toString())) { return NO_MATCH; } int start = getStartPosition(elseTree); diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/InlineTrivialConstant.java b/core/src/main/java/com/google/errorprone/bugpatterns/InlineTrivialConstant.java index 730e8f7dddd..5eb82d213e1 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/InlineTrivialConstant.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/InlineTrivialConstant.java @@ -20,6 +20,7 @@ import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.util.ASTHelpers.getSymbol; import static com.google.errorprone.util.ASTHelpers.isSameType; +import static com.google.errorprone.util.ASTHelpers.stripParentheses; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ListMultimap; @@ -119,7 +120,7 @@ private static Optional isTrivialConstant( if (!isSameType(sym.asType(), state.getSymtab().stringType, state)) { return Optional.empty(); } - ExpressionTree initializer = tree.getInitializer(); + ExpressionTree initializer = stripParentheses(tree.getInitializer()); if (initializer.getKind().equals(Tree.Kind.STRING_LITERAL)) { String value = (String) ((LiteralTree) initializer).getValue(); if (Objects.equals(value, "")) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/LoopConditionChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/LoopConditionChecker.java index b9234056616..0183e6c6600 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/LoopConditionChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/LoopConditionChecker.java @@ -187,7 +187,7 @@ public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void unused) { } private void check(ExpressionTree expression) { - Symbol sym = ASTHelpers.getSymbol(expression); + Symbol sym = ASTHelpers.getSymbol(ASTHelpers.stripParentheses(expression)); modified |= variables.contains(sym); } } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/NonAtomicVolatileUpdate.java b/core/src/main/java/com/google/errorprone/bugpatterns/NonAtomicVolatileUpdate.java index d454912f1f1..be7f9d644a2 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/NonAtomicVolatileUpdate.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/NonAtomicVolatileUpdate.java @@ -28,6 +28,7 @@ import static com.google.errorprone.matchers.Matchers.toType; import com.google.errorprone.BugPattern; +import com.google.errorprone.util.ASTHelpers; import com.google.errorprone.BugPattern.StandardTags; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker.AssignmentTreeMatcher; @@ -55,7 +56,7 @@ public class NonAtomicVolatileUpdate extends BugChecker /** Extracts the expression from a UnaryTree and applies a matcher to it. */ private static Matcher expressionFromUnaryTree(Matcher exprMatcher) { return (UnaryTree tree, VisitorState state) -> { - return exprMatcher.matches(tree.getExpression(), state); + return exprMatcher.matches(ASTHelpers.stripParentheses(tree.getExpression()), state); }; } @@ -63,7 +64,7 @@ private static Matcher expressionFromUnaryTree(Matcher variableFromCompoundAssignmentTree( Matcher exprMatcher) { return (CompoundAssignmentTree tree, VisitorState state) -> { - return exprMatcher.matches(tree.getVariable(), state); + return exprMatcher.matches(ASTHelpers.stripParentheses(tree.getVariable()), state); }; } @@ -71,7 +72,7 @@ private static Matcher variableFromCompoundAssignmentTre private static Matcher variableFromAssignmentTree( Matcher exprMatcher) { return (AssignmentTree tree, VisitorState state) -> { - return exprMatcher.matches(tree.getVariable(), state); + return exprMatcher.matches(ASTHelpers.stripParentheses(tree.getVariable()), state); }; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/PatternMatchingInstanceof.java b/core/src/main/java/com/google/errorprone/bugpatterns/PatternMatchingInstanceof.java index fd46161dbe7..dd190854b4f 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/PatternMatchingInstanceof.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/PatternMatchingInstanceof.java @@ -23,6 +23,7 @@ import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.util.ASTHelpers.getSymbol; import static com.google.errorprone.util.ASTHelpers.getType; +import static com.google.errorprone.util.ASTHelpers.stripParentheses; import static com.google.errorprone.util.SourceVersion.supportsPatternMatchingInstanceof; import static com.google.errorprone.util.TargetType.targetType; import static java.lang.Boolean.TRUE; @@ -278,7 +279,7 @@ private ImmutableSet findAllCasts( new TreePathScanner() { @Override public Void visitTypeCast(TypeCastTree node, Void unused) { - var castee = constantExpressions.constantExpression(node.getExpression(), state); + var castee = constantExpressions.constantExpression(stripParentheses(node.getExpression()), state); if (castee.isPresent() && castee.get().equals(symbol) && state.getTypes().isSameType(getType(node.getType()), targetType)) { diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/AssignmentExpressionTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/AssignmentExpressionTest.java index 37c250ac7ba..fcb6caca4b3 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/AssignmentExpressionTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/AssignmentExpressionTest.java @@ -130,6 +130,157 @@ void test() { .doTest(); } + @Test + public void multipleAssignments_parenthesizedInner_noFinding() { + helper + .addSourceLines( + "Test.java", + """ + class Test { + void test() { + Object a; + Object b; + a = (b = new Object()); + } + } + """) + .doTest(); + } + + @Test + public void comparedToNull_innerParenthesized_noFinding() { + helper + .addSourceLines( + "Test.java", + """ + class Test { + void test() { + Object a; + Object b; + if ((a = (b = new Object())) != null) { + return; + } + } + } + """) + .doTest(); + } + + @Test + public void initializerWithParenthesizedAssignment_finding() { + helper + .addSourceLines( + "Test.java", + """ + class Test { + void test() { + int j; + // BUG: Diagnostic contains: + int i = (j = 0); + } + } + """) + .doTest(); + } + + @Test + public void deeplyChainedAssignments_noFinding() { + helper + .addSourceLines( + "Test.java", + """ + class Test { + void test() { + Object a, b, c; + a = (b = (c = new Object())); + } + } + """) + .doTest(); + } + + @Test + public void deeplyChainedInitializer_innerFinding() { + helper + .addSourceLines( + "Test.java", + """ + class Test { + void test() { + int k; + int j; + // BUG: Diagnostic contains: + int i = (j = (k = 0)); + } + } + """) + .doTest(); + } + + @Test + public void duplicateAssignment_throughParens() { + refactoringHelper + .addInputLines( + "Test.java", + """ + class Test { + void test() { + // BUG: Diagnostic contains: + Object a = (a = new Object()); + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + void test() { + // BUG: Diagnostic contains: + Object a = (new Object()); + } + } + """) + .doTest(); + } + + @Test + public void doubleParenthesizedBinaryContext_noFinding() { + helper + .addSourceLines( + "Test.java", + """ + class Test { + Object a; + Object b; + + void test() { + if (((a = b)) != null) { + return; + } + } + } + """) + .doTest(); + } + + @Test + public void returnedParenthesized_finding() { + helper + .addSourceLines( + "Test.java", + """ + class Test { + Object field; + + Object test() { + // BUG: Diagnostic contains: + return (field = new Object()); + } + } + """) + .doTest(); + } + @Test public void returnedValue() { helper diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/DuplicateBranchesTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/DuplicateBranchesTest.java index d7c485e5aee..b324fe5d417 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/DuplicateBranchesTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/DuplicateBranchesTest.java @@ -53,6 +53,131 @@ String g(boolean a, String b, String c) { .doTest(); } + @Test + public void positive_parenthesizedBranch() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + int f() { + // BUG: Diagnostic contains: + return true ? 1 * 5 : (1 * 5); + } + } + """) + .doTest(); + } + + @Test + public void positive_parenthesizedThenBranch() { + BugCheckerRefactoringTestHelper.newInstance(DuplicateBranches.class, getClass()) + .addInputLines( + "Test.java", + """ + class Test { + int x; + + void m() { + x = true ? (1 * 5) : 1 * 5; + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + int x; + + void m() { + x = 1 * 5; + } + } + """) + .doTest(); + } + + @Test + public void positive_bothBranchesParenthesized() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + int x; + + void m() { + // BUG: Diagnostic contains: + x = true ? (1 * 5) : (1 * 5); + } + } + """) + .doTest(); + } + + @Test + public void positive_parenthesizedStringBranch() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + String f(boolean a) { + // BUG: Diagnostic contains: + return a ? "hello" : ("hello"); + } + } + """) + .doTest(); + } + + @Test + public void positive_parenthesizedVariableBranch() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + String f(boolean a, String x) { + // BUG: Diagnostic contains: + return a ? x : (x); + } + } + """) + .doTest(); + } + + @Test + public void positive_parenthesizedMethodCallBranch() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + String f(boolean a) { + // BUG: Diagnostic contains: + return a ? toString() : (toString()); + } + } + """) + .doTest(); + } + + @Test + public void negative_differentContentAfterStripping() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + int f(boolean a, int x) { + return a ? (x + 1) : (x - 1); + } + } + """) + .doTest(); + } + @Test public void negative() { compilationHelper diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/InlineTrivialConstantTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/InlineTrivialConstantTest.java index 5df1c974165..4dd6a755166 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/InlineTrivialConstantTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/InlineTrivialConstantTest.java @@ -51,6 +51,89 @@ void f() { .doTest(); } + @Test + public void positive_parenthesizedInitializer() { + BugCheckerRefactoringTestHelper.newInstance(InlineTrivialConstant.class, getClass()) + .addInputLines( + "Test.java", + """ + class Test { + private static final String EMPTY = (""); + + void f() { + System.err.println(EMPTY); + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + + void f() { + System.err.println(""); + } + } + """) + .doTest(); + } + + @Test + public void positive_doubleParenthesizedInitializer() { + BugCheckerRefactoringTestHelper.newInstance(InlineTrivialConstant.class, getClass()) + .addInputLines( + "Test.java", + """ + class Test { + private static final String EMPTY = (("")); + + void f() { + System.err.println(EMPTY); + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + + void f() { + System.err.println(""); + } + } + """) + .doTest(); + } + + @Test + public void positive_otherValidNamesParenthesized() { + CompilationTestHelper.newInstance(InlineTrivialConstant.class, getClass()) + .addSourceLines( + "Test.java", + """ + class Test { + // BUG: Diagnostic contains: + private static final String EMPTY_STR = (""); + // BUG: Diagnostic contains: + private static final String EMPTY_STRING = (""); + } + """) + .doTest(); + } + + @Test + public void negative_nonEmptyParenthesized() { + CompilationTestHelper.newInstance(InlineTrivialConstant.class, getClass()) + .addSourceLines( + "Test.java", + """ + class Test { + private static final String EMPTY = ("hello"); + } + """) + .doTest(); + } + @Test public void negative() { CompilationTestHelper.newInstance(InlineTrivialConstant.class, getClass()) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/LoopConditionCheckerTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/LoopConditionCheckerTest.java index 170f06a4e7d..65e6ae08dc1 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/LoopConditionCheckerTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/LoopConditionCheckerTest.java @@ -175,6 +175,169 @@ void f() { .doTest(); } + @Test + public void negative_parenthesizedUpdate() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + for (int i = 0; i < 10; (i)++) {} + } + } + """) + .doTest(); + } + + @Test + public void negative_doubleParenthesizedUpdate() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + for (int i = 0; i < 10; ((i))++) {} + } + } + """) + .doTest(); + } + + @Test + public void negative_parenthesizedPrefixIncrement() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + for (int i = 0; i < 10; ++(i)) {} + } + } + """) + .doTest(); + } + + @Test + public void negative_parenthesizedPrefixDecrement() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + for (int i = 9; i >= 0; --(i)) {} + } + } + """) + .doTest(); + } + + @Test + public void negative_parenthesizedPostfixDecrement() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + for (int i = 9; i >= 0; (i)--) {} + } + } + """) + .doTest(); + } + + @Test + public void negative_parenthesizedDoWhileBody() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + int i = 0; + do { + (i)++; + } while (i < 10); + } + } + """) + .doTest(); + } + + @Test + public void negative_parenthesizedWhileBody() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + int i = 0; + while (i < 10) { + (i)++; + } + } + } + """) + .doTest(); + } + + @Test + public void negative_multipleConditionVarsParenthesized() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + int i, j; + for (i = 0, j = 0; i < 10 && j < 5; (i)++, (j)++) {} + } + } + """) + .doTest(); + } + + @Test + public void negative_parenthesizedCompoundAssignmentInBody() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + int i = 0; + while (i < 10) { + (i) += 1; + } + } + } + """) + .doTest(); + } + + @Test + public void negative_parenthesizedAssignmentInBody() { + compilationTestHelper + .addSourceLines( + "Test.java", + """ + class Test { + void f() { + int i = 0; + while (i < 10) { + (i) = i + 1; + } + } + } + """) + .doTest(); + } + @Test public void negative_field() { compilationTestHelper diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/NonAtomicVolatileUpdateTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/NonAtomicVolatileUpdateTest.java index 20d83eb3027..e5f45fa97c1 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/NonAtomicVolatileUpdateTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/NonAtomicVolatileUpdateTest.java @@ -108,6 +108,146 @@ public void stringUpdate() { .doTest(); } + @Test + public void parenthesizedOperand_positive() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + volatile int x; + volatile long y; + + void bad() { + // BUG: Diagnostic contains: + (x)++; + // BUG: Diagnostic contains: + (y)++; + // BUG: Diagnostic contains: + (x)--; + // BUG: Diagnostic contains: + (y)--; + } + } + """) + .doTest(); + } + + @Test + public void parenthesizedPrefixIncrementDecrement_positive() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + volatile int x; + + void bad() { + // BUG: Diagnostic contains: + ++(x); + // BUG: Diagnostic contains: + --(x); + } + } + """) + .doTest(); + } + + @Test + public void doubleParenthesized_positive() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + volatile int x; + + void bad() { + // BUG: Diagnostic contains: + ((x))++; + } + } + """) + .doTest(); + } + + @Test + public void parenthesizedCompoundAssignment_positive() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + volatile int x; + volatile long y; + + void bad() { + // BUG: Diagnostic contains: + (x) += 1; + // BUG: Diagnostic contains: + (y) -= 1; + } + } + """) + .doTest(); + } + + @Test + public void parenthesizedNonVolatile_negative() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + int nonVolatile; + + void ok() { + (nonVolatile)++; + } + } + """) + .doTest(); + } + + @Test + public void parenthesizedSynchronized_negative() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + volatile int x; + + void ok() { + synchronized (this) { + (x)++; + ++(x); + (x) += 1; + } + } + } + """) + .doTest(); + } + + @Test + public void parenthesizedVolatileString_positive() { + compilationHelper + .addSourceLines( + "Test.java", + """ + class Test { + volatile String s; + + void bad() { + // BUG: Diagnostic contains: + (s) += "update"; + } + } + """) + .doTest(); + } + @Test public void negativeCases() { compilationHelper diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/PatternMatchingInstanceofTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/PatternMatchingInstanceofTest.java index 9f00cbbf847..45e117af67c 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/PatternMatchingInstanceofTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/PatternMatchingInstanceofTest.java @@ -86,6 +86,128 @@ void test(Object o) { .doTest(); } + @Test + public void seesThroughParensOnCastOperand() { + helper + .addInputLines( + "Test.java", + """ + class Test { + int test(Object o) { + if (o instanceof String) { + String s = (String)(o); + return s.length(); + } + return o.hashCode(); + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + int test(Object o) { + if (o instanceof String s) { + + return s.length(); + } + return o.hashCode(); + } + } + """) + .doTest(); + } + + @Test + public void seesThroughDoubleParensOnCastOperand() { + helper + .addInputLines( + "Test.java", + """ + class Test { + int test(Object o) { + if (o instanceof String) { + String s = (String)((o)); + return s.length(); + } + return o.hashCode(); + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + int test(Object o) { + if (o instanceof String s) { + + return s.length(); + } + return o.hashCode(); + } + } + """) + .doTest(); + } + + @Test + public void parenthesizedOperandNegatedIfElse() { + helper + .addInputLines( + "Test.java", + """ + class Test { + int test(Object o) { + if (!(o instanceof String)) { + } else { + String s = (String)(o); + return s.length(); + } + return o.hashCode(); + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + int test(Object o) { + if (!(o instanceof String s)) { + } else { + + return s.length(); + } + return o.hashCode(); + } + } + """) + .doTest(); + } + + @Test + public void parenthesizedOperandInTernary() { + helper + .addInputLines( + "Test.java", + """ + class Test { + int test(Object o) { + return o instanceof String ? ((String)(o)).length() : 0; + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + int test(Object o) { + return o instanceof String string ? string.length() : 0; + } + } + """) + .doTest(); + } + @Test public void seesThroughParens() { helper