diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties
index f12e26a2bc25..99f30a38feaa 100644
--- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties
+++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties
@@ -132,8 +132,15 @@ annotate.overridden.methods.parameters=Annotate overriding method parameters as
anonymous.ref.loc.can.be.replaced.with.0=Anonymous #ref #loc can be replaced with {0}
anonymous.ref.loc.can.be.replaced.with.lambda=Anonymous #ref #loc can be replaced with lambda
assigning.a.collection.of.nullable.elements=Assigning a collection of nullable elements into a collection of non-null elements
-returning.a.class.with.nullable.arguments=Returning a class with nullable type arguments when a class with not-null type arguments is expected
-returning.a.class.with.notnull.arguments=Returning a class with not-null type arguments when a class with nullable type arguments is expected
+returning.a.class.with.nullable.arguments=\
+
\
+ Returning a class with nullable type arguments when a class with not-null type arguments is expected{0}\
+
+returning.a.class.with.notnull.arguments=\
+ \
+ Returning a class with not-null type arguments when a class with nullable type arguments is expected{0}\
+
+returning.a.type.nullability.conflict.message=Return type:
conflicting.nullability.annotations=Conflicting nullability annotations
nullable.stuff.error.overriding.nullable.with.notnull=Overriding a collection of nullable elements with a collection of non-null elements
nullable.stuff.error.overriding.notnull.with.nullable=Overriding a collection of non-null elements with a collection of nullable elements
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java
index 24660d075f09..acf86cbaadc8 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java
@@ -533,13 +533,16 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
private void checkGenericClassOnReturn(@Nullable PsiType expectedType,
@NotNull PsiExpression returnValue) {
PsiType returnType = returnValue.getType();
- JavaTypeNullabilityUtil.NullabilityConflict
- conflict = JavaTypeNullabilityUtil.getNullabilityConflictInAssignment(expectedType, returnType,
- REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS);
- if (conflict == JavaTypeNullabilityUtil.NullabilityConflict.UNKNOWN) return;
- String messageKey = conflict == JavaTypeNullabilityUtil.NullabilityConflict.NOT_NULL_TO_NULL ?
+ JavaTypeNullabilityUtil.NullabilityConflictContext
+ context = JavaTypeNullabilityUtil.getNullabilityConflictInAssignment(expectedType, returnType,
+ REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS);
+ if (context.nullabilityConflict == JavaTypeNullabilityUtil.NullabilityConflict.UNKNOWN) return;
+ String messageKey = context.nullabilityConflict == JavaTypeNullabilityUtil.NullabilityConflict.NOT_NULL_TO_NULL ?
"returning.a.class.with.notnull.arguments" : "returning.a.class.with.nullable.arguments";
- reportProblem(holder, returnValue, messageKey);
+
+ reportProblem(holder, returnValue, LocalQuickFix.EMPTY_ARRAY,
+ messageKey, new Object[]{""},
+ messageKey, new Object[]{NullableStuffInspectionUtil.getTypePresentationInNullabilityConflict(context)});
}
private void checkCollectionNullityOnAssignment(@NotNull PsiElement errorElement,
@@ -587,9 +590,19 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
protected void reportProblem(@NotNull ProblemsHolder holder, @NotNull PsiElement anchor, @NotNull LocalQuickFix @NotNull [] fixes,
@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String messageKey, Object... args) {
- holder.registerProblem(anchor,
- JavaAnalysisBundle.message(messageKey, args),
- ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fixes);
+ reportProblem(holder, anchor, fixes, messageKey, args, messageKey, args);
+ }
+
+ protected void reportProblem(@NotNull ProblemsHolder holder, @NotNull PsiElement anchor, @NotNull LocalQuickFix @NotNull [] fixes,
+ @NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String descriptionKey, @NotNull Object @NotNull[] descriptionArgs,
+ @NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String tooltipKey, @NotNull Object @NotNull[] tooltipArgs) {
+ ProblemsHolder.ProblemBuilder builder = holder.problem(anchor, JavaAnalysisBundle.message(descriptionKey, descriptionArgs))
+ .tooltip(JavaAnalysisBundle.message(tooltipKey, tooltipArgs))
+ .highlight(ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
+ for (LocalQuickFix quickFix : fixes) {
+ builder.fix(quickFix);
+ }
+ builder.register();
}
private static boolean isNullableNotNullCollectionConflict(@Nullable PsiType expectedType,
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionUtil.java
new file mode 100644
index 000000000000..c3062c9d0aea
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionUtil.java
@@ -0,0 +1,200 @@
+// 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.codeInspection.nullable;
+
+import com.intellij.java.analysis.JavaAnalysisBundle;
+import com.intellij.openapi.util.NlsSafe;
+import com.intellij.openapi.util.text.HtmlChunk;
+import com.intellij.psi.*;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.JavaTypeNullabilityUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+final class NullableStuffInspectionUtil {
+ static @NotNull @NlsSafe String getTypePresentationInNullabilityConflict(@NotNull JavaTypeNullabilityUtil.NullabilityConflictContext context) {
+ PsiElement place = context.getPlace();
+ if (place == null) return "";
+ int dimensionsInArray = (context.type instanceof PsiArrayType type) ? type.getArrayDimensions() : 0;
+
+ PsiTypeElement topmostType = PsiTreeUtil.getTopmostParentOfType(place, PsiTypeElement.class);
+ if (topmostType == null) return "";
+
+ PsiTypeElement target = PsiTreeUtil.getParentOfType(place, PsiTypeElement.class, false);
+ if (target == null) return "";
+
+ PsiType outerType = topmostType.getType();
+ if (target == topmostType) {
+ // Presentation is not handling with the whole type
+ return "";
+ }
+
+ List path = computeTypeArgumentPath(topmostType, target, dimensionsInArray);
+ if (path == null) return "";
+
+ Context presentationContext = getPresentationContext(outerType, path, outerType instanceof PsiArrayType);
+
+ String typeText = presentationContext.sb.toString();
+ String annotationText = getAnnotationText(context);
+ if (presentationContext.position == null || annotationText == null) return "";
+ HtmlChunk result = generateHtmlChunk(typeText, "@" + annotationText, presentationContext.position);
+
+ return result.toString();
+ }
+
+ private static @Nullable String getAnnotationText(@NotNull JavaTypeNullabilityUtil.NullabilityConflictContext context) {
+ PsiAnnotation annotation = context.getAnnotation();
+ if (annotation == null) return null;
+ PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement();
+ if (ref == null) return null;
+ return ref.getReferenceName();
+ }
+
+ private static @NotNull HtmlChunk generateHtmlChunk(@NlsSafe String text,
+ @NlsSafe String annotationText,
+ int position) {
+ HtmlChunk result = HtmlChunk.p().child(
+ HtmlChunk.fragment(
+ HtmlChunk.text(JavaAnalysisBundle.message("returning.a.type.nullability.conflict.message")),
+ HtmlChunk.text(" "),
+ HtmlChunk.text(text.substring(0, position)),
+ HtmlChunk.tag("b").children(HtmlChunk.text(annotationText)),
+ HtmlChunk.text(" "),
+ HtmlChunk.text(text.substring(position))
+ )
+ );
+ return result;
+ }
+
+ private static @Nullable List<@NotNull Integer> computeTypeArgumentPath(@NotNull PsiTypeElement top, @NotNull PsiTypeElement target, int firstArrayDepth) {
+ List indices = new ArrayList<>();
+ PsiTypeElement current = target;
+ boolean isFirstArrayOccurence = true;
+ while (true) {
+ PsiTypeElement parentTypeElement = PsiTreeUtil.getParentOfType(current, PsiTypeElement.class, true);
+ if (parentTypeElement == null) return null;
+ PsiType parentType = parentTypeElement.getType();
+ if (parentType instanceof PsiClassType) {
+ PsiJavaCodeReferenceElement ref = parentTypeElement.getInnermostComponentReferenceElement();
+ if (ref == null) return null;
+ PsiReferenceParameterList params = ref.getParameterList();
+ if (params == null) return null;
+ PsiTypeElement[] args = params.getTypeParameterElements();
+ int found = -1;
+ for (int i = 0; i < args.length; i++) {
+ if (args[i] == current) {
+ found = i;
+ break;
+ }
+ }
+ if (found < 0) return null;
+ indices.add(found);
+ }
+ else if (parentType instanceof PsiWildcardType ||
+ parentType instanceof PsiCapturedWildcardType) {
+ indices.add(0);
+ } else if (parentType instanceof PsiArrayType arrayType) {
+ int candidateArrayDepth = arrayType.getArrayDimensions();
+ int numberToAdd;
+ if (isFirstArrayOccurence && candidateArrayDepth == firstArrayDepth) {
+ numberToAdd = 0;
+ } else if (isFirstArrayOccurence && firstArrayDepth > 0) {
+ numberToAdd = firstArrayDepth;
+ } else {
+ numberToAdd = candidateArrayDepth;
+ }
+ for (int i = 0; i < numberToAdd; i++) {
+ indices.add(0);
+ }
+ }
+ if (parentTypeElement == top) break;
+ current = parentTypeElement;
+ isFirstArrayOccurence = false;
+ }
+ Collections.reverse(indices);
+ return indices;
+ }
+
+ private static @NotNull Context getPresentationContext(@NotNull PsiType type, List path, boolean isInsideArray) {
+ Context context = new Context();
+ buildTextRepresentation(type, context, path, 0, isInsideArray);
+ return context;
+ }
+
+ /**
+ * Constructs the simplified presentable view of the type and detects the place in which the nullability annotation should be inserted.
+ * The result of this method is stored in the {@link Context#sb} member.
+ * @param isInsideArray - Corresponds to the state whether the method is called within the array dimension. It is used to distinguish
+ * cases {@code @Nullable String[]} and {@code String @Nullable [][]}.
+ */
+ private static void buildTextRepresentation(@NotNull PsiType type,
+ @NotNull Context context,
+ @NotNull List<@NotNull Integer> path,
+ int depth,
+ boolean isInsideArray) {
+ if (depth == path.size()) {
+ if (type instanceof PsiArrayType arrayType && isInsideArray) {
+ context.sb.append(arrayType.getPresentableText(false));
+ context.sb.append(" ");
+ context.position = context.sb.length();
+ }
+ else if (type instanceof PsiArrayType arrayType) {
+ context.sb.append(arrayType.getDeepComponentType().getPresentableText(false));
+ context.sb.append(" ");
+ context.position = context.sb.length();
+ context.sb.repeat("[]", Math.max(0, arrayType.getArrayDimensions()));
+ }
+ else {
+ String text = type.getPresentableText(false);
+ context.position = context.sb.length();
+ context.sb.append(text);
+ }
+ return;
+ }
+ if (type instanceof PsiArrayType arrayType) {
+ PsiType component = arrayType.getComponentType();
+ buildTextRepresentation(component, context, path, depth + 1, true);
+ context.sb.append("[]");
+ return;
+ }
+ if (type instanceof PsiWildcardType wc) {
+ String prefix = wc.isExtends() ? "? extends " : wc.isSuper() ? "? super " : "?";
+ context.sb.append(prefix);
+ PsiType bound = wc.getBound();
+ if (bound != null) {
+ buildTextRepresentation(bound, context, path, depth + 1, false);
+ }
+ return;
+ }
+ if (type instanceof PsiClassType cls) {
+ String name = cls.getClassName();
+ if (name == null) name = cls.getPresentableText(false);
+ context.sb.append(name);
+ PsiType[] params = cls.getParameters();
+ if (params.length > 0) {
+ context.sb.append('<');
+ for (int i = 0; i < params.length; i++) {
+ if (i > 0) context.sb.append(", ");
+ if (i == path.get(depth)) {
+ buildTextRepresentation(params[i], context, path, depth + 1, false);
+ }
+ else {
+ context.sb.append(params[i].getPresentableText(false));
+ }
+ }
+ context.sb.append('>');
+ }
+ return;
+ }
+
+ context.sb.append(type.getPresentableText(false));
+ }
+
+ private static final class Context {
+ private @Nullable Integer position = null;
+ private final @NotNull StringBuilder sb = new StringBuilder();
+ }
+}
diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml
index ecf95a529137..522df70ffe67 100644
--- a/java/java-impl/resources/META-INF/JavaPlugin.xml
+++ b/java/java-impl/resources/META-INF/JavaPlugin.xml
@@ -2043,6 +2043,7 @@
implementationClass="com.intellij.codeInspection.wrongPackageStatement.WrongPackageStatementInspection"/>
diff --git a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java
index b95e44f8e430..095ce25e4018 100644
--- a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java
+++ b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java
@@ -11,6 +11,7 @@ import com.intellij.codeInspection.options.OptPane;
import com.intellij.codeInspection.options.OptionController;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.java.JavaBundle;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
@@ -25,6 +26,7 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import static com.intellij.codeInspection.options.OptPane.checkbox;
import static com.intellij.codeInspection.options.OptPane.pane;
@@ -54,6 +56,15 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
);
}
+ @Override
+ public @Nullable @Nls String getStaticDescription() {
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ return "";
+ } else {
+ return null;
+ }
+ }
+
@Override
public @NotNull OptionController getOptionController() {
return super.getOptionController()
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 8c0b58ed79fa..9eb6eabfe956 100644
--- a/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java
+++ b/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java
@@ -11,10 +11,7 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
/**
* Helper methods to compute nullability of Java types.
@@ -23,14 +20,14 @@ import java.util.Set;
public final class JavaTypeNullabilityUtil {
/**
* Computes the class type nullability
- *
+ *
* @param type type to compute nullability for
* @return type nullability
*/
public static @NotNull TypeNullability getTypeNullability(@NotNull PsiClassType type) {
return getTypeNullability(type, null, !isLocal(type));
}
-
+
private static @NotNull TypeNullability getTypeNullability(@NotNull PsiClassType type,
@Nullable Set visited,
boolean checkContainer) {
@@ -77,7 +74,7 @@ public final class JavaTypeNullabilityUtil {
}
return TypeNullability.UNKNOWN;
}
-
+
private static boolean isLocal(PsiClassType classType) {
PsiElement context = classType.getPsiContext();
return context instanceof PsiJavaCodeReferenceElement &&
@@ -115,27 +112,27 @@ public final class JavaTypeNullabilityUtil {
* @param rightType assigned value
* @param checkNotNullToNull whether to check for nullability conflict when assigning not-null to null value
*
- * @see JavaTypeNullabilityUtil#getNullabilityConflictType(PsiType, PsiType)
+ * @see JavaTypeNullabilityUtil#getNullabilityConflictTypeContext(PsiType, PsiType)
*/
- public static @NotNull NullabilityConflict getNullabilityConflictInAssignment(@Nullable PsiType leftType,
+ public static @NotNull NullabilityConflictContext getNullabilityConflictInAssignment(@Nullable PsiType leftType,
@Nullable PsiType rightType,
boolean checkNotNullToNull) {
return getNullabilityConflictInAssignment(leftType, rightType, checkNotNullToNull, false);
}
- private static @NotNull NullabilityConflict getNullabilityConflictInAssignment(@Nullable PsiType leftType,
+ private static @NotNull NullabilityConflictContext getNullabilityConflictInAssignment(@Nullable PsiType leftType,
@Nullable PsiType rightType,
boolean checkNotNullToNull,
boolean checkConflictInInitialType) {
if (checkConflictInInitialType) {
- NullabilityConflict nullabilityConflict = getNullabilityConflictType(leftType, rightType);
- if (isAllowedNullabilityConflictType(checkNotNullToNull, nullabilityConflict)) return nullabilityConflict;
+ NullabilityConflictContext context = getNullabilityConflictTypeContext(leftType, rightType);
+ if (isAllowedNullabilityConflictType(checkNotNullToNull, context)) return context;
}
if (leftType == null || TypeConversionUtil.isNullType(leftType) ||
rightType == null || TypeConversionUtil.isNullType(rightType)
) {
- return NullabilityConflict.UNKNOWN;
+ return NullabilityConflictContext.UNKNOWN;
}
if (rightType instanceof PsiIntersectionType) {
@@ -163,7 +160,7 @@ public final class JavaTypeNullabilityUtil {
}
if (!(leftType instanceof PsiClassType) || !(rightType instanceof PsiClassType)) {
- return NullabilityConflict.UNKNOWN;
+ return NullabilityConflictContext.UNKNOWN;
}
return getNullabilityConflictInTypeArguments(leftType, rightType, checkNotNullToNull);
@@ -178,35 +175,34 @@ public final class JavaTypeNullabilityUtil {
* @param checkNotNullToNull whether to check for nullability conflict when assigning not-null to null value.
* @return first inconsistency in nullability inside generic class type arguments.
*/
- private static @NotNull NullabilityConflict getNullabilityConflictInTypeArguments(@NotNull PsiType leftType,
+ private static @NotNull NullabilityConflictContext getNullabilityConflictInTypeArguments(@NotNull PsiType leftType,
@NotNull PsiType rightType,
boolean checkNotNullToNull) {
- if (isRawType(leftType) || isRawType(rightType)) return NullabilityConflict.UNKNOWN;
+ if (isRawType(leftType) || isRawType(rightType)) return NullabilityConflictContext.UNKNOWN;
PsiClass leftClass = PsiTypesUtil.getPsiClass(leftType);
- if (leftClass == null) return NullabilityConflict.UNKNOWN;
-
+ if (leftClass == null) return NullabilityConflictContext.UNKNOWN;
List leftParameterTypeList = getParentParameterTypeListFromDerivedType(leftType, leftClass);
List rightParameterTypeList = getParentParameterTypeListFromDerivedType(rightType, leftClass);
if (leftParameterTypeList == null ||
rightParameterTypeList == null ||
leftParameterTypeList.size() != rightParameterTypeList.size()) {
- return NullabilityConflict.UNKNOWN;
+ return NullabilityConflictContext.UNKNOWN;
}
for (int i = 0; i < leftParameterTypeList.size(); i++) {
PsiType leftParameterType = leftParameterTypeList.get(i);
PsiType rightParameterType = rightParameterTypeList.get(i);
- NullabilityConflict nullabilityConflict = getNullabilityConflictInAssignment(
+ NullabilityConflictContext context = getNullabilityConflictInAssignment(
leftParameterType,
rightParameterType,
checkNotNullToNull,
true
);
- if (nullabilityConflict != NullabilityConflict.UNKNOWN) return nullabilityConflict;
+ if (context.nullabilityConflict != NullabilityConflict.UNKNOWN) return context;
}
- return NullabilityConflict.UNKNOWN;
+ return NullabilityConflictContext.UNKNOWN;
}
private static boolean isRawType(@NotNull PsiType type) {
@@ -225,9 +221,9 @@ public final class JavaTypeNullabilityUtil {
return PsiUtil.substituteTypeParameters(derivedType, superClass, true);
}
- private static boolean isAllowedNullabilityConflictType(boolean checkNotNullToNull, @NotNull NullabilityConflict nullabilityConflict) {
- return nullabilityConflict != NullabilityConflict.UNKNOWN &&
- (checkNotNullToNull || nullabilityConflict != NullabilityConflict.NOT_NULL_TO_NULL);
+ private static boolean isAllowedNullabilityConflictType(boolean checkNotNullToNull, @NotNull NullabilityConflictContext context) {
+ return context.nullabilityConflict != NullabilityConflict.UNKNOWN &&
+ (checkNotNullToNull || context.nullabilityConflict != NullabilityConflict.NOT_NULL_TO_NULL);
}
/**
@@ -236,18 +232,73 @@ public final class JavaTypeNullabilityUtil {
* @param rightType assigned value
* @see NullabilityConflict
*/
- public static @NotNull NullabilityConflict getNullabilityConflictType(@Nullable PsiType leftType, @Nullable PsiType rightType) {
- if (leftType == null || rightType == null) return NullabilityConflict.UNKNOWN;
+ public static @NotNull NullabilityConflictContext getNullabilityConflictTypeContext(@Nullable PsiType leftType, @Nullable PsiType rightType) {
+ if (leftType == null || rightType == null) return NullabilityConflictContext.UNKNOWN;
TypeNullability leftTypeNullability = leftType.getNullability();
TypeNullability rightTypeNullability = rightType.getNullability();
Nullability leftNullability = leftTypeNullability.nullability();
Nullability rightNullability = rightTypeNullability.nullability();
- if (leftNullability == Nullability.NOT_NULL && rightNullability == Nullability.NULLABLE) return NullabilityConflict.NULL_TO_NOT_NULL;
+ if (leftNullability == Nullability.NOT_NULL && rightNullability == Nullability.NULLABLE) {
+ return new NullabilityConflictContext(NullabilityConflict.NULL_TO_NOT_NULL, rightType);
+ }
// It is not possible to have NOT_NULL_TO_NULL conflict when left type is wildcard with upper bound,
// e.g., this assignment is legal {@code List extends @Nullable Object> = List<@NotNull String>}
- else if (leftNullability == Nullability.NULLABLE && rightNullability == Nullability.NOT_NULL && !GenericsUtil.isWildcardWithExtendsBound(leftType)) return NullabilityConflict.NOT_NULL_TO_NULL;
- return NullabilityConflict.UNKNOWN;
+ else if (leftNullability == Nullability.NULLABLE && rightNullability == Nullability.NOT_NULL && !GenericsUtil.isWildcardWithExtendsBound(leftType)) {
+ return new NullabilityConflictContext(NullabilityConflict.NOT_NULL_TO_NULL, rightType);
+ }
+ return NullabilityConflictContext.UNKNOWN;
+ }
+
+
+ /**
+ * Holds information about the nullability conflict that might be used to provide more descriptive error messages.
+ */
+ public static class NullabilityConflictContext {
+ public final @NotNull NullabilityConflict nullabilityConflict;
+ public final @Nullable PsiType type;
+
+ public static final NullabilityConflictContext UNKNOWN = new NullabilityConflictContext(NullabilityConflict.UNKNOWN, null);
+
+ public NullabilityConflictContext(@NotNull NullabilityConflict nullabilityConflict, @Nullable PsiType type) {
+ this.type = type;
+ this.nullabilityConflict = nullabilityConflict;
+ }
+
+ /**
+ * @return type argument or array type in which the conflict is occurred.
+ */
+ public @Nullable PsiElement getPlace() {
+ return getPlace(type);
+ }
+
+
+ /**
+ * @return nullability annotation that produces the conflict.
+ */
+ public @Nullable PsiAnnotation getAnnotation() {
+ if (type == null) return null;
+ TypeNullability nullability = type.getNullability();
+ NullabilityAnnotationInfo info = nullability.toNullabilityAnnotationInfo();
+ if (info == null) return null;
+ return info.getAnnotation();
+ }
+
+ private static @Nullable PsiElement getPlace(PsiType placeHolder) {
+ if (placeHolder instanceof PsiClassType) {
+ return ((PsiClassType)placeHolder).getPsiContext();
+ }
+ else if (placeHolder instanceof PsiCapturedWildcardType) {
+ return getPlace(((PsiCapturedWildcardType) placeHolder).getWildcard());
+ }
+ else if (placeHolder instanceof PsiWildcardType) {
+ return getPlace(((PsiWildcardType)placeHolder).getBound());
+ }
+ else if (placeHolder instanceof PsiArrayType) {
+ return getPlace(placeHolder.getDeepComponentType());
+ }
+ return null;
+ }
}
/**
@@ -267,4 +318,4 @@ public final class JavaTypeNullabilityUtil {
*/
UNKNOWN
}
-}
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/nullableProblems/ReturnIncompatibilitiesWithGeneric.java b/java/java-tests/testData/inspection/nullableProblems/ReturnIncompatibilitiesWithGeneric.java
index eb5500ba3eb8..d94919ecab6e 100644
--- a/java/java-tests/testData/inspection/nullableProblems/ReturnIncompatibilitiesWithGeneric.java
+++ b/java/java-tests/testData/inspection/nullableProblems/ReturnIncompatibilitiesWithGeneric.java
@@ -1,20 +1,21 @@
import org.jspecify.annotations.NotNull;
import org.jspecify.annotations.Nullable;
+import org.jspecify.annotations.NullMarked;
import java.util.List;
import java.util.Map;
import java.util.Collection;
class B {
B<@NotNull String> simpleNullableToNotNull(B<@Nullable String> arg) {
- return arg;
+ return arg;
}
B<@Nullable String> simpleNotNullToNullable(B<@NotNull String> arg) {
- return arg;
+ return arg;
}
B> nested(B> arg) {
- return arg;
+ return arg;
}
B extends @Nullable Object> extendsWildcardNullable(B<@NotNull Object> arg) {
@@ -22,7 +23,7 @@ class B {
}
B extends @NotNull Object> extendsWildcardNotNull(B<@Nullable String> arg) {
- return arg;
+ return arg;
}
B super @NotNull String> SupperWildcard(B<@Nullable Object> arg) {
@@ -30,7 +31,7 @@ class B {
}
B extends @NotNull Object> extendsWildcardBothNotNull(B extends @Nullable Object> arg) {
- return arg;
+ return arg;
}
B extends @Nullable Object> extendsWildcardBothNullable(B extends @NotNull Object> arg) {
@@ -42,35 +43,35 @@ class B {
}
B extends B<@NotNull Object>> nestedWithWildcard(B> arg) {
- return arg;
+ return arg;
}
Map, List<@Nullable String>> checkIsPerformedIfSecondTypeArgumentIsTheSame(Map, List<@NotNull String>> arg) {
- return arg;
+ return arg;
}
B<@NotNull String>[] array(B<@Nullable String>[] arg) {
- return arg;
+ return arg;
}
Object[] @NotNull [] nullabilityInNestedArray(Object[] @Nullable [] arg) {
- return arg;
+ return arg;
}
B<@NotNull String>[][] multiDimensionalArray(B<@Nullable String>[][] arg) {
- return arg;
+ return arg;
}
static class C {
C