[java-psi] IDEA-377644 PsiImmediateClassType, PsiArrayType: reset nullability when annotations changed

Also: do not merge annotations during the substitution (unnecessary anymore after nullability is implemented and causes problems)
Some tests that rely on annotation merging during the substitution are removed/updated. The behavior was inconsistent anyway.

GitOrigin-RevId: c13e68616507bbb10cbe0384d122334879e166df
This commit is contained in:
Tagir Valeev
2025-09-18 15:04:14 +00:00
committed by intellij-monorepo-bot
parent 4f1f7eaf8a
commit fbd13ff64c
8 changed files with 56 additions and 57 deletions
@@ -1,9 +1,8 @@
// 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.refactoring.changeSignature;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.*;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableUtil;
import com.intellij.codeInsight.generation.surroundWith.SurroundWithUtil;
@@ -936,7 +935,8 @@ public final class JavaChangeSignatureUsageProcessor implements ChangeSignatureU
newType = mySubstitutor.substitute(newType);
}
if (newType != null) {
typeElement.replace(myFactory.createTypeElement(newType));
PsiTypeElement targetElement = (PsiTypeElement)typeElement.replace(myFactory.createTypeElement(newType));
applyNullability(targetElement, newType);
}
}
}
@@ -953,6 +953,22 @@ public final class JavaChangeSignatureUsageProcessor implements ChangeSignatureU
}
}
private static void applyNullability(@NotNull PsiTypeElement typeElement, @NotNull PsiType origType) {
TypeNullability typeNullability = typeElement.getType().getNullability();
Nullability nullability = typeNullability.nullability();
if (typeNullability.equals(TypeNullability.UNKNOWN)) {
PsiElement parent = typeElement.getParent();
if (parent instanceof PsiModifierListOwner owner) {
nullability = NullableNotNullManager.getNullability(owner);
}
}
if (nullability != origType.getNullability().nullability()) {
String annotation = NullableNotNullManager.getInstance(typeElement.getProject())
.getDefaultAnnotation(origType.getNullability().nullability(), typeElement);
typeElement.addAnnotation(annotation);
}
}
private static void ensureNullabilityAnnotationsDoNotRepeat(@NotNull PsiTypeElement element, PsiType newType) {
PsiElement parent = element.getParent();
if (parent instanceof PsiModifierListOwner owner &&
@@ -7,6 +7,7 @@ import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.JavaTypeNullabilityUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents an array type.
@@ -15,7 +16,7 @@ import org.jetbrains.annotations.NotNull;
*/
public class PsiArrayType extends PsiType.Stub implements JvmArrayType {
private final PsiType myComponentType;
private final TypeNullability myNullability;
private TypeNullability myNullability;
public PsiArrayType(@NotNull PsiType componentType) {
this(componentType, TypeAnnotationProvider.EMPTY);
@@ -24,14 +25,14 @@ public class PsiArrayType extends PsiType.Stub implements JvmArrayType {
public PsiArrayType(@NotNull PsiType componentType, PsiAnnotation @NotNull [] annotations) {
super(annotations);
myComponentType = componentType;
myNullability = JavaTypeNullabilityUtil.getNullabilityFromAnnotations(annotations);
myNullability = null;
}
public PsiArrayType(@NotNull PsiType componentType, @NotNull TypeAnnotationProvider provider) {
this(componentType, provider, JavaTypeNullabilityUtil.getNullabilityFromAnnotations(provider.getAnnotations()));
this(componentType, provider, null);
}
PsiArrayType(@NotNull PsiType componentType, @NotNull TypeAnnotationProvider provider, @NotNull TypeNullability nullability) {
PsiArrayType(@NotNull PsiType componentType, @NotNull TypeAnnotationProvider provider, @Nullable TypeNullability nullability) {
super(provider);
myComponentType = componentType;
myNullability = nullability;
@@ -121,6 +122,9 @@ public class PsiArrayType extends PsiType.Stub implements JvmArrayType {
@Override
public @NotNull TypeNullability getNullability() {
if (myNullability == null) {
myNullability = JavaTypeNullabilityUtil.getNullabilityFromAnnotations(getAnnotations());
}
return myNullability;
}
@@ -134,6 +138,15 @@ public class PsiArrayType extends PsiType.Stub implements JvmArrayType {
return new PsiArrayType(getComponentType(), getAnnotationProvider(), nullability);
}
@Override
public @NotNull PsiArrayType annotate(@NotNull TypeAnnotationProvider provider) {
PsiArrayType annotated = (PsiArrayType)super.annotate(provider);
if (annotated != this) {
annotated.myNullability = null;
}
return annotated;
}
@Override
public boolean equals(Object obj) {
return obj instanceof PsiArrayType &&
@@ -7,7 +7,6 @@ import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightTypeParameter;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.impl.source.PsiImmediateClassType;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceVariable;
import com.intellij.psi.util.PsiUtil;
@@ -15,7 +14,6 @@ import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.HashingStrategy;
import com.intellij.util.containers.UnmodifiableHashMap;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -145,17 +143,6 @@ public final class PsiSubstitutorImpl implements PsiSubstitutor {
return PsiType.getJavaLangObject(typeParameter.getManager(), typeParameter.getResolveScope());
}
private static @NotNull TypeAnnotationProvider getMergedProvider(@NotNull PsiType type1, @NotNull PsiType type2) {
if(type1.getAnnotationProvider() == TypeAnnotationProvider.EMPTY && !(type1 instanceof PsiClassReferenceType)) {
return type2.getAnnotationProvider();
}
if(type2.getAnnotationProvider() == TypeAnnotationProvider.EMPTY && !(type2 instanceof PsiClassReferenceType)) {
return type1.getAnnotationProvider();
}
return () -> StreamEx.of(type1.getAnnotations()).append(type2.getAnnotations()).distinct(PsiAnnotation::getText)
.toArray(PsiAnnotation.EMPTY_ARRAY);
}
private class SubstitutionVisitor extends PsiTypeMapper {
private final boolean ignoreNullity;
@@ -216,17 +203,13 @@ public final class PsiSubstitutorImpl implements PsiSubstitutor {
if (PsiTypes.voidType().equals(result)) {
return classType;
}
if (result != null) {
if (result instanceof PsiClassType || result instanceof PsiArrayType || result instanceof PsiWildcardType) {
// TODO: remove once nullability works better than annotations
result = result.annotate(getMergedProvider(classType, result));
}
if (!ignoreNullity) {
TypeNullability origNullability = classType.getNullability();
result = origNullability.equals(TypeNullability.UNKNOWN) ? result : result.withNullability(origNullability.instantiatedWith(result.getNullability()));
}
if (result == null || ignoreNullity) {
return result;
}
return result;
TypeNullability origNullability = classType.getNullability();
return origNullability.equals(TypeNullability.UNKNOWN)
? result
: result.withNullability(origNullability.instantiatedWith(result.getNullability()));
}
PsiSubstitutor resultSubstitutor = processClass(aClass, resolveResult.getSubstitutor());
return new PsiImmediateClassType(aClass, resultSubstitutor, classType.getLanguageLevel(),
@@ -120,7 +120,9 @@ public class PsiClassReferenceType extends PsiClassType.Stub {
@Override
public @NotNull PsiClassType annotate(@NotNull TypeAnnotationProvider provider) {
PsiClassReferenceType annotated = (PsiClassReferenceType)super.annotate(provider);
annotated.myNullability = null;
if (annotated != this) {
annotated.myNullability = null;
}
return annotated;
}
@@ -152,6 +152,15 @@ public class PsiImmediateClassType extends PsiClassType.Stub {
return new PsiImmediateClassType(myClass, mySubstitutor, myLanguageLevel, getAnnotationProvider(), myPsiContext, nullability);
}
@Override
public @NotNull PsiImmediateClassType annotate(@NotNull TypeAnnotationProvider provider) {
PsiImmediateClassType annotated = (PsiImmediateClassType)super.annotate(provider);
if (annotated != this) {
annotated.myNullability = null;
}
return annotated;
}
@Override
public int getParameterCount() {
PsiTypeParameterList list = myClass.getTypeParameterList();
@@ -16,7 +16,7 @@ interface Child extends Parent<@NotNull String> {
class JavaMain {
void test(Child child) {
for (@Anno String s : child.getList()) {
for (String s : child.getList()) {
}
}
@@ -20,7 +20,6 @@ import com.intellij.psi.*;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl;
import com.intellij.util.ArrayUtilRt;
@@ -195,26 +194,6 @@ public class LightAdvHighlightingFixtureTest extends LightJavaCodeInsightFixture
doTest();
}
public void testTypeAnnotations() {
myFixture.addClass("""
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.TYPE_USE})
@interface Nullable {}
""");
myFixture.addClass("class Middle<R> extends Base<@Nullable R, String>{}");
myFixture.addClass("class Child<R> extends Middle<R>{}");
PsiClass baseClass = myFixture.addClass("class Base<R, C> {}");
PsiClass fooClass = myFixture.addClass("""
class Foo {
Child<String> field;
}""");
PsiField fooField = fooClass.findFieldByName("field", false);
PsiType substituted =
TypeConversionUtil.getSuperClassSubstitutor(baseClass, (PsiClassType)fooField.getType()).substitute(baseClass.getTypeParameters()[0]);
assertEquals(1, substituted.getAnnotations().length);
}
public void testCodeFragmentMayAccessDefaultPackage() {
myFixture.addClass("public class MainClass { }");
@@ -34,12 +34,9 @@ class NlsTypeUse {
cons.accept(<warning descr="Hardcoded string literal: \"foo\"">"foo"</warning>, "bar");
}
void typeArgsCustomType(NlsValueMap<String> map, NlsValueMap2<String, String> map2) {
void typeArgsCustomType(NlsValueMap<String> map) {
map.put("foo", <warning descr="Hardcoded string literal: \"bar\"">"bar"</warning>);
map2.put("foo", <warning descr="Hardcoded string literal: \"bar\"">"bar"</warning>);
}
interface NlsValueMap<K> extends Map<K, @Nls String> {}
interface NlsValueMap2<K, V> extends Map<K, @Nls V> {}
}