From dbfdbb4d40217ae81bd8727f4b2a251d1270d092 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 12 Sep 2024 14:55:08 +0200 Subject: [PATCH] [java-stubs] TypeAnnotationContainer: properly report annotation owner from generated annotations Fixes IDEA-354380 Erroneous null warning when using jspecify 0.3 NonNull bounds in generics The wrong owner caused malfunction of a condition inside DfaPsiUtil.getElementNullability. As a result, we weren't able to merge declared nullability with instantiation-based nullability for compiled code GitOrigin-RevId: 3ae900174cb7d34a801f07d9644997c85d257b9f --- .../src/com/intellij/psi/PsiType.java | 2 +- .../intellij/psi/TypeAnnotationProvider.java | 11 +++++ .../impl/cache/TypeAnnotationContainer.java | 46 +++++++++++++------ .../intellij/java/psi/ClsStubBuilderTest.java | 25 ++++++++-- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/java/java-psi-api/src/com/intellij/psi/PsiType.java b/java/java-psi-api/src/com/intellij/psi/PsiType.java index c7c1b3c0af5c..ad4a88e44dd5 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiType.java @@ -85,7 +85,7 @@ public abstract class PsiType implements PsiAnnotationOwner, Cloneable, JvmType try { PsiType copy = (PsiType)clone(); - copy.myAnnotationProvider = provider; + copy.myAnnotationProvider = provider.withOwner(copy); return copy; } catch (CloneNotSupportedException e) { diff --git a/java/java-psi-api/src/com/intellij/psi/TypeAnnotationProvider.java b/java/java-psi-api/src/com/intellij/psi/TypeAnnotationProvider.java index f12d6e2f3a5c..d9353cbfa507 100644 --- a/java/java-psi-api/src/com/intellij/psi/TypeAnnotationProvider.java +++ b/java/java-psi-api/src/com/intellij/psi/TypeAnnotationProvider.java @@ -1,6 +1,7 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.psi; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -28,6 +29,16 @@ public interface TypeAnnotationProvider { @NotNull PsiAnnotation @NotNull [] getAnnotations(); + /** + * @param owner owner for annotations in this provider + * @return a provider whose annotations are updated to return the supplied owner. + * May return itself if changing the owner is not supported, or owner is already set for all the annotations. + */ + @ApiStatus.Internal + default @NotNull TypeAnnotationProvider withOwner(@NotNull PsiAnnotationOwner owner) { + return this; + } + final class Static implements TypeAnnotationProvider { private final @NotNull PsiAnnotation @NotNull [] myAnnotations; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/cache/TypeAnnotationContainer.java b/java/java-psi-impl/src/com/intellij/psi/impl/cache/TypeAnnotationContainer.java index 01afecb56ba7..9b730b6cc0a7 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/cache/TypeAnnotationContainer.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/cache/TypeAnnotationContainer.java @@ -94,17 +94,7 @@ public class TypeAnnotationContainer { */ public TypeAnnotationProvider getProvider(PsiElement parent) { if (isEmpty()) return TypeAnnotationProvider.EMPTY; - return () -> { - List result = new ArrayList<>(); - for (TypeAnnotationEntry entry : myList) { - if (entry.myPath.length == 0) { - PsiAnnotation anno = parent instanceof PsiCompiledElement ? new ClsTypeAnnotationImpl(parent, entry.myText) : - JavaPsiFacade.getElementFactory(parent.getProject()).createAnnotationFromText(entry.myText, parent); - result.add(anno); - } - } - return result.toArray(PsiAnnotation.EMPTY_ARRAY); - }; + return new TypeAnnotationContainerProvider(parent, ObjectUtils.tryCast(parent, PsiAnnotationOwner.class)); } /** @@ -340,10 +330,12 @@ public class TypeAnnotationContainer { private final NotNullLazyValue myReferenceElement; private final NotNullLazyValue myParameterList; private final PsiElement myParent; + private final @Nullable PsiAnnotationOwner myOwner; private final String myText; - ClsTypeAnnotationImpl(PsiElement parent, String text) { + ClsTypeAnnotationImpl(PsiElement parent, @Nullable PsiAnnotationOwner owner, String text) { myParent = parent; + myOwner = owner; myText = text; myReferenceElement = NotNullLazyValue.atomicLazy(() -> { int index = myText.indexOf('('); @@ -391,7 +383,7 @@ public class TypeAnnotationContainer { @Override public @Nullable PsiAnnotationOwner getOwner() { - return ObjectUtils.tryCast(myParent, PsiAnnotationOwner.class); + return myOwner; } @Override @@ -432,4 +424,32 @@ public class TypeAnnotationContainer { } } } + + private class TypeAnnotationContainerProvider implements TypeAnnotationProvider { + private final PsiElement myParent; + private final @Nullable PsiAnnotationOwner myOwner; + + private TypeAnnotationContainerProvider(PsiElement parent, @Nullable PsiAnnotationOwner owner) { + myParent = parent; + myOwner = owner; + } + + @Override + public @NotNull TypeAnnotationProvider withOwner(@NotNull PsiAnnotationOwner owner) { + return new TypeAnnotationContainerProvider(myParent, owner); + } + + @Override + public @NotNull PsiAnnotation @NotNull [] getAnnotations() { + List result = new ArrayList<>(); + for (TypeAnnotationEntry entry : myList) { + if (entry.myPath.length == 0) { + PsiAnnotation anno = myParent instanceof PsiCompiledElement ? new ClsTypeAnnotationImpl(myParent, myOwner, entry.myText) : + JavaPsiFacade.getElementFactory(myParent.getProject()).createAnnotationFromText(entry.myText, myParent); + result.add(anno); + } + } + return result.toArray(PsiAnnotation.EMPTY_ARRAY); + } + } } diff --git a/java/java-tests/testSrc/com/intellij/java/psi/ClsStubBuilderTest.java b/java/java-tests/testSrc/com/intellij/java/psi/ClsStubBuilderTest.java index 7a3199ba44e4..368655348596 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/ClsStubBuilderTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/ClsStubBuilderTest.java @@ -1,4 +1,3 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.psi; import com.intellij.JavaTestUtil; @@ -7,12 +6,13 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.IoTestUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiClass; +import com.intellij.psi.*; import com.intellij.psi.impl.compiled.ClsFileImpl; import com.intellij.psi.stubs.PsiFileStub; import com.intellij.psi.stubs.StubBase; import com.intellij.testFramework.IdeaTestUtil; import com.intellij.testFramework.LightJavaCodeInsightTestCase; +import org.jetbrains.annotations.NotNull; import java.io.File; @@ -33,6 +33,20 @@ public class ClsStubBuilderTest extends LightJavaCodeInsightTestCase { public void testTestSuite() { doTest(); } public void testDoubleTest() { doTest(); /* IDEA-53195 */ } public void testTypeAnno() { doTest(); } + + public void testTypeAnnoOwner() { + VirtualFile file = getClassVirtualFile("TypeAnno"); + PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(file); + assertTrue(psiFile instanceof PsiCompiledElement && psiFile instanceof PsiJavaFile); + PsiType returnType = ((PsiJavaFile)psiFile).getClasses()[0].findMethodsByName("get1TypeParam", false)[0].getReturnType(); + PsiAnnotation topAnno = returnType.getAnnotations()[0]; + assertNotNull(topAnno); + assertSame(returnType, topAnno.getOwner()); + assertTrue(returnType instanceof PsiClassType); + PsiType parameter = ((PsiClassType)returnType).getParameters()[0]; + PsiAnnotation paramAnno = parameter.getAnnotations()[0]; + assertSame(parameter, paramAnno.getOwner()); + } public void testModifiers() { doTest("../repo/pack/" + getTestName(false)); } public void testModuleInfo() { doTest("module-info"); } @@ -64,10 +78,15 @@ public class ClsStubBuilderTest extends LightJavaCodeInsightTestCase { } private void doTest(String clsPath) { + VirtualFile clsFile = getClassVirtualFile(clsPath); + doTest(clsFile, getTestName(false) + ".txt"); + } + + private static @NotNull VirtualFile getClassVirtualFile(String clsPath) { String clsFilePath = JavaTestUtil.getJavaTestDataPath() + "/psi/cls/stubBuilder/" + clsPath + ".class"; VirtualFile clsFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(clsFilePath); assertNotNull("Can't find: " + clsFilePath, clsFile); - doTest(clsFile, getTestName(false) + ".txt"); + return clsFile; } @Override