diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/BadImport.java b/core/src/main/java/com/google/errorprone/bugpatterns/BadImport.java index 2a12892cc36..2dd5a2520b7 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/BadImport.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/BadImport.java @@ -20,6 +20,7 @@ import static com.google.errorprone.matchers.Matchers.annotations; import static com.google.errorprone.matchers.ProtobufMatchers.MESSAGE_LITE_TYPE; import static com.google.errorprone.util.ASTHelpers.getSymbol; +import static com.google.errorprone.util.ASTHelpers.hasExplicitSource; import static com.google.errorprone.util.ASTHelpers.isSubtype; import com.google.common.collect.ImmutableSet; @@ -275,6 +276,15 @@ private void moveTypeAnnotations( SuggestedFix.Builder builder) { for (AnnotationTree annotation : HAS_TYPE_USE_ANNOTATION.multiMatchResult(annotationHolder, state).matchingNodes()) { + if (!hasExplicitSource(annotation, state)) { + // A record component's type-use annotation can be reached through javac's + // synthesized members (field/accessor/canonical constructor), whose copy of the + // annotation tree has no recorded end position. Deleting and reinserting it would + // build an invalid Replacement and crash the whole compilation (#6074). Leave the + // annotation where it is -- the node is still qualified by the prefixWith() call + // above, it just won't be relocated ahead of the qualified type. + continue; + } builder.delete(annotation); builder.prefixWith(node, state.getSourceForNode(annotation) + " "); } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/BadImportTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/BadImportTest.java index 8d82cb10e11..1d4a6d87bf6 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/BadImportTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/BadImportTest.java @@ -582,6 +582,53 @@ abstract class Test { .doTest(); } + + @Test + public void recordComponentWithTypeUseAnnotation_doesNotCrash() { + refactoringTestHelper + .addInputLines( + "input/TypeUseAnnotation.java", + """ + package test; + + import java.lang.annotation.ElementType; + import java.lang.annotation.Target; + + @Target(ElementType.TYPE_USE) + @interface TypeUseAnnotation {} + """) + .expectUnchanged() + .addInputLines( + "input/SomeClass.java", + """ + package test; + + class SomeClass { + enum Builder { + A + } + } + """) + .expectUnchanged() + .addInputLines( + "input/Test.java", + """ + package test; + + import test.SomeClass.Builder; + + record Test(@TypeUseAnnotation Builder builder) {} + """) + .addOutputLines( + "output/Test.java", + """ + package test; + + record Test(@TypeUseAnnotation SomeClass.Builder builder) {} + """) + .doTest(); + } + @Test public void negative_truth8AssertThatFalseFlag() { compilationTestHelper