From 4b06f4efeeb567360c6c7b094e5b1d4550d1828e Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 8 Sep 2025 18:55:20 +0200 Subject: [PATCH] [java-psi] Disable nullity processing during upper bound capturing We rely on PsiCapturedWildcardType identity when creating recursive types (the upper bound of a PsiCapturedWildcardType may refer to the same type). However, when we are under NullMarked annotation, the PsiCapturedWildcardType instance could be recreated, which prevents us from creating a proper PsiType object. Fixes IDEA-378142 Regress issue with resolve type of returned generics value GitOrigin-RevId: 4fb3c306e302715ab8de93f606397cd2a3dcb864 --- .../intellij/psi/PsiCapturedWildcardType.java | 2 +- .../src/com/intellij/psi/PsiSubstitutor.java | 13 ++++++++ .../intellij/psi/impl/PsiSubstitutorImpl.java | 31 +++++++++++++------ .../java/psi/PsiTypeNullabilityTest.java | 26 ++++++++++++++++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/java/java-psi-api/src/com/intellij/psi/PsiCapturedWildcardType.java b/java/java-psi-api/src/com/intellij/psi/PsiCapturedWildcardType.java index 0f1ef72a7e4a..91dc2de86c16 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiCapturedWildcardType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiCapturedWildcardType.java @@ -53,7 +53,7 @@ public final class PsiCapturedWildcardType extends PsiType.Stub { PsiType originalBound = !wildcardType.isSuper() ? wildcardType.getBound() : null; PsiType glb = originalBound; for (PsiType boundType : boundTypes) { - final PsiType substitutedBoundType = captureSubstitutor.substitute(boundType); + final PsiType substitutedBoundType = captureSubstitutor.substituteIgnoringNullability(boundType); //glb for array types is not specified yet if (originalBound instanceof PsiArrayType && substitutedBoundType instanceof PsiArrayType && diff --git a/java/java-psi-api/src/com/intellij/psi/PsiSubstitutor.java b/java/java-psi-api/src/com/intellij/psi/PsiSubstitutor.java index 6a41de5c7b0f..5761e7a8fa0e 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiSubstitutor.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiSubstitutor.java @@ -51,6 +51,19 @@ public interface PsiSubstitutor { @Contract(pure = true, value = "null -> null") PsiType substitute(@Nullable PsiType type); + /** + * Substitutes type parameters occurring in {@code type} with their values. + * If value for type parameter is {@code null}, appropriate erasure is returned. + * The nullability of the original type + * + * @param type the type to substitute the type parameters for.siT + * @return the result of the substitution. + */ + @Contract(pure = true, value = "null -> null") + default PsiType substituteIgnoringNullability(@Nullable PsiType type) { + return substitute(type); + } + /** * @return true if this substitutor has at least one raw substitution */ diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java index df0a897e585e..ad4c2813a61a 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java @@ -46,7 +46,6 @@ public final class PsiSubstitutorImpl implements PsiSubstitutor { private static final UnmodifiableHashMap EMPTY_MAP = UnmodifiableHashMap.empty(PSI_EQUIVALENCE); private final @NotNull UnmodifiableHashMap mySubstitutionMap; - private final SubstitutionVisitor mySimpleSubstitutionVisitor = new SubstitutionVisitor(); PsiSubstitutorImpl(@NotNull Map map) { mySubstitutionMap = UnmodifiableHashMap.fromMap(PSI_EQUIVALENCE, map); @@ -78,7 +77,7 @@ public final class PsiSubstitutorImpl implements PsiSubstitutor { } /** - * @return type mapped to type parameter; null if type parameter is mapped to null; or PsiType.VOID if no mapping exists + * @return type mapped to type parameter; null if the type parameter is mapped to null; or PsiType.VOID if no mapping exists */ private PsiType getFromMap(@NotNull PsiTypeParameter typeParameter) { if (typeParameter instanceof LightTypeParameter && ((LightTypeParameter)typeParameter).useDelegateToSubstitute()) { @@ -88,12 +87,19 @@ public final class PsiSubstitutorImpl implements PsiSubstitutor { } @Override - public PsiType substitute(PsiType type) { - if (type == null) { - return null; - } + public PsiType substitute(@Nullable PsiType type) { + return doSubstitute(type, false); + } + + @Override + public PsiType substituteIgnoringNullability(@Nullable PsiType type) { + return doSubstitute(type, true); + } + + private @Nullable PsiType doSubstitute(@Nullable PsiType type, boolean ignoreNullity) { + if (type == null) return null; PsiUtil.ensureValidType(type); - PsiType substituted = type.accept(mySimpleSubstitutionVisitor); + PsiType substituted = type.accept(new SubstitutionVisitor(ignoreNullity)); return correctExternalSubstitution(substituted, type); } @@ -151,6 +157,11 @@ public final class PsiSubstitutorImpl implements PsiSubstitutor { } private class SubstitutionVisitor extends PsiTypeMapper { + private final boolean ignoreNullity; + + private SubstitutionVisitor(boolean ignoreNullity) { + this.ignoreNullity = ignoreNullity; + } @Override public PsiType visitType(@NotNull PsiType type) { @@ -210,8 +221,10 @@ public final class PsiSubstitutorImpl implements PsiSubstitutor { // TODO: remove once nullability works better than annotations result = result.annotate(getMergedProvider(classType, result)); } - TypeNullability origNullability = classType.getNullability(); - result = origNullability.equals(TypeNullability.UNKNOWN) ? result : result.withNullability(origNullability.instantiatedWith(result.getNullability())); + if (!ignoreNullity) { + TypeNullability origNullability = classType.getNullability(); + result = origNullability.equals(TypeNullability.UNKNOWN) ? result : result.withNullability(origNullability.instantiatedWith(result.getNullability())); + } } return result; } diff --git a/java/java-tests/testSrc/com/intellij/java/psi/PsiTypeNullabilityTest.java b/java/java-tests/testSrc/com/intellij/java/psi/PsiTypeNullabilityTest.java index 22c824421f01..2b295363d083 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/PsiTypeNullabilityTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/PsiTypeNullabilityTest.java @@ -377,4 +377,30 @@ public final class PsiTypeNullabilityTest extends LightJavaCodeInsightFixtureTes TypeNullability typeNullability = info.toTypeNullability(); assertEquals("NOT_NULL (@NotNullByDefault on package foo)", typeNullability.toString()); } + + public void testFBoundResolveUnderNotNull() { + myFixture.configureByText("Test.java", """ + import org.jetbrains.annotations.NotNullByDefault; + + @NotNullByDefault + interface RestClient2 { + default void test(RequestHeadersUriSpec spec2) { + spec2.uri().attributes().retrieve(); + } + + interface UriSpec> { + S uri(); + } + + interface RequestHeadersSpec> { + S attributes(); + Object retrieve(); + } + + interface RequestHeadersUriSpec> extends UriSpec, RequestHeadersSpec { + } + } + """); + myFixture.checkHighlighting(); + } }