[java-inspections] IDEA-380526 Jspecify. False positive. Non-null type argument is expected for Array type

GitOrigin-RevId: 132eab2bc3bc9c4f6995a29796d8596b6e4a3c5c
This commit is contained in:
Mikhail Pyltsin
2025-11-07 03:31:56 +00:00
committed by intellij-monorepo-bot
parent ad6d52df3b
commit 8c3b69c65b
16 changed files with 453 additions and 35 deletions
@@ -475,8 +475,11 @@ public final class GenericsUtil {
}
}
else if (type instanceof PsiArrayType) {
PsiType component = eliminateWildcards(((PsiArrayType)type).getComponentType(), false);
PsiType newArray = type instanceof PsiEllipsisType ? new PsiEllipsisType(component) : new PsiArrayType(component);
PsiArrayType psiArrayType = (PsiArrayType)type;
PsiType component = eliminateWildcards(psiArrayType.getComponentType(), false);
PsiType newArray = type instanceof PsiEllipsisType ?
new PsiEllipsisType(component).withContainerNullability(psiArrayType) :
new PsiArrayType(component).withContainerNullability(psiArrayType);
return newArray.annotate(type.getAnnotationProvider()).withNullability(type.getNullability());
}
else if (type instanceof PsiWildcardType) {
@@ -1,10 +1,14 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi;
import com.intellij.codeInsight.NullabilityAnnotationInfo;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.TypeNullability;
import com.intellij.lang.jvm.types.JvmArrayType;
import com.intellij.openapi.project.Project;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.JavaTypeNullabilityUtil;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -16,26 +20,30 @@ import org.jetbrains.annotations.Nullable;
*/
public class PsiArrayType extends PsiType.Stub implements JvmArrayType {
private final PsiType myComponentType;
private TypeNullability myNullability;
TypeNullability myNullability;
@Nullable
final PsiTypeElementPointer myElementPointer;
public PsiArrayType(@NotNull PsiType componentType) {
this(componentType, TypeAnnotationProvider.EMPTY);
}
public PsiArrayType(@NotNull PsiType componentType, PsiAnnotation @NotNull [] annotations) {
super(annotations);
myComponentType = componentType;
myNullability = null;
this(componentType, TypeAnnotationProvider.Static.create(annotations));
}
public PsiArrayType(@NotNull PsiType componentType, @NotNull TypeAnnotationProvider provider) {
this(componentType, provider, null);
this(componentType, provider, null, null);
}
PsiArrayType(@NotNull PsiType componentType, @NotNull TypeAnnotationProvider provider, @Nullable TypeNullability nullability) {
PsiArrayType(@NotNull PsiType componentType,
@NotNull TypeAnnotationProvider provider,
@Nullable TypeNullability nullability,
@Nullable PsiTypeElementPointer elememtPointer) {
super(provider);
myComponentType = componentType;
myNullability = nullability;
myElementPointer = elememtPointer;
}
@Override
@@ -120,10 +128,60 @@ public class PsiArrayType extends PsiType.Stub implements JvmArrayType {
return myComponentType;
}
/**
* Creates a new array type instance with the specified nullable container nullability.
*
* @param elementPointer the PSI element pointer representing the context, or null if no specific context is required.
* @return a new instance of {@link PsiType} with the specified nullable container nullability.
*/
@ApiStatus.Experimental
@NotNull
public PsiType withContainerNullability(@Nullable PsiTypeElementPointer elementPointer) {
if (elementPointer == null) return this;
if (elementPointer == myElementPointer) return this;
return new PsiArrayType(myComponentType, getAnnotationProvider(), myNullability, elementPointer);
}
@ApiStatus.Experimental
@Nullable
static TypeNullability getContainerNullability(@NotNull PsiTypeElement psiContext) {
Project project = psiContext.getProject();
if (project.isDefault()) return null;
NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
if (manager == null) return null;
NullabilityAnnotationInfo defaultNullability = manager.findDefaultTypeUseNullability(psiContext);
TypeNullability nullability = defaultNullability == null ? null : defaultNullability.toTypeNullability();
return nullability;
}
/**
* Creates a new array type instance with the specified nullable container nullability.
*
* @param arrayType the array type from which container nullability will be taken.
* @return a new instance of {@link PsiType} with the specified nullable container nullability.
*/
@ApiStatus.Experimental
@NotNull
public PsiType withContainerNullability(@Nullable PsiArrayType arrayType) {
if (arrayType == null) return this;
if (arrayType.myElementPointer == myElementPointer) return this;
return new PsiArrayType(myComponentType, getAnnotationProvider(), myNullability, arrayType.myElementPointer);
}
@Override
public @NotNull TypeNullability getNullability() {
if (myNullability == null) {
myNullability = JavaTypeNullabilityUtil.getNullabilityFromAnnotations(getAnnotations());
TypeNullability nullability = JavaTypeNullabilityUtil.getNullabilityFromAnnotations(getAnnotations());
if (nullability == TypeNullability.UNKNOWN && myElementPointer != null) {
PsiTypeElement element = myElementPointer.retrieveElement();
if (element != null) {
TypeNullability containerNullability = getContainerNullability(element);
if (containerNullability != null) {
nullability = containerNullability;
}
}
}
myNullability = nullability;
}
return myNullability;
}
@@ -135,7 +193,7 @@ public class PsiArrayType extends PsiType.Stub implements JvmArrayType {
*/
@Override
public @NotNull PsiArrayType withNullability(@NotNull TypeNullability nullability) {
return new PsiArrayType(getComponentType(), getAnnotationProvider(), nullability);
return new PsiArrayType(getComponentType(), getAnnotationProvider(), nullability, myElementPointer);
}
@Override
@@ -4,6 +4,7 @@ package com.intellij.psi;
import com.intellij.codeInsight.TypeNullability;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents the type of a variable arguments array passed as a method parameter.
@@ -20,9 +21,12 @@ public class PsiEllipsisType extends PsiArrayType {
public PsiEllipsisType(@NotNull PsiType componentType, @NotNull TypeAnnotationProvider provider) {
super(componentType, provider);
}
private PsiEllipsisType(@NotNull PsiType componentType, @NotNull TypeAnnotationProvider provider, @NotNull TypeNullability nullability) {
super(componentType, provider, nullability);
private PsiEllipsisType(@NotNull PsiType componentType,
@NotNull TypeAnnotationProvider provider,
@Nullable TypeNullability nullability,
@Nullable PsiTypeElementPointer typeElementPointer) {
super(componentType, provider, nullability, typeElementPointer);
}
@Override
@@ -46,9 +50,25 @@ public class PsiEllipsisType extends PsiArrayType {
super.equalsToText(text);
}
@NotNull
@Override
public PsiType withContainerNullability(@Nullable PsiTypeElementPointer elementPointer) {
if (elementPointer == null) return this;
if (elementPointer == myElementPointer) return this;
return new PsiEllipsisType(getComponentType(), getAnnotationProvider(), myNullability, elementPointer);
}
@NotNull
@Override
public PsiType withContainerNullability(@Nullable PsiArrayType arrayType) {
if (arrayType == null) return this;
if (arrayType.myElementPointer == myElementPointer) return this;
return new PsiEllipsisType(getComponentType(), getAnnotationProvider(), myNullability, arrayType.myElementPointer);
}
@Override
public @NotNull PsiEllipsisType withNullability(@NotNull TypeNullability nullability) {
return new PsiEllipsisType(getComponentType(), getAnnotationProvider(), nullability);
return new PsiEllipsisType(getComponentType(), getAnnotationProvider(), nullability, this.myElementPointer);
}
/**
@@ -0,0 +1,22 @@
// 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;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Experimental
public interface PsiTypeElementPointer {
@Nullable
PsiTypeElement retrieveElement();
static PsiTypeElementPointer constant(@NotNull PsiTypeElement ref) {
return new PsiTypeElementPointer() {
@Override
public @NotNull PsiTypeElement retrieveElement() {
return ref;
}
};
}
}
@@ -25,7 +25,8 @@ public abstract class PsiTypeMapper extends PsiTypeVisitorEx<PsiType> {
PsiType mappedComponent = mapType(componentType);
if (mappedComponent == null) return null;
if (mappedComponent == componentType) return type;
return new PsiArrayType(mappedComponent, type.getAnnotationProvider());
return new PsiArrayType(mappedComponent, type.getAnnotationProvider())
.withContainerNullability(type);
}
@Override
@@ -189,9 +189,9 @@ public class ClsTypeElementImpl extends ClsElementImpl implements PsiTypeElement
PsiType type = deepestChild.getType();
for (int i = depth - 1; i >= 0; i--) {
if (i == 0 && isVarArgs()) {
type = new PsiEllipsisType(type);
type = new PsiEllipsisType(type).withContainerNullability(PsiTypeElementPointer.constant(deepestChild));
} else {
type = type.createArrayType();
type = type.createArrayType().withContainerNullability(PsiTypeElementPointer.constant(deepestChild));
}
type = type.annotate(containers.get(i).getProvider(this));
}
@@ -3,12 +3,15 @@ package com.intellij.psi.impl.source;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.RecursionGuard;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.PsiJavaParserFacadeImpl;
import com.intellij.psi.impl.compiled.ClsTypeElementImpl;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.IElementType;
@@ -26,6 +29,9 @@ import java.util.Arrays;
import java.util.List;
public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeElement {
private static final Logger LOG = Logger.getInstance(PsiTypeElementImpl.class);
public PsiTypeElementImpl() {
this(JavaElementType.TYPE);
}
@@ -84,7 +90,8 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
else if (PsiUtil.isJavaToken(child, ElementType.PRIMITIVE_TYPE_BIT_SET)) {
assert type == null : this;
String text = child.getText();
type = annotations.isEmpty() ? PsiJavaParserFacadeImpl.getPrimitiveType(text) : new PsiPrimitiveType(text, createProvider(annotations));
type =
annotations.isEmpty() ? PsiJavaParserFacadeImpl.getPrimitiveType(text) : new PsiPrimitiveType(text, createProvider(annotations));
}
else if (PsiUtil.isJavaToken(child, JavaTokenType.VAR_KEYWORD)) {
assert type == null : this;
@@ -153,12 +160,14 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
return type;
}
private static PsiType createArray(PsiType elementType, List<TypeAnnotationProvider> providers, boolean ellipsis) {
private PsiType createArray(PsiType elementType, List<TypeAnnotationProvider> providers, boolean ellipsis) {
PsiType result = elementType;
for (int i = providers.size() - 1; i >= 0; i--) {
TypeAnnotationProvider provider = providers.get(i);
result = ellipsis && i == 0 ? new PsiEllipsisType(result, provider) : new PsiArrayType(result, provider);
result = ellipsis && i == 0 ?
new PsiEllipsisType(result, provider).withContainerNullability(createTypeElementPointer(this)) :
new PsiArrayType(result, provider).withContainerNullability(createTypeElementPointer(this));
}
providers.clear();
return result;
@@ -212,15 +221,11 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
@Override
public boolean isInferredType() {
return PsiUtil.isJavaToken(getFirstChild(), JavaTokenType.VAR_KEYWORD) ||
PsiAugmentProvider.isInferredType(this);
PsiAugmentProvider.isInferredType(this);
}
private static @NotNull ClassReferencePointer getReferenceComputable(@NotNull PsiJavaCodeReferenceElement ref) {
PsiElement root = SyntaxTraverser.psiApi()
.parents(ref.getParent())
.takeWhile(it -> it instanceof PsiTypeElement || it instanceof PsiReferenceParameterList || it instanceof PsiJavaCodeReferenceElement)
.last();
PsiElement parent = root instanceof PsiTypeElement ? root.getParent() : null;
PsiElement parent = getAnchorParent(ref);
if (parent instanceof PsiMethod || parent instanceof PsiVariable) {
PsiModifierListOwner owner = (PsiModifierListOwner)parent;
int[] pathFromRoot = getPathFromRoot(ref);
@@ -228,12 +233,21 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
}
return ClassReferencePointer.constant(ref);
}
private static @Nullable PsiElement getAnchorParent(@NotNull PsiElement ref) {
PsiElement root = SyntaxTraverser.psiApi()
.parents(ref)
.takeWhile(it -> it instanceof PsiTypeElement || it instanceof PsiReferenceParameterList || it instanceof PsiJavaCodeReferenceElement)
.last();
PsiElement parent = root instanceof PsiTypeElement ? root.getParent() : null;
return parent;
}
// n = -1 => go to qualifier
// n >= 0 => go to type parameter #n
private static int[] getPathFromRoot(@NotNull PsiJavaCodeReferenceElement ref) {
IntList result = null;
while(true) {
while (true) {
PsiElement parent = ref.getParent();
if (parent instanceof PsiJavaCodeReferenceElement) {
PsiJavaCodeReferenceElement parentRef = (PsiJavaCodeReferenceElement)parent;
@@ -241,10 +255,12 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
if (result == null) result = new IntArrayList();
result.add(0, -1);
ref = parentRef;
} else {
}
else {
throw new IllegalStateException("Unexpected parent (going not from qualifier): " + parent.getText());
}
} else if (parent instanceof PsiTypeElement) {
}
else if (parent instanceof PsiTypeElement) {
PsiElement nextParent = parent.getParent();
while (nextParent instanceof PsiTypeElement) {
parent = nextParent;
@@ -260,10 +276,12 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
ref = (PsiJavaCodeReferenceElement)nextRef;
if (result == null) result = new IntArrayList();
result.add(0, index);
} else {
}
else {
return result == null ? ArrayUtil.EMPTY_INT_ARRAY : result.toIntArray();
}
} else {
}
else {
throw new IllegalStateException("Unexpected parent: " + parent.getText());
}
}
@@ -442,7 +460,8 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
if (parent instanceof PsiModifierListOwner) {
PsiModifierList modifierList = ((PsiModifierListOwner)parent).getModifierList();
if (modifierList != null) {
PsiTypeParameterList list = parent instanceof PsiTypeParameterListOwner ? ((PsiTypeParameterListOwner)parent).getTypeParameterList() : null;
PsiTypeParameterList list =
parent instanceof PsiTypeParameterListOwner ? ((PsiTypeParameterListOwner)parent).getTypeParameterList() : null;
if (list == null || list.textMatches("")) {
return (PsiAnnotation)modifierList.add(annotation);
}
@@ -498,4 +517,84 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
public String toString() {
return "PsiTypeElement:" + getText();
}
@ApiStatus.Experimental
@NotNull
public static PsiTypeElementPointer createTypeElementPointer(@NotNull PsiTypeElement typeElement) {
if (typeElement instanceof ClsTypeElementImpl) {
return PsiTypeElementPointer.constant(typeElement);
}
PsiElement parent = getAnchorParent(typeElement);
if ((parent instanceof JavaStubPsiElement) && (parent instanceof PsiMethod || parent instanceof PsiVariable)) {
PsiModifierListOwner owner = (PsiModifierListOwner)parent;
PsiTypeElement ownerType =
parent instanceof PsiMethod ? ((PsiMethod)parent).getReturnTypeElement() : ((PsiVariable)parent).getTypeElement();
if (ownerType == null) return PsiTypeElementPointer.constant(typeElement);
int[] pathFromRoot = getPathFromRoot(ownerType, typeElement);
if (pathFromRoot == null) return PsiTypeElementPointer.constant(typeElement);
return computeTypeElementPointerFromTypeOwner(owner, pathFromRoot, new WeakReference<>(typeElement));
}
return PsiTypeElementPointer.constant(typeElement);
}
private static @NotNull PsiTypeElementPointer computeTypeElementPointerFromTypeOwner(@NotNull PsiModifierListOwner owner,
int @NotNull [] root,
@NotNull WeakReference<PsiTypeElement> reference) {
return new PsiTypeElementPointer() {
@Override
public @Nullable PsiTypeElement retrieveElement() {
PsiTypeElement element = reference.get();
if (element != null && element.isValid()) return element;
if (!owner.isValid()) return null;
PsiElement parentTypeElement =
owner instanceof PsiMethod ? ((PsiMethod)owner).getReturnTypeElement() : ((PsiVariable)owner).getTypeElement();
if (parentTypeElement == null) return null;
PsiElement typeElement = parentTypeElement;
for (int i : root) {
List<@NotNull PsiElement> filtered = ContainerUtil.filter(typeElement.getChildren(),
t -> t instanceof PsiTypeElement ||
t instanceof PsiJavaCodeReferenceElement ||
t instanceof PsiReferenceParameterList);
if (i >= filtered.size()) return null;
typeElement = filtered.get(i);
}
if (typeElement instanceof PsiTypeElement) {
return (PsiTypeElement)typeElement;
}
LOG.error("Can't decode PsiTypeElement with indexes",
new Attachment("memberType.txt", parentTypeElement.getText() + "\n" + Arrays.toString(root)));
return null;
}
};
}
private static int @Nullable [] getPathFromRoot(@NotNull PsiTypeElement targetParent, @NotNull PsiTypeElement child) {
IntList result = new IntArrayList();
PsiElement currentChild = child;
while (true) {
if (currentChild == targetParent) {
return result.toIntArray();
}
PsiElement parent = currentChild.getParent();
if (parent instanceof PsiTypeElement ||
parent instanceof PsiJavaCodeReferenceElement ||
parent instanceof PsiReferenceParameterList) {
List<PsiElement> psiTypeElementChildren =
ContainerUtil.filter(parent.getChildren(),
t -> t instanceof PsiTypeElement ||
t instanceof PsiJavaCodeReferenceElement ||
t instanceof PsiReferenceParameterList);
int index = psiTypeElementChildren.indexOf(currentChild);
result.add(0, index);
currentChild = parent;
continue;
}
LOG.error("Can't code PsiTypeElement with indexes",
new Attachment("memberType.txt", targetParent.getText() + "\n" + child.getText()));
return null;
}
}
}
@@ -9,6 +9,7 @@ import com.intellij.psi.*;
import com.intellij.psi.impl.GeneratedMarkerVisitor;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.cache.TypeInfo;
import com.intellij.psi.impl.source.PsiTypeElementImpl;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.impl.source.tree.java.AnnotationElement;
import com.intellij.psi.tree.IElementType;
@@ -43,7 +44,9 @@ public final class JavaSharedImplUtil {
List<PsiAnnotation[]> allAnnotations = collectAnnotations(anchor, stopAt);
if (allAnnotations == null) return null;
for (int i = 0, size = allAnnotations.size(); i < size; i++) {
type = ((ellipsisType && i == size - 1) ? new PsiEllipsisType(type) : type.createArrayType())
type = ((ellipsisType && i == size - 1) ?
new PsiEllipsisType(type).withContainerNullability(PsiTypeElementImpl.createTypeElementPointer(typeElement) ) :
type.createArrayType().withContainerNullability(PsiTypeElementImpl.createTypeElementPointer(typeElement)))
.annotate(TypeAnnotationProvider.Static.create(allAnnotations.get(i)));
}
@@ -0,0 +1,44 @@
package org.example;
import org.jspecify.annotations.*;
class UseArrayTest {
@NullMarked
abstract static class Container {
interface Lib<T extends @Nullable Object> {
T get();
}
abstract Lib<String[][]> useArray1();
abstract Lib<? extends String[][]> useArray2();
}
@NullMarked
abstract class Container2<L> {
interface Lib2<T> {
T get();
}
abstract Lib2<L> useArray2();
}
class ArrayUseTest {
void test(Container a, Container2<? extends String[][]> b) {
if (<warning descr="Condition 'a.useArray1().get() == null' is always 'false'">a.useArray1().get() == null</warning>) { //expected
System.out.println("1");
}
String[][] strings2 = a.useArray2().get();
if (<warning descr="Condition 'strings2 == null' is always 'false'">strings2 == null</warning>) { //expected
System.out.println("1");
}
String[][] string3 = b.useArray2().get();
if (<warning descr="Condition 'string3 == null' is always 'false'">string3 == null</warning>) { //expected
System.out.println("1");
}
}
}
}
@@ -0,0 +1,18 @@
package org.example;
import org.jspecify.annotations.NullMarked;
class SimpleArrayTest {
static class Container {
@NullMarked
abstract static class ArraySameType {
interface Lib<T> {
T get();
}
abstract void useArray(Lib<Object[]> l); //not expected
abstract Lib<? extends String[][]> useArray2(); //not expected
}
}
}
@@ -249,4 +249,9 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
doTest();
}
public void testJSpecifyArrayNullability() {
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
}
@@ -514,4 +514,10 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
public void testArrayUnderContainerAnnotation() {
myInspection.REPORT_NOT_ANNOTATED_INSTANTIATION_NOT_NULL_TYPE = true;
addJSpecifyNullMarked(myFixture);
doTest();
}
}
@@ -1,6 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.psi;
import com.intellij.codeInsight.Nullability;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiClassReferenceType;
@@ -11,6 +12,7 @@ import com.intellij.psi.impl.source.tree.java.ParameterElement;
import com.intellij.testFramework.LeakHunter;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import com.intellij.util.ref.GCWatcher;
import org.intellij.lang.annotations.Language;
public class AstLeaksTest extends LightJavaCodeInsightFixtureTestCase {
public void test_AST_should_be_on_a_soft_reference__for_changed_files_as_well() {
@@ -90,6 +92,134 @@ public class AstLeaksTest extends LightJavaCodeInsightFixtureTestCase {
assertFalse(file.isContentsLoaded());
}
public void testNullabilityCorrectLoad() {
@Language("JAVA") String nullMarked =
"""
package org.jspecify.annotations;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.MODULE})
public @interface NullMarked {}""";
myFixture.addClass(nullMarked);
final PsiClass cls = myFixture.addClass(
"""
@org.jspecify.annotations.NullMarked
class Foo {
static void test(java.util.Map<String, ? extends java.lang.String[]> args) {}
}""");
PsiFileImpl file = (PsiFileImpl)cls.getContainingFile();
assertNotNull(cls.getNode());
PsiType type = cls.getMethods()[0].getParameterList().getParameters()[0].getTypeElement().getType();
assertTrue(type instanceof PsiClassReferenceType);
LeakHunter.checkLeak(type, ParameterElement.class,
node -> node.getPsi().equals(cls.getMethods()[0].getParameterList().getParameters()[0]));
GCWatcher.tracking(cls.getNode()).ensureCollected();
assertFalse(file.isContentsLoaded());
assertTrue(type.equalsToText("java.util.Map<java.lang.String,? extends java.lang.String[]>"));
assertFalse(file.isContentsLoaded());
PsiType targetType = ((PsiWildcardType)((PsiClassReferenceType)(type)).getParameters()[1]).getBound();
assertTrue(targetType instanceof PsiArrayType);
assertTrue(targetType.equalsToText("java.lang.String[]"));
assertFalse(file.isContentsLoaded());
assertSame(targetType.getNullability().nullability(), Nullability.NOT_NULL);
}
public void testNullabilityCorrectLoadGetBeforeAsymPath() {
@Language("JAVA") String nullMarked =
"""
package org.jspecify.annotations;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.MODULE})
public @interface NullMarked {}""";
myFixture.addClass(nullMarked);
final PsiClass cls = myFixture.addClass(
"""
@org.jspecify.annotations.NullMarked
class Foo {
static void test(java.util.Map<String, java.lang.String[]> args) {}
}""");
PsiFileImpl file = (PsiFileImpl)cls.getContainingFile();
assertNotNull(cls.getNode());
PsiType type = cls.getMethods()[0].getParameterList().getParameters()[0].getTypeElement().getType();
assertTrue(type instanceof PsiClassReferenceType);
//don't create intermediate variables, it is necessary to skip it for gc collection
PsiType targetType = ((PsiTypeElement)cls.getMethods()[0].getParameterList().getParameters()[0].getTypeElement().getChildren()[0].getChildren()[3].getChildren()[4]).getType();
LeakHunter.checkLeak(targetType, ParameterElement.class,
node -> node.getPsi().equals(cls.getMethods()[0].getParameterList().getParameters()[0]));
GCWatcher.tracking(cls.getNode()).ensureCollected();
assertFalse(file.isContentsLoaded());
assertTrue(type.equalsToText("java.util.Map<java.lang.String,java.lang.String[]>"));
assertFalse(file.isContentsLoaded());
assertTrue(targetType instanceof PsiArrayType);
assertTrue(targetType.equalsToText("java.lang.String[]"));
assertFalse(file.isContentsLoaded());
assertSame(targetType.getNullability().nullability(), Nullability.NOT_NULL);
}
public void testNullabilityCorrectLoadGetBefore() {
@Language("JAVA") String nullMarked =
"""
package org.jspecify.annotations;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.MODULE})
public @interface NullMarked {}""";
myFixture.addClass(nullMarked);
final PsiClass cls = myFixture.addClass(
"""
@org.jspecify.annotations.NullMarked
class Foo {
static void test(java.util.Map<String, ? extends java.lang.String[]> args) {}
}""");
PsiFileImpl file = (PsiFileImpl)cls.getContainingFile();
assertNotNull(cls.getNode());
PsiType type = cls.getMethods()[0].getParameterList().getParameters()[0].getTypeElement().getType();
assertTrue(type instanceof PsiClassReferenceType);
//don't create an intermediate variables, it is necessary to skip it for gc collection
PsiType targetType = ((PsiTypeElement)cls.getMethods()[0].getParameterList().getParameters()[0].getTypeElement().getChildren()[0].getChildren()[3].getChildren()[4].getChildren()[4]).getType();
LeakHunter.checkLeak(targetType, ParameterElement.class,
node -> node.getPsi().equals(cls.getMethods()[0].getParameterList().getParameters()[0]));
GCWatcher.tracking(cls.getNode()).ensureCollected();
assertFalse(file.isContentsLoaded());
assertTrue(type.equalsToText("java.util.Map<java.lang.String,? extends java.lang.String[]>"));
assertFalse(file.isContentsLoaded());
assertTrue(targetType instanceof PsiArrayType);
assertTrue(targetType.equalsToText("java.lang.String[]"));
assertFalse(file.isContentsLoaded());
assertSame(targetType.getNullability().nullability(), Nullability.NOT_NULL);
}
public void testNullabilityCorrectLoadEllipsis() {
@Language("JAVA") String nullMarked =
"""
package org.jspecify.annotations;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.MODULE})
public @interface NullMarked {}""";
myFixture.addClass(nullMarked);
final PsiClass cls = myFixture.addClass(
"""
@org.jspecify.annotations.NullMarked
class Foo {
static void test(String... args) {}
}""");
PsiFileImpl file = (PsiFileImpl)cls.getContainingFile();
assertNotNull(cls.getNode());
PsiType type = cls.getMethods()[0].getParameterList().getParameters()[0].getTypeElement().getType();
assertTrue(type instanceof PsiEllipsisType);
LeakHunter.checkLeak(type, ParameterElement.class,
node -> node.getPsi().equals(cls.getMethods()[0].getParameterList().getParameters()[0]));
GCWatcher.tracking(cls.getNode()).ensureCollected();
assertFalse(file.isContentsLoaded());
assertTrue(type.equalsToText("java.lang.String..."));
assertFalse(file.isContentsLoaded());
assertSame(type.getNullability().nullability(), Nullability.NOT_NULL);
}
public void test_no_hard_refs_to_AST_via_array_component_type() {
final PsiClass cls = myFixture.addClass("class Foo { Object[] bar() {} }");
PsiFileImpl file = (PsiFileImpl)cls.getContainingFile();
@@ -1,6 +1,8 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.psi;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInsight.TypeNullability;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.application.ex.PathManagerEx;
@@ -555,4 +557,11 @@ public class ClsPsiTest extends LightIdeaTestCase {
assertThat(PsiInvalidElementAccessException.findOutInvalidationReason(mirror)).contains(
PsiInvalidElementAccessException.findOutInvalidationReason(clsFile));
}
public void testClsArrayTypeCreation() {
PsiJavaFile file = getFile("ClsContainer");
PsiType type = file.getClasses()[0].getMethods()[3].getReturnType();
TypeNullability nullability = type.getNullability();
assertEquals(Nullability.NOT_NULL, nullability.nullability());
}
}