From 7e3a48e6ad44a2d573ba09a61028848d65aef527 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 4 Mar 2025 09:44:31 +0100 Subject: [PATCH] [java-psi] Proper order of annotations inside PsiNewExpressionImpl Fixes IDEA-366918 Wrong order of annotations in new array expressions GitOrigin-RevId: 29bbfb29123cd273f4c3d34f74e8a00bc6ab5ec1 --- .../tree/java/PsiNewExpressionImpl.java | 20 ++++++-- .../fixture/NewExpressionAnnotations.java | 6 +++ .../DataFlowInspection21Test.java | 1 + .../psi/impl/PsiNewExpressionTest.java | 49 +++++++++++++++++++ 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/NewExpressionAnnotations.java create mode 100644 java/java-tests/testSrc/com/intellij/psi/impl/PsiNewExpressionTest.java diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiNewExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiNewExpressionImpl.java index c149e860c463..2f6348ff9b6a 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiNewExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiNewExpressionImpl.java @@ -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 annotations = new SmartList<>(); + List 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 diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NewExpressionAnnotations.java b/java/java-tests/testData/inspection/dataFlow/fixture/NewExpressionAnnotations.java new file mode 100644 index 000000000000..7102381b532a --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NewExpressionAnnotations.java @@ -0,0 +1,6 @@ +import org.jetbrains.annotations.*; +import java.util.*; + +class A { + int @NotNull [] @Nullable [] a = new int @NotNull [] @Nullable [] { null }; +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java index a9e6fe64d47f..78c721726a51 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java @@ -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); diff --git a/java/java-tests/testSrc/com/intellij/psi/impl/PsiNewExpressionTest.java b/java/java-tests/testSrc/com/intellij/psi/impl/PsiNewExpressionTest.java new file mode 100644 index 000000000000..a4a7f1626ba6 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/psi/impl/PsiNewExpressionTest.java @@ -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 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 allAnnotations = + StreamEx.of(PsiTreeUtil.collectElementsOfType(newExpression, PsiAnnotation.class)) + .toMap(anno -> anno.getQualifiedName(), Function.identity()); + assertEquals(4, allAnnotations.size()); + + + Map 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 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)); + } + } +}