[java-psi] Proper order of annotations inside PsiNewExpressionImpl

Fixes IDEA-366918 Wrong order of annotations in new array expressions

GitOrigin-RevId: 29bbfb29123cd273f4c3d34f74e8a00bc6ab5ec1
This commit is contained in:
Tagir Valeev
2025-03-04 17:38:52 +00:00
committed by intellij-monorepo-bot
parent 8e6bbbf1e1
commit 7e3a48e6ad
4 changed files with 71 additions and 5 deletions
@@ -22,6 +22,7 @@ import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -46,6 +47,7 @@ public class PsiNewExpressionImpl extends ExpressionPsiElement implements PsiNew
private @Nullable PsiType doGetType(@Nullable PsiAnnotation stopAt) {
PsiType type = null;
List<PsiAnnotation> annotations = new SmartList<>();
List<TypeAnnotationProvider> arrayComponentAnnotations = new ArrayList<>();
boolean stop = false;
for (ASTNode child = getFirstChildNode(); child != null; child = child.getTreeNext()) {
@@ -53,7 +55,13 @@ public class PsiNewExpressionImpl extends ExpressionPsiElement implements PsiNew
if (elementType == JavaElementType.ANNOTATION) {
PsiAnnotation annotation = (PsiAnnotation)child.getPsi();
annotations.add(annotation);
if (annotation == stopAt) stop = true;
if (annotation == stopAt) {
// Drop previous array annotations. Only the subsequent annotations matter
// E.g., if we have "new int @A [] @B [] @C []" and we search an owner for "@B"
// then we should return "new int @B [] @C []"
arrayComponentAnnotations.clear();
stop = true;
}
}
else if (elementType == JavaElementType.JAVA_CODE_REFERENCE) {
assert type == null : this;
@@ -70,8 +78,7 @@ public class PsiNewExpressionImpl extends ExpressionPsiElement implements PsiNew
else if (elementType == JavaTokenType.LBRACKET) {
assert type != null : this;
PsiAnnotation[] copy = ContainerUtil.copyAndClear(annotations, PsiAnnotation.ARRAY_FACTORY, true);
type = type.createArrayType().annotate(TypeAnnotationProvider.Static.create(copy));
if (stop) return type;
arrayComponentAnnotations.add(TypeAnnotationProvider.Static.create(copy));
}
else if (elementType == JavaElementType.ANONYMOUS_CLASS) {
PsiElementFactory factory = JavaPsiFacade.getElementFactory(getProject());
@@ -83,8 +90,11 @@ public class PsiNewExpressionImpl extends ExpressionPsiElement implements PsiNew
}
}
// stop == true means annotation is misplaced
return stop ? null : type;
for (int i = arrayComponentAnnotations.size() - 1; i >= 0; i--) {
TypeAnnotationProvider provider = arrayComponentAnnotations.get(i);
type = new PsiArrayType(type, provider);
}
return type;
}
@Override
@@ -0,0 +1,6 @@
import org.jetbrains.annotations.*;
import java.util.*;
class A {
int @NotNull [] @Nullable [] a = new int @NotNull [] @Nullable [] { null };
}
@@ -154,6 +154,7 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
public void testGetterVsDirectAccessObjectEquals() { doTest(); }
public void testSetterAndGetter() { doTest(); }
public void testStaticEqualsContract() { doTest(); }
public void testNewExpressionAnnotations() { doTest(); }
public void testJSpecifyLocalWithGenerics() {
addJSpecifyNullMarked(myFixture);
@@ -0,0 +1,49 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import one.util.streamex.StreamEx;
import java.util.Map;
import java.util.function.Function;
public final class PsiNewExpressionTest extends LightJavaCodeInsightFixtureTestCase {
public void testArrayAnnotations() {
myFixture.configureByText("Foo.java", """
@interface A { }
@interface B { }
@interface C { }
@interface D { }
class Foo {
int[][] data = new @A <caret>int @B @C [] @D [] {};
}
""");
PsiFile file = myFixture.getFile();
PsiNewExpression newExpression =
PsiTreeUtil.getParentOfType(file.findElementAt(myFixture.getCaretOffset()), PsiNewExpression.class);
assertNotNull(newExpression);
assertEquals("@A int @B @C [] @D []", newExpression.getType().getCanonicalText(true));
Map<String, PsiAnnotation> allAnnotations =
StreamEx.of(PsiTreeUtil.collectElementsOfType(newExpression, PsiAnnotation.class))
.toMap(anno -> anno.getQualifiedName(), Function.identity());
assertEquals(4, allAnnotations.size());
Map<String, String> expectedOwnerTexts = Map.of(
"A", "@A int",
"B", "@A int @B @C [] @D []",
"C", "@A int @B @C [] @D []",
"D", "@A int @D []"
);
for (Map.Entry<String, String> entry : expectedOwnerTexts.entrySet()) {
String anno = entry.getKey();
String expectedType = entry.getValue();
PsiAnnotationOwner owner = allAnnotations.get(anno).getOwner();
PsiType type = assertInstanceOf(owner, PsiType.class);
assertEquals(anno, expectedType, type.getCanonicalText(true));
}
}
}