From 96abece6f3a970186cd38e9c266f58947f7a55e8 Mon Sep 17 00:00:00 2001 From: Harsh Mehta Date: Fri, 12 Jun 2026 09:46:52 +0530 Subject: [PATCH 1/3] Improve type handling in SuggestedFixes and add new tests for variable type replacement Signed-off-by: Harsh Mehta --- .../errorprone/fixes/SuggestedFixes.java | 4 +- .../bugpatterns/VarWithPrimitiveTest.java | 122 ++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFixes.java b/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFixes.java index 7c384c113a3..5fa79321507 100644 --- a/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFixes.java +++ b/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFixes.java @@ -1863,10 +1863,10 @@ public static String castTree(ExpressionTree expressionTree, String toType, Visi public static Optional replaceVariableType( VariableTree tree, String replacementType, VisitorState state) { Tree type = tree.getType(); - if (hasExplicitSource(type, state)) { + if (type != null && hasExplicitSource(type, state)) { return Optional.of(SuggestedFix.replace(type, replacementType)); } - int pos = getStartPosition(type); + int pos = type != null ? getStartPosition(type) : Position.NOPOS; if (pos == Position.NOPOS) { pos = getStartPosition(tree); } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java index 1eab280f126..1673c8ad8f1 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java @@ -17,6 +17,7 @@ package com.google.errorprone.bugpatterns; import com.google.errorprone.BugCheckerRefactoringTestHelper; +import com.google.errorprone.CompilationTestHelper; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -27,6 +28,9 @@ public final class VarWithPrimitiveTest { private final BugCheckerRefactoringTestHelper refactoringHelper = BugCheckerRefactoringTestHelper.newInstance(VarWithPrimitive.class, getClass()); + private final CompilationTestHelper compilationHelper = + CompilationTestHelper.newInstance(VarWithPrimitive.class, getClass()); + // from https://openjdk.org/projects/amber/guides/lvti-style-guide#G7 @Test public void lvtiExamples() { @@ -194,6 +198,124 @@ long getAgeAsLong() { .doTest(); } + @Test + public void enhancedForLoop() { + refactoringHelper + .addInputLines( + "Test.java", + """ + class Test { + void t() { + int[] arr = {1, 2, 3}; + for (var x : arr) { + System.out.println(x); + } + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + void t() { + int[] arr = {1, 2, 3}; + for (int x : arr) { + System.out.println(x); + } + } + } + """) + .doTest(); + } + + @Test + public void forLoopInitializer() { + refactoringHelper + .addInputLines( + "Test.java", + """ + class Test { + void t() { + for (var i = 0; i < 10; i++) { + System.out.println(i); + } + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + void t() { + for (int i = 0; i < 10; i++) { + System.out.println(i); + } + } + } + """) + .doTest(); + } + + @Test + public void implicitLambdaParameter_noMatch() { + refactoringHelper + .addInputLines( + "Test.java", + """ + import java.util.List; + import java.util.Map; + import java.util.stream.Collectors; + import java.util.stream.IntStream; + class Test { + void foo() { + byte[] bar = new byte[6]; + Map> indicesMap = + IntStream.range(0, 6) + .mapToObj(n -> n) + .collect( + Collectors.groupingBy( + n -> bar[n], + Collectors.mapping( + n -> (byte) n.intValue(), Collectors.toList()))); + } + } + """) + .expectUnchanged() + .doTest(); + } + + @Test + public void multiParamImplicitLambda_noMatch() { + compilationHelper + .addSourceLines( + "Test.java", + """ + import java.util.function.IntBinaryOperator; + class Test { + void t() { + IntBinaryOperator op = (a, b) -> a + b; + } + } + """) + .doTest(); + } + + @Test + public void explicitLambdaParam_noMatch() { + compilationHelper + .addSourceLines( + "Test.java", + """ + import java.util.function.IntUnaryOperator; + class Test { + void t() { + IntUnaryOperator op = (int n) -> n * 2; + } + } + """) + .doTest(); + } + @Test public void explicitType() { refactoringHelper From 2247948f3fe21aeab4d3b01ef65653ec67d9953b Mon Sep 17 00:00:00 2001 From: Harsh Mehta Date: Fri, 12 Jun 2026 14:54:56 +0530 Subject: [PATCH 2/3] Add tests for var lambda parameters and annotated var handling Signed-off-by: Harsh Mehta --- .../bugpatterns/VarWithPrimitiveTest.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java index 1673c8ad8f1..de9a3090ca7 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java @@ -284,6 +284,80 @@ void foo() { .doTest(); } + @Test + public void varLambdaParam() { + refactoringHelper + .addInputLines( + "Test.java", + """ + import java.util.function.IntUnaryOperator; + class Test { + void t() { + IntUnaryOperator op = (var n) -> n * 2; + } + } + """) + .addOutputLines( + "Test.java", + """ + import java.util.function.IntUnaryOperator; + class Test { + void t() { + IntUnaryOperator op = (int n) -> n * 2; + } + } + """) + .doTest(); + } + + @Test + public void annotatedVarLambdaParam() { + refactoringHelper + .addInputLines("A.java", "@interface A { int var() default 0; }") + .expectUnchanged() + .addInputLines( + "Test.java", + """ + import java.util.function.IntUnaryOperator; + class Test { + void t() { + IntUnaryOperator op = (@A(var = 0) var n) -> n * 2; + } + } + """) + .addOutputLines( + "Test.java", + """ + import java.util.function.IntUnaryOperator; + class Test { + void t() { + IntUnaryOperator op = (@A(var = 0) int n) -> n * 2; + } + } + """) + .doTest(); + } + + @Test + public void annotatedVarLocalVariable() { + // The token scan finds two 'var' tokens (@A(var=0) attribute + type keyword) and safely + // returns no fix rather than risk replacing the annotation attribute instead of the type. + refactoringHelper + .addInputLines("A.java", "@interface A { int var() default 0; }") + .expectUnchanged() + .addInputLines( + "Test.java", + """ + class Test { + void t() { + @A(var = 0) var n = 5; + } + } + """) + .expectUnchanged() + .doTest(); + } + @Test public void multiParamImplicitLambda_noMatch() { compilationHelper From 58810695d420a1ccc867867dc643797e5a87d192 Mon Sep 17 00:00:00 2001 From: Harsh Mehta Date: Fri, 12 Jun 2026 15:07:59 +0530 Subject: [PATCH 3/3] Add tests for annotated var handling in JDK 27 and above Signed-off-by: Harsh Mehta --- .../bugpatterns/VarWithPrimitiveTest.java | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java index de9a3090ca7..cc386520f35 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/VarWithPrimitiveTest.java @@ -16,6 +16,8 @@ package com.google.errorprone.bugpatterns; +import static com.google.common.truth.TruthJUnit.assume; + import com.google.errorprone.BugCheckerRefactoringTestHelper; import com.google.errorprone.CompilationTestHelper; import org.junit.Test; @@ -339,9 +341,10 @@ void t() { } @Test - public void annotatedVarLocalVariable() { - // The token scan finds two 'var' tokens (@A(var=0) attribute + type keyword) and safely - // returns no fix rather than risk replacing the annotation attribute instead of the type. + public void annotatedVarLocalVariable_beforeJdk27() { + // Before JDK 27 there is no AST node for the var type, so the token scan finds two 'var' + // tokens (@A(var=0) attribute + type keyword) and safely returns no fix. + assume().that(Runtime.version().feature()).isLessThan(27); refactoringHelper .addInputLines("A.java", "@interface A { int var() default 0; }") .expectUnchanged() @@ -358,6 +361,35 @@ void t() { .doTest(); } + @Test + public void annotatedVarLocalVariable_jdk27AndAbove() { + // On JDK 27+ there is an AST node for the var type with source positions, so hasExplicitSource + // returns true and the replacement targets the type keyword directly, preserving the annotation. + assume().that(Runtime.version().feature()).isAtLeast(27); + refactoringHelper + .addInputLines("A.java", "@interface A { int var() default 0; }") + .expectUnchanged() + .addInputLines( + "Test.java", + """ + class Test { + void t() { + @A(var = 0) var n = 5; + } + } + """) + .addOutputLines( + "Test.java", + """ + class Test { + void t() { + @A(var = 0) int n = 5; + } + } + """) + .doTest(); + } + @Test public void multiParamImplicitLambda_noMatch() { compilationHelper