From 89af2dec36e2204af39cc66078f7ca9a35d735a4 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Thu, 13 Nov 2025 15:19:50 +0100 Subject: [PATCH] [java-inspections] part of IDEA-380832 - external annotations for type parameters GitOrigin-RevId: 43e826ca12b06ad607e325e81736fe56c514b69f --- .../hints/AnnotationInlayProvider.kt | 124 ++++++++++++++++-- .../intellij/codeInsight/TypeNullability.java | 2 +- .../util/JavaTypeNullabilityUtil.java | 12 +- .../intellij/psi/impl/PsiClassImplUtil.java | 8 ++ .../ExternalTypeAnnotationContainer.java | 26 +++- .../impl/cache/TypeAnnotationContainer.java | 13 ++ .../impl/PsiClassReferenceListStubImpl.java | 8 ++ .../ExternalTypeParameterAnnotations.java | 37 ++++++ .../IncompatibleInstantiation.java | 5 + .../ExternalAnnotationsManagerTest.java | 56 +++++++- .../daemon/inlays/AnnotationHintsTest.kt | 2 +- .../DataFlowInspection8Test.java | 5 + java/jdkAnnotations/java/util/annotations.xml | 7 +- 13 files changed, 280 insertions(+), 25 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/ExternalTypeParameterAnnotations.java diff --git a/java/java-impl/src/com/intellij/codeInsight/hints/AnnotationInlayProvider.kt b/java/java-impl/src/com/intellij/codeInsight/hints/AnnotationInlayProvider.kt index c8cd0a6f0933..92a91714e6d6 100644 --- a/java/java-impl/src/com/intellij/codeInsight/hints/AnnotationInlayProvider.kt +++ b/java/java-impl/src/com/intellij/codeInsight/hints/AnnotationInlayProvider.kt @@ -22,7 +22,9 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.pom.java.JavaFeature import com.intellij.psi.* +import com.intellij.psi.impl.source.PsiClassReferenceType import com.intellij.psi.tree.TokenSet +import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiUtil import com.intellij.psi.util.createSmartPointer import org.jetbrains.annotations.PropertyKey @@ -55,18 +57,52 @@ public class AnnotationInlayProvider : InlayHintsProvider { if (project.isDefault) return null return object : SharedBypassCollector { override fun collectFromElement(element: PsiElement, sink: InlayTreeSink) { + if (element is PsiTypeParameterListOwner) { + sink.whenOptionEnabled(SHOW_EXTERNAL) { + val originalOwner = element.originalElement + if (originalOwner !is PsiCompiledElement || originalOwner !is PsiTypeParameterListOwner) { + return@whenOptionEnabled + } + val typeParameterList = originalOwner.typeParameterList ?: return@whenOptionEnabled + for ((index, originalParameter) in typeParameterList.typeParameters.withIndex()) { + val parameter = element.typeParameters.getOrNull(index) ?: return@whenOptionEnabled + val manager = ExternalAnnotationsManager.getInstance(project) + processTypeParameterRecursively(parameter, originalParameter, sink) + parameter.extendsList.referenceElements.zip(originalParameter.extendsList.referencedTypes) + .forEach { pair: Pair -> + val referenceElement = pair.first + val classType = pair.second + if (referenceElement == null || classType == null) { + return@forEach + } + classType.annotations + .filter(manager::isExternalAnnotation) + .forEach { + sink.addAnnotationPresentation(it, project, InlineInlayPosition(referenceElement.textRange.startOffset, false), HINT_FORMAT, TYPE_ANNOTATION_PAYLOADS) + } + } + if (originalParameter.superTypes.size == 1 && parameter.extendsList.referenceElements.isEmpty()) { + if (originalParameter.superTypes[0].equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { + originalParameter.superTypes[0].annotations + .filter(manager::isExternalAnnotation) + .forEach { + // it is not really correct, because + // annotations should be applied to object, like: `T extends @NotNull Object` + // but it is too long, so let's apply it to type parameters + sink.addAnnotationPresentation(it, project, InlineInlayPosition(parameter.textRange.startOffset, false), HINT_FORMAT, TYPE_ANNOTATION_PAYLOADS) + } + } + } + } + } + } if (element is PsiTypeElement) { sink.whenOptionEnabled(SHOW_EXTERNAL) { val originalElement = element.originalElement + val typeParameter = PsiTreeUtil.getParentOfType(element, PsiTypeParameter::class.java) + if (typeParameter != null) return@whenOptionEnabled if (originalElement is PsiTypeElement && originalElement is PsiCompiledElement) { - val type = originalElement.type - val offset = element.textRange.startOffset - val manager = ExternalAnnotationsManager.getInstance(project) - type.annotations - .filter(manager::isExternalAnnotation) - .forEach { - sink.addAnnotationPresentation(it, project, InlineInlayPosition(offset, false), HINT_FORMAT, TYPE_ANNOTATION_PAYLOADS) - } + showPsiTypeElement(originalElement, element, sink) } } } @@ -101,6 +137,78 @@ public class AnnotationInlayProvider : InlayHintsProvider { } } } + + private fun showPsiTypeElement( + originalElement: PsiTypeElement, + element: PsiTypeElement, + sink: InlayTreeSink, + ) { + val type = originalElement.type + val offset = element.textRange.startOffset + val manager = ExternalAnnotationsManager.getInstance(project) + type.annotations + .filter(manager::isExternalAnnotation) + .forEach { + sink.addAnnotationPresentation(it, project, InlineInlayPosition(offset, false), HINT_FORMAT, TYPE_ANNOTATION_PAYLOADS) + } + } + + private fun processTypeParameterRecursively(parameter: PsiTypeParameter, originalParameter: PsiTypeParameter, sink: InlayTreeSink) { + fun recursiveProcessTypeElement(element: PsiElement, originalElement: PsiElement) { + if (element is PsiTypeElement && originalElement is PsiTypeElement) { + showPsiTypeElement(originalElement, element, sink) + } + element.children + .filter { + it is PsiTypeElement || + it is PsiJavaCodeReferenceElement || + it is PsiReferenceParameterList + }.zip( + originalElement.children + .filter { + it is PsiTypeElement || + it is PsiJavaCodeReferenceElement + }) + .forEach { + val nestedElement = it.first + val nestedOriginalElement = it.second + if (nestedElement is PsiTypeElement && nestedOriginalElement is PsiTypeElement) { + recursiveProcessTypeElement(nestedElement, nestedOriginalElement) + } + if (nestedElement is PsiReferenceParameterList && nestedOriginalElement is PsiReferenceParameterList) { + nestedElement.typeParameterElements.zip(nestedOriginalElement.typeParameterElements).forEach { nested -> + recursiveProcessTypeElement(nested.first, nested.second) + } + } + if (nestedElement is PsiJavaCodeReferenceElement && nestedOriginalElement is PsiJavaCodeReferenceElement) { + val originalTypeParameterElements = nestedOriginalElement.parameterList?.typeParameterElements + val typeParameterElements = nestedElement.parameterList?.typeParameterElements + if (typeParameterElements != null && originalTypeParameterElements != null) { + typeParameterElements.zip(originalTypeParameterElements).forEach { nested -> + recursiveProcessTypeElement(nested.first, nested.second) + } + } + } + } + } + + parameter.extendsList.referenceElements.zip(originalParameter.superTypes) + .forEach { + val referenceElement = it.first + val originalType = it.second + if (referenceElement == null || originalType !is PsiClassReferenceType) return@forEach + val parameterList = referenceElement.parameterList + val originalParameterList = originalType.reference.parameterList + if (parameterList == null || originalParameterList == null) return@forEach + parameterList.typeParameterElements.zip(originalParameterList.typeParameterElements) + .forEach { pair -> + val parameter = pair.first + val originalParameter = pair.second + if (parameter == null || originalParameter == null) return@forEach + recursiveProcessTypeElement(parameter, originalParameter) + } + } + } } } } diff --git a/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java b/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java index b37da5a02541..827b597abc30 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java @@ -129,7 +129,7 @@ public final class TypeNullability { if (manager != null) { NullabilityAnnotationInfo typeUseNullability = manager.findDefaultTypeUseNullability(parameter); if (typeUseNullability != null) { - return typeUseNullability.toTypeNullability(); + return typeUseNullability.toTypeNullability().inherited(); } } return UNKNOWN; diff --git a/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java b/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java index b908e024bddc..97cb64884422 100644 --- a/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java +++ b/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java @@ -53,6 +53,13 @@ public final class JavaTypeNullabilityUtil { PsiTypeParameter typeParameter = (PsiTypeParameter)target; PsiReferenceList extendsList = typeParameter.getExtendsList(); PsiClassType[] extendTypes = extendsList.getReferencedTypes(); + Set nextVisited = visited == null ? new HashSet<>() : visited; + nextVisited.add(type); + //superTypes returns Object for `empty` parameter list, which can contain external annotations + List nullabilities = ContainerUtil.map(typeParameter.getSuperTypes(), t -> getTypeNullability(t, nextVisited, checkContainer)); + TypeNullability fromSuper = TypeNullability.intersect(nullabilities).inherited(); + if (fromSuper != TypeNullability.UNKNOWN) return fromSuper; + //but this Object cannot hold nullability container, because doesn't have a reference if (extendTypes.length == 0 && checkContainer) { NullableNotNullManager manager = NullableNotNullManager.getInstance(typeParameter.getProject()); // If there's no bound, we assume an implicit `extends Object` bound, which is subject to default annotation if any. @@ -60,11 +67,6 @@ public final class JavaTypeNullabilityUtil { if (typeUseNullability != null) { return typeUseNullability.toTypeNullability().inherited(); } - } else { - Set nextVisited = visited == null ? new HashSet<>() : visited; - nextVisited.add(type); - List nullabilities = ContainerUtil.map(extendTypes, t -> getTypeNullability(t, nextVisited, checkContainer)); - return TypeNullability.intersect(nullabilities).inherited(); } } return TypeNullability.UNKNOWN; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java index 65b4fe16268b..138665b56959 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java @@ -16,6 +16,8 @@ import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.augment.PsiAugmentProvider; import com.intellij.psi.augment.PsiExtensionMethod; +import com.intellij.psi.impl.cache.ExternalTypeAnnotationContainer; +import com.intellij.psi.impl.cache.TypeAnnotationContainer; import com.intellij.psi.impl.java.stubs.PsiClassReferenceListStub; import com.intellij.psi.impl.java.stubs.PsiClassStub; import com.intellij.psi.impl.java.stubs.impl.PsiClassStubImpl; @@ -845,6 +847,12 @@ public final class PsiClassImplUtil { } PsiManager manager = psiClass.getManager(); PsiClassType objectType = PsiType.getJavaLangObject(manager, psiClass.getResolveScope()); + if (psiClass instanceof PsiTypeParameter && psiClass.getParent() instanceof PsiTypeParameterList && + psiClass.getParent().getParent() instanceof PsiCompiledElement) { + TypeAnnotationContainer annotations = ExternalTypeAnnotationContainer.create((PsiTypeParameter)psiClass); + annotations = annotations.forBound().forConjunction(0); + objectType = objectType.annotate(annotations.getProvider(psiClass)); + } result[0] = objectType; } System.arraycopy(implementsTypes, 0, result, extendsListLength, implementsTypes.length); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/cache/ExternalTypeAnnotationContainer.java b/java/java-psi-impl/src/com/intellij/psi/impl/cache/ExternalTypeAnnotationContainer.java index 6dce9c883d69..17f8782656c6 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/cache/ExternalTypeAnnotationContainer.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/cache/ExternalTypeAnnotationContainer.java @@ -2,19 +2,18 @@ package com.intellij.psi.impl.cache; import com.intellij.codeInsight.ExternalAnnotationsManager; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiModifierListOwner; -import com.intellij.psi.TypeAnnotationProvider; +import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; /** * A container that reports external type annotations. External type annotations are described in annotation.xml files - * with additional {@code typePath} attribute. The attribute contains several components starting with '/' and separated with '/' - * (no ending '/' is allowed). The allowed components are the following: + * with additional {@code typePath} attribute. The attribute contains several components starting with '/' or '-' and separated with '/' + * (no ending '/' is allowed). + * '-' indicates that type parameters are considered. + * The allowed components are the following: *
    *
  • {@code number} - one-based type argument index (1-255) - *
  • {@code *} (asterisk) - bound of a wildcard type + *
  • {@code *} (asterisk) - go to a bound *
  • {@code []} (square brackets) - array element (also works for varargs) *
  • {@code .} (dot) - enclosing type of inner type *
@@ -70,4 +69,17 @@ public final class ExternalTypeAnnotationContainer implements TypeAnnotationCont public static @NotNull TypeAnnotationContainer create(@NotNull PsiModifierListOwner owner) { return new ExternalTypeAnnotationContainer("", owner); } + + public static @NotNull TypeAnnotationContainer create(@NotNull PsiTypeParameter typeParameter) { + PsiElement parent = typeParameter.getParent(); + if (parent instanceof PsiTypeParameterList && + parent.getParent() instanceof PsiModifierListOwner && + parent.getParent() instanceof PsiTypeParameterListOwner) { + PsiTypeParameterList parameterList = (PsiTypeParameterList)parent; + int index = parameterList.getTypeParameterIndex(typeParameter); + String path = "-/" + (index + 1); + return new ExternalTypeAnnotationContainer(path, (PsiModifierListOwner)parent.getParent()); + } + return EMPTY; + } } 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 d157574b3c76..d4096a673e90 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 @@ -75,6 +75,18 @@ public interface TypeAnnotationContainer { */ @NotNull TypeAnnotationContainer forTypeArgument(int index); + /** + * Returns a type annotation container for the given conjunction index. + * Used for type parameters. + * + * @param i type argument index, zero-based + * @return type annotation container for a given type argument + * (assuming that this type annotation container is used for a class type with type arguments) + */ + default @NotNull TypeAnnotationContainer forConjunction(int i) { + return forTypeArgument(i); + } + /** * @param parent parent PSI element for context * @return TypeAnnotationProvider @@ -85,4 +97,5 @@ public interface TypeAnnotationContainer { * Appends to StringBuilder annotation text that applicable to this element immediately (not to sub-elements) */ void appendImmediateText(@NotNull StringBuilder sb); + } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/impl/PsiClassReferenceListStubImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/impl/PsiClassReferenceListStubImpl.java index 0008747d5066..c4a7a0a3885b 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/impl/PsiClassReferenceListStubImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/impl/PsiClassReferenceListStubImpl.java @@ -2,6 +2,7 @@ package com.intellij.psi.impl.java.stubs.impl; import com.intellij.psi.*; +import com.intellij.psi.impl.cache.ExternalTypeAnnotationContainer; import com.intellij.psi.impl.cache.TypeAnnotationContainer; import com.intellij.psi.impl.cache.TypeInfo; import com.intellij.psi.impl.compiled.ClsJavaCodeReferenceElementImpl; @@ -65,6 +66,13 @@ public class PsiClassReferenceListStubImpl extends StubBase im for (int i = 0; i < types.length; i++) { TypeInfo info = myInfos[i]; TypeAnnotationContainer annotations = info.getTypeAnnotations(); + if (annotations == TypeAnnotationContainer.EMPTY) { + PsiElement psi = myParent.getPsi(); + if (psi instanceof PsiTypeParameter) { + annotations = ExternalTypeAnnotationContainer.create((PsiTypeParameter)psi); + annotations = annotations.forBound().forConjunction(i); + } + } ClsJavaCodeReferenceElementImpl reference = new ClsJavaCodeReferenceElementImpl(getPsi(), info.text(), annotations); types[i] = new PsiClassReferenceType(reference, null).annotate(annotations.getProvider(reference)); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ExternalTypeParameterAnnotations.java b/java/java-tests/testData/inspection/dataFlow/fixture/ExternalTypeParameterAnnotations.java new file mode 100644 index 000000000000..b91dfefc281f --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ExternalTypeParameterAnnotations.java @@ -0,0 +1,37 @@ +package org.example; + +import org.jspecify.annotations.*; + +import java.util.*; + +@NullMarked +class CustomOptionalTest { + @Nullable + public String a; + + Optional returnNotNullableDelegate() { + return Delegate.ofNullable(a); + } + + Optional returnNotNullable() { + return Optional.ofNullable(a); + } + + Comparator getComparator() { + return Comparator.<@Nullable String>naturalOrder(); + } +} + +class TestList{ + static void test(String a) { + if (List.of("1", a).getFirst() == null) { + System.out.println("null"); + } + } +} + +class Delegate { + static Optional ofNullable(@Nullable L t) { + return Optional.ofNullable(t); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/nullableProblems/IncompatibleInstantiation.java b/java/java-tests/testData/inspection/nullableProblems/IncompatibleInstantiation.java index 3162221ee59a..f3f77f040a90 100644 --- a/java/java-tests/testData/inspection/nullableProblems/IncompatibleInstantiation.java +++ b/java/java-tests/testData/inspection/nullableProblems/IncompatibleInstantiation.java @@ -2,6 +2,7 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.lang.annotation.ElementType; +import java.util.Comparator; import java.util.function.Supplier; @NullMarked @@ -13,6 +14,10 @@ class Main { } + Comparator getComparator() { + return Comparator.<@Nullable String>naturalOrder(); + } + static T fNullableBound(Supplier supplier){ return supplier.get(); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/ExternalAnnotationsManagerTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/ExternalAnnotationsManagerTest.java index c548a61130c6..35a9f4a15544 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/ExternalAnnotationsManagerTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/ExternalAnnotationsManagerTest.java @@ -117,7 +117,22 @@ public class ExternalAnnotationsManagerTest extends LightPlatformTestCase { assertClassFqn(nameText, psiFile, externalName, null); String typePath = annotationData.getTypePath(); - if (typePath != null) { + if (typePath != null && typePath.startsWith("-")) { + PsiTypeParameterList typeParameterList; + if (listOwner instanceof PsiClass variable) { + typeParameterList = variable.getTypeParameterList(); + } else if (listOwner instanceof PsiMethod method) { + typeParameterList = method.getTypeParameterList(); + } else { + fail("typePath is not allowed for " + listOwner.getClass().getSimpleName(), psiFile, externalName); + break; + } + String error = validatePath(typePath, typeParameterList); + if (error != null) { + fail("Invalid typePath: " + error, psiFile, externalName); + } + } + else if (typePath != null) { PsiType type; if (listOwner instanceof PsiVariable variable) { type = variable.getType(); @@ -191,6 +206,45 @@ public class ExternalAnnotationsManagerTest extends LightPlatformTestCase { return Objects.requireNonNull(PsiUtil.getTypeByPsiElement(listOwner), () -> String.valueOf(listOwner)); } + private static String validatePath(@NotNull String pathString, @NotNull PsiTypeParameterList typeParameterList) { + if (!pathString.startsWith("-/")) { + return "Must start with '-/'"; + } + String[] components = pathString.split("/", -1); + String component = components[1]; + int i; + try { + i = Integer.parseInt(component); + } + catch (NumberFormatException ignored) { + return "Invalid path: " + pathString + "; invalid component: " + component; + } + PsiTypeParameter[] parameters = typeParameterList.getTypeParameters(); + if (i <= 0 || parameters.length < i) { + return "Invalid path: " + pathString + "; type parameter index out of bounds: " + i; + } + PsiTypeParameter parameter = parameters[i - 1]; + String asterix = components[2]; + if (!asterix.equals("*")) { + return "Invalid path: " + pathString + "; only '*' is allowed for type parameter " + parameter.getName(); + } + String nextIndex = components[3]; + try { + i = Integer.parseInt(nextIndex); + } + catch (NumberFormatException ignored) { + return "Invalid path: " + pathString + "; invalid extends component: " + nextIndex; + } + PsiClassType[] types = parameter.getExtendsList().getReferencedTypes(); + if (components.length == 4 && i == 1 && types.length == 0) return null; + if (i <= 0 || types.length < i) { + return "Invalid path: " + pathString + "; extends index out of bounds: " + i; + } + String rest = StringUtil.join(components, 4, components.length, "/"); + if (rest.isEmpty()) return null; + return validatePath(rest, types[i - 1]); + } + private static String validatePath(String pathString, PsiType type) { if (!pathString.startsWith("/")) { return "Must start with '/'"; diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/AnnotationHintsTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/AnnotationHintsTest.kt index 769e432e64cf..c929d2392486 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/AnnotationHintsTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/AnnotationHintsTest.kt @@ -115,7 +115,7 @@ import java.util.stream.Stream; import jdk.internal.ValueBased; @ValueBased -public final class Optional { +public final class Optional*/T> { private static final Optional EMPTY = new Optional((Object)null); private final T value; diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index f5ec0d64024f..6b1c1a9b35a4 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -374,4 +374,9 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { public void testNewMethodReferenceMustBeNonNull() { doTestWith((insp, __) -> insp.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true); } + public void testExternalTypeParameterAnnotations() { + addJSpecifyNullMarked(myFixture); + setupTypeUseAnnotations("org.jspecify.annotations", myFixture); + doTest(); + } } \ No newline at end of file diff --git a/java/jdkAnnotations/java/util/annotations.xml b/java/jdkAnnotations/java/util/annotations.xml index 08326e60fcb7..0fdfbc3aedf7 100644 --- a/java/jdkAnnotations/java/util/annotations.xml +++ b/java/jdkAnnotations/java/util/annotations.xml @@ -2752,7 +2752,7 @@ - + @@ -3517,7 +3517,7 @@ - + @@ -4416,6 +4416,9 @@ + + +