[Java. Inspections] IDEA-381448 Show exact place for nullability conflict in return statements

GitOrigin-RevId: fe03e0582080b4717f7ee85f68b581780518cec4
This commit is contained in:
Georgii Ustinov
2025-11-25 18:12:48 +00:00
committed by intellij-monorepo-bot
parent b8942e68ca
commit 6a33175cfe
15 changed files with 405 additions and 67 deletions
@@ -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=\
<html><body>\
Returning a class with nullable type arguments when a class with not-null type arguments is expected{0}\
</body></html>
returning.a.class.with.notnull.arguments=\
<html><body>\
Returning a class with not-null type arguments when a class with nullable type arguments is expected{0}\
</body></html>
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
@@ -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,
@@ -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<Integer> 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<Integer> 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<Integer> 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();
}
}
@@ -2043,6 +2043,7 @@
implementationClass="com.intellij.codeInspection.wrongPackageStatement.WrongPackageStatementInspection"/>
<localInspection groupPathKey="group.path.names.probable.bugs" language="JAVA" shortName="NullableProblems" bundle="messages.JavaBundle"
key="inspection.nullable.problems.display.name"
hasStaticDescription="true"
groupKey="group.names.nullability.problems" groupBundle="messages.InspectionsBundle" enabledByDefault="true"
level="WARNING"
implementationClass="com.intellij.codeInspection.nullable.NullableStuffInspection"/>
@@ -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()
@@ -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<PsiClassType> 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<PsiType> leftParameterTypeList = getParentParameterTypeListFromDerivedType(leftType, leftClass);
List<PsiType> 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
}
}
}
@@ -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<T> {
B<@NotNull String> simpleNullableToNotNull(B<@Nullable String> arg) {
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">arg</warning>;
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected" tooltip="Returning a class with nullable type arguments when a class with not-null type arguments is expected<p>Return type: B<<b>@Nullable</b> String></p>">arg</warning>;
}
B<@Nullable String> simpleNotNullToNullable(B<@NotNull String> arg) {
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected">arg</warning>;
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected" tooltip="Returning a class with not-null type arguments when a class with nullable type arguments is expected<p>Return type: B<<b>@NotNull</b> String></p>">arg</warning>;
}
B<B<@NotNull String>> nested(B<B<@Nullable String>> arg) {
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">arg</warning>;
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected" tooltip="Returning a class with nullable type arguments when a class with not-null type arguments is expected<p>Return type: B<B<<b>@Nullable</b> String>></p>">arg</warning>;
}
B<? extends @Nullable Object> extendsWildcardNullable(B<@NotNull Object> arg) {
@@ -22,7 +23,7 @@ class B<T> {
}
B<? extends @NotNull Object> extendsWildcardNotNull(B<@Nullable String> arg) {
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">arg</warning>;
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected" tooltip="Returning a class with nullable type arguments when a class with not-null type arguments is expected<p>Return type: B<<b>@Nullable</b> String></p>">arg</warning>;
}
B<? super @NotNull String> SupperWildcard(B<@Nullable Object> arg) {
@@ -30,7 +31,7 @@ class B<T> {
}
B<? extends @NotNull Object> extendsWildcardBothNotNull(B<? extends @Nullable Object> arg) {
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">arg</warning>;
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected" tooltip="Returning a class with nullable type arguments when a class with not-null type arguments is expected<p>Return type: B<? extends <b>@Nullable</b> Object></p>">arg</warning>;
}
B<? extends @Nullable Object> extendsWildcardBothNullable(B<? extends @NotNull Object> arg) {
@@ -42,35 +43,35 @@ class B<T> {
}
B<? extends B<@NotNull Object>> nestedWithWildcard(B<B<@Nullable Object>> arg) {
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">arg</warning>;
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected" tooltip="Returning a class with nullable type arguments when a class with not-null type arguments is expected<p>Return type: B<B<<b>@Nullable</b> Object>></p>">arg</warning>;
}
Map<List<String>, List<@Nullable String>> checkIsPerformedIfSecondTypeArgumentIsTheSame(Map<List<String>, List<@NotNull String>> arg) {
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected">arg</warning>;
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected" tooltip="Returning a class with not-null type arguments when a class with nullable type arguments is expected<p>Return type: Map<List<String>, List<<b>@NotNull</b> String>></p>">arg</warning>;
}
B<@NotNull String>[] array(B<@Nullable String>[] arg) {
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">arg</warning>;
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected" tooltip="Returning a class with nullable type arguments when a class with not-null type arguments is expected<p>Return type: B<<b>@Nullable</b> String>[]</p>">arg</warning>;
}
Object[] @NotNull [] nullabilityInNestedArray(Object[] @Nullable [] arg) {
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">arg</warning>;
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected" tooltip="Returning a class with nullable type arguments when a class with not-null type arguments is expected<p>Return type: Object[] <b>@Nullable</b> []</p>">arg</warning>;
}
B<@NotNull String>[][] multiDimensionalArray(B<@Nullable String>[][] arg) {
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">arg</warning>;
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected" tooltip="Returning a class with nullable type arguments when a class with not-null type arguments is expected<p>Return type: B<<b>@Nullable</b> String>[][]</p>">arg</warning>;
}
static class C<T, V> {
C<Object, @NotNull String> secondArgument(C<Object, @Nullable String> arg) {
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">arg</warning>;
return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected" tooltip="Returning a class with nullable type arguments when a class with not-null type arguments is expected<p>Return type: C<Object, <b>@Nullable</b> String></p>">arg</warning>;
}
}
interface I {}
List<@Nullable String> intersectionSimple(Object arg) {
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected">(List<@NotNull String> & I) arg</warning>;
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected" tooltip="Returning a class with not-null type arguments when a class with nullable type arguments is expected">(List<@NotNull String> & I) arg</warning>;
}
Collection<@Nullable String> intersectionTreatsFirstMatchingUnknown(Object arg) {
@@ -78,11 +79,11 @@ class B<T> {
}
Collection<@Nullable String> intersectionTreatsFirstMatchingMismatch(Object arg) {
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected">(Collection<@NotNull String> & List<@NotNull String> & I) arg</warning>;
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected" tooltip="Returning a class with not-null type arguments when a class with nullable type arguments is expected">(Collection<@NotNull String> & List<@NotNull String> & I) arg</warning>;
}
List<List<@Nullable String>> nestedIntersection(Object arg) {
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected">(List<List<@NotNull String>> & I) arg</warning>;
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected" tooltip="Returning a class with not-null type arguments when a class with nullable type arguments is expected">(List<List<@NotNull String>> & I) arg</warning>;
}
static class NoStackOverflow {
@@ -96,4 +97,11 @@ class B<T> {
return p;
}
}
@NullMarked
static class ReturnWithNullMarked {
static List<@Nullable String> f(List<String> arg) {
return <warning descr="Returning a class with not-null type arguments when a class with nullable type arguments is expected" tooltip="Returning a class with not-null type arguments when a class with nullable type arguments is expected<p>Return type: List<<b>@NullMarked</b> String></p>">arg</warning>;
}
}
}
@@ -7,6 +7,7 @@ import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ex.InspectionManagerEx;
import com.intellij.codeInspection.nullable.NullableStuffInspection;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
@@ -31,6 +32,7 @@ import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import one.util.streamex.EntryStream;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -221,11 +223,10 @@ public class JSpecifyConformanceAnnotationTest extends LightJavaCodeInsightFixtu
}
@Override
protected void reportProblem(@NotNull ProblemsHolder holder,
@NotNull PsiElement anchor,
LocalQuickFix @NotNull [] fixes,
@NotNull String messageKey, Object... args) {
switch (messageKey) {
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) {
switch (descriptionKey) {
case "inspection.nullable.problems.primitive.type.annotation",
"inspection.nullable.problems.at.throws",
"inspection.nullable.problems.at.type.parameter",
@@ -14,6 +14,7 @@ import com.intellij.codeInspection.dataFlow.NullabilityProblemKind;
import com.intellij.codeInspection.ex.InspectionManagerEx;
import com.intellij.codeInspection.nullable.NotNullFieldNotInitializedInspection;
import com.intellij.codeInspection.nullable.NullableStuffInspection;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.module.Module;
@@ -41,6 +42,7 @@ import one.util.streamex.EntryStream;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
@@ -561,11 +563,10 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT
}
@Override
protected void reportProblem(@NotNull ProblemsHolder holder,
@NotNull PsiElement anchor,
LocalQuickFix @NotNull [] fixes,
@NotNull String messageKey, Object... args) {
switch (messageKey) {
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) {
switch (descriptionKey) {
case "inspection.nullable.problems.primitive.type.annotation", "inspection.nullable.problems.receiver.annotation",
"inspection.nullable.problems.outer.type", "inspection.nullable.problems.at.reference.list",
"inspection.nullable.problems.at.constructor", "inspection.nullable.problems.at.enum.constant" ->
@@ -511,6 +511,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
public void testReturnIncompatibilitiesWithGeneric() {
myInspection.REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS = true;
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
+4
View File
@@ -287,6 +287,8 @@ com.intellij.codeInspection.HintAction
- com.intellij.codeInsight.intention.IntentionAction
- fixSilently(com.intellij.openapi.editor.Editor):Z
- a:showHint(com.intellij.openapi.editor.Editor):Z
a:com.intellij.codeInspection.InspectionManager
- a:createProblemDescriptor(com.intellij.psi.PsiElement,com.intellij.openapi.util.TextRange,java.lang.String,com.intellij.codeInspection.ProblemHighlightType,java.lang.String,Z,com.intellij.codeInspection.LocalQuickFix[]):com.intellij.codeInspection.ProblemDescriptor
com.intellij.codeInspection.InspectionProfile
- java.lang.Comparable
- sf:DEFAULT_PROFILE_NAME:java.lang.String
@@ -347,6 +349,8 @@ com.intellij.codeInspection.ProblemDescriptor
- a:setProblemGroup(com.intellij.lang.annotation.ProblemGroup):V
- a:setTextAttributes(com.intellij.openapi.editor.colors.TextAttributesKey):V
- a:showTooltip():Z
f:com.intellij.codeInspection.ProblemsHolder$ProblemBuilder
- tooltip(java.lang.String):com.intellij.codeInspection.ProblemsHolder$ProblemBuilder
com.intellij.codeInspection.QuickFix
- com.intellij.openapi.application.WriteActionAware
- sf:EMPTY_ARRAY:com.intellij.codeInspection.QuickFix[]
@@ -81,6 +81,14 @@ public abstract class InspectionManager {
boolean onTheFly,
@NotNull LocalQuickFix @Nullable ... fixes);
public abstract @NotNull ProblemDescriptor createProblemDescriptor(@NotNull PsiElement psiElement,
@Nullable("null means the text range of the element") TextRange rangeInElement,
@NotNull @InspectionMessage String descriptionTemplate,
@NotNull ProblemHighlightType highlightType,
@NotNull String tooltipMessage,
boolean onTheFly,
@NotNull LocalQuickFix @Nullable ... fixes);
@Contract(pure = true)
public abstract @NotNull ProblemDescriptor createProblemDescriptor(@NotNull PsiElement psiElement,
@NotNull @InspectionMessage String descriptionTemplate,
@@ -244,10 +244,12 @@ public class ProblemsHolder {
private @NotNull ProblemHighlightType myHighlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
private @Nullable TextRange myRange;
private final @NotNull List<LocalQuickFix> myFixes = new ArrayList<>();
private @NotNull String myTooltip;
private ProblemBuilder(@NotNull PsiElement element, @InspectionMessage @NotNull String template) {
myPsiElement = element;
myDescriptionTemplate = template;
myTooltip = myDescriptionTemplate;
}
/**
@@ -314,8 +316,24 @@ public class ProblemsHolder {
return this;
}
/**
* @param tooltip a message that is visible in the editor. It might contain HTML markup.
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
public ProblemBuilder tooltip(@NotNull String tooltip) {
myTooltip = tooltip;
return this;
}
public void register() {
registerProblem(myPsiElement, myDescriptionTemplate, myHighlightType, myRange, myFixes.toArray(LocalQuickFix.EMPTY_ARRAY));
ProblemDescriptor descriptor = getManager()
.createProblemDescriptor(myPsiElement,
myRange,
myDescriptionTemplate,
myHighlightType, myTooltip, myOnTheFly,
myFixes.toArray(LocalQuickFix.EMPTY_ARRAY));
registerProblem(descriptor);
}
}
}
+2
View File
@@ -246,6 +246,8 @@ com.intellij.codeInsight.template.impl.TemplateOptionalProcessor
com.intellij.codeInsight.template.impl.TemplatePreprocessor
- sf:EP_NAME:com.intellij.openapi.extensions.ExtensionPointName
- a:preprocessTemplate(com.intellij.openapi.editor.Editor,com.intellij.psi.PsiFile,I,java.lang.String,java.lang.String):V
a:com.intellij.codeInspection.InspectionManagerBase
- createProblemDescriptor(com.intellij.psi.PsiElement,com.intellij.openapi.util.TextRange,java.lang.String,com.intellij.codeInspection.ProblemHighlightType,java.lang.String,Z,com.intellij.codeInspection.LocalQuickFix[]):com.intellij.codeInspection.ProblemDescriptor
com.intellij.codeInspection.InspectionToolResultExporter
- com.intellij.codeInspection.ProblemDescriptionsProcessor
- a:addProblemElement(com.intellij.codeInspection.reference.RefEntity,Z,com.intellij.codeInspection.CommonProblemDescriptor[]):V
@@ -98,6 +98,18 @@ public abstract class InspectionManagerBase extends InspectionManager {
return new ProblemDescriptorBase(psiElement, psiElement, descriptionTemplate, fixes, highlightType, false, rangeInElement, tooltip, onTheFly);
}
@Override
public @NotNull ProblemDescriptor createProblemDescriptor(@NotNull PsiElement psiElement,
@Nullable("null means the text range of the element") TextRange rangeInElement,
@NotNull String descriptionTemplate,
@NotNull ProblemHighlightType highlightType,
@Nullable String tooltipMessage,
boolean onTheFly,
@NotNull LocalQuickFix @Nullable ... fixes) {
boolean showTooltip = highlightType != ProblemHighlightType.INFORMATION;
return new ProblemDescriptorBase(psiElement, psiElement, descriptionTemplate, fixes, highlightType, false, rangeInElement, showTooltip, onTheFly, tooltipMessage);
}
@Override
public @NotNull ProblemDescriptor createProblemDescriptor(@NotNull PsiElement psiElement,
@NotNull @InspectionMessage String descriptionTemplate,