[java-inspections] IDEA-377682 JSpecify. Nullable incompatibilities. Calls with generics

GitOrigin-RevId: 5b501cf6f6e99a675ad2e78283000a0be86fb3ff
This commit is contained in:
Mikhail Pyltsin
2026-01-16 15:32:49 +01:00
committed by intellij-monorepo-bot
parent 0c57575a17
commit 76d276b20d
11 changed files with 367 additions and 122 deletions

View File

@@ -132,7 +132,10 @@ annotate.overridden.methods.parameters.nonnull=Annotate overriding method parame
annotate.overridden.methods.parameters.nullable=Annotate overriding method parameters as nullable
anonymous.ref.loc.can.be.replaced.with.0=Anonymous #ref can be replaced with {0}
anonymous.ref.loc.can.be.replaced.with.lambda=Anonymous #ref can be replaced with lambda
assigning.a.collection.of.nullable.elements=Assigning a collection of nullable elements into a collection of non-null elements
complex.problem.with.nullability=\
<html><body>\
Incompatible type arguments due to nullability{0}\
</body></html>
assigning.a.class.with.nullable.elements=\
<html><body>\
Assigning a class with nullable type arguments when a class with not-null type arguments is expected{0}\
@@ -141,6 +144,14 @@ assigning.a.class.with.notnull.elements=\
<html><body>\
Assigning a class with not-null type arguments when a class with nullable type arguments is expected{0}\
</body></html>
overriding.a.class.with.nullable.elements=\
<html><body>\
Overriding a class with nullable type arguments when a class with not-null type arguments is expected{0}\
</body></html>
overriding.a.class.with.notnull.elements=\
<html><body>\
Overriding a class with not-null type arguments when a class with nullable type arguments is expected{0}\
</body></html>
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}\
@@ -152,8 +163,6 @@ returning.a.class.with.notnull.arguments=\
expected.type.nullability.conflict.message=Expected type:
actual.type.nullability.conflict.message=Actual 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
comparision.between.object.and.primitive=Comparison between Object and primitive is illegal and is accepted in Java 7 only
custom.exception.class.should.have.a.constructor=Custom exception class should have a constructor with a single message parameter of String type
delimiters.argument.contains.duplicated.characters=StringTokenizer 'delimiters' argument contains duplicated characters

View File

@@ -2,7 +2,6 @@
package com.intellij.codeInspection.nullable;
import com.intellij.codeInsight.*;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.MoveAnnotationOnStaticMemberQualifyingTypeFix;
import com.intellij.codeInsight.intention.AddAnnotationModCommandAction;
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
@@ -19,7 +18,6 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.GeneratedSourcesFilter;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
@@ -187,9 +185,10 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
JavaResolveResult result = expression.advancedResolve(false);
PsiElement target = result.getElement();
if (target instanceof PsiMethod) {
checkCollectionNullityOnAssignment(expression,
LambdaUtil.getFunctionalInterfaceReturnType(expression),
result.getSubstitutor().substitute(((PsiMethod)target).getReturnType()));
checkNestedGenericClasses(holder, expression,
LambdaUtil.getFunctionalInterfaceReturnType(expression),
result.getSubstitutor().substitute(((PsiMethod)target).getReturnType()),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
}
@@ -213,7 +212,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
PsiExpression initializer = field.getInitializer();
PsiElement identifyingElement = field.getIdentifyingElement();
if (initializer != null && identifyingElement != null) {
checkNestedGenericClasses(identifyingElement, field.getType(), initializer.getType(),
checkNestedGenericClasses(holder, identifyingElement, field.getType(), initializer.getType(),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
}
@@ -491,7 +490,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) {
PsiExpression rExpression = expression.getRExpression();
if (rExpression == null) return;
checkNestedGenericClasses(expression.getOperationSign(),
checkNestedGenericClasses(holder, expression.getOperationSign(),
expression.getLExpression().getType(),
rExpression.getType(),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
@@ -503,7 +502,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
if (identifier == null) return;
PsiExpression initializer = variable.getInitializer();
if (initializer == null) return;
checkNestedGenericClasses(identifier, variable.getType(), initializer.getType(),
checkNestedGenericClasses(holder, identifier, variable.getType(), initializer.getType(),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
@@ -511,7 +510,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
public void visitReturnStatement(@NotNull PsiReturnStatement statement) {
PsiExpression returnValue = statement.getReturnValue();
if (returnValue == null) return;
checkNestedGenericClasses(returnValue,
checkNestedGenericClasses(holder, returnValue,
PsiTypesUtil.getMethodReturnType(statement), returnValue.getType(),
ConflictNestedTypeProblem.RETURN_NESTED_TYPE_PROBLEM);
}
@@ -520,8 +519,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
public void visitLambdaExpression(@NotNull PsiLambdaExpression lambda) {
super.visitLambdaExpression(lambda);
PsiElement body = lambda.getBody();
if (body instanceof PsiExpression) {
checkCollectionNullityOnAssignment(body, LambdaUtil.getFunctionalInterfaceReturnType(lambda), (PsiExpression)body);
if (body instanceof PsiExpression psiExpression) {
checkNestedGenericClasses(holder, body, LambdaUtil.getFunctionalInterfaceReturnType(lambda), psiExpression.getType(), ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
}
@@ -539,46 +538,42 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
PsiExpression argument = arguments[i];
if (i < parameters.length &&
(i < parameters.length - 1 || !MethodCallInstruction.isVarArgCall(method, substitutor, arguments, parameters))) {
checkCollectionNullityOnAssignment(argument, substitutor.substitute(parameters[i].getType()), argument);
PsiType expectedType = substitutor.substitute(parameters[i].getType());
checkNestedGenericClasses(holder, argument, expectedType, argument.getType(),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
}
}
private void checkNestedGenericClasses(@NotNull PsiElement errorElement,
@Nullable PsiType expectedType,
@Nullable PsiType actualType,
@NotNull ConflictNestedTypeProblem problem) {
if(expectedType == null || actualType == null) return;
JavaTypeNullabilityUtil.NullabilityConflictContext
context = JavaTypeNullabilityUtil.getNullabilityConflictInAssignment(expectedType, actualType,
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 ?
problem.notNullToNullProblem() : problem.nullToNotNullProblem();
reportProblem(holder, errorElement, LocalQuickFix.EMPTY_ARRAY,
messageKey, new Object[]{""},
messageKey, new Object[]{NullableStuffInspectionUtil.getNullabilityConflictPresentation(context)});
}
private void checkCollectionNullityOnAssignment(@NotNull PsiElement errorElement,
@Nullable PsiType expectedType,
@Nullable PsiExpression assignedExpression) {
if (assignedExpression == null) return;
checkCollectionNullityOnAssignment(errorElement, expectedType, assignedExpression.getType());
}
private void checkCollectionNullityOnAssignment(@NotNull PsiElement errorElement,
@Nullable PsiType expectedType,
@Nullable PsiType assignedType) {
if (isNullableNotNullCollectionConflict(expectedType, assignedType, file, new HashSet<>())) {
reportProblem(holder, errorElement, "assigning.a.collection.of.nullable.elements");
}
}
};
}
private boolean checkNestedGenericClasses(@NotNull ProblemsHolder holder,
@NotNull PsiElement errorElement,
@Nullable PsiType expectedType,
@Nullable PsiType actualType,
@NotNull ConflictNestedTypeProblem problem) {
if (expectedType == null || actualType == null) return false;
JavaTypeNullabilityUtil.NullabilityConflictContext
context = JavaTypeNullabilityUtil.getNullabilityConflictInAssignment(expectedType, actualType,
REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS);
JavaTypeNullabilityUtil.NullabilityConflict conflict = context.nullabilityConflict();
String messageKey;
switch (conflict) {
case UNKNOWN -> {
return false;
}
case NOT_NULL_TO_NULL -> messageKey = problem.notNullToNullProblem();
case NULL_TO_NOT_NULL -> messageKey = problem.nullToNotNullProblem();
case COMPLEX -> messageKey = problem.complexProblem();
default -> throw new IllegalStateException("Unexpected value: " + conflict);
}
reportProblem(holder, errorElement, LocalQuickFix.EMPTY_ARRAY,
messageKey, new Object[]{""},
messageKey, new Object[]{NullableStuffInspectionUtil.getNullabilityConflictPresentation(context)});
return true;
}
private void checkConflictingContainerAnnotations(@NotNull ProblemsHolder holder, @Nullable PsiModifierList list) {
if (list == null || !list.hasAnnotations()) return;
NullableNotNullManager manager = NullableNotNullManager.getInstance(holder.getProject());
@@ -621,34 +616,6 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
builder.register();
}
private static boolean isNullableNotNullCollectionConflict(@Nullable PsiType expectedType,
@Nullable PsiType assignedType,
@NotNull PsiFile place,
@NotNull Set<? super Couple<PsiType>> visited) {
if (!visited.add(Couple.of(expectedType, assignedType))) return false;
GlobalSearchScope scope = place.getResolveScope();
if (isNullityConflict(JavaGenericsUtil.getCollectionItemType(expectedType, scope),
JavaGenericsUtil.getCollectionItemType(assignedType, scope))) {
return true;
}
for (int i = 0; i <= 1; i++) {
PsiType expectedArg = PsiUtil.substituteTypeParameter(expectedType, CommonClassNames.JAVA_UTIL_MAP, i, false);
PsiType assignedArg = PsiUtil.substituteTypeParameter(assignedType, CommonClassNames.JAVA_UTIL_MAP, i, false);
if (isNullityConflict(expectedArg, assignedArg) ||
expectedArg != null && assignedArg != null && isNullableNotNullCollectionConflict(expectedArg, assignedArg, place, visited)) {
return true;
}
}
return false;
}
private static boolean isNullityConflict(PsiType expected, PsiType assigned) {
return DfaPsiUtil.getTypeNullability(expected) == Nullability.NOT_NULL && DfaPsiUtil.getTypeNullability(assigned) == Nullability.NULLABLE;
}
private @Nullable @InspectionMessage String checkIndirectInheritance(PsiElement psiClass, PsiClass intf) {
for (PsiMethod intfMethod : intf.getAllMethods()) {
PsiClass intfMethodClass = intfMethod.getContainingClass();
@@ -1003,8 +970,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
PsiTypeElement returnTypeElement = method.getReturnTypeElement();
if (returnTypeElement != null &&
isNullableNotNullCollectionConflict(superMethod.getReturnType(), method.getReturnType(), holder.getFile(), new HashSet<>())) {
reportProblem(holder, returnTypeElement, "nullable.stuff.error.overriding.notnull.with.nullable");
checkNestedGenericClasses(holder, returnTypeElement, superMethod.getReturnType(), method.getReturnType(),
ConflictNestedTypeProblem.OVERRIDING_NESTED_TYPE_PROBLEM)) {
break;
}
}
@@ -1107,8 +1074,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
PsiTypeElement typeElement = parameter.getTypeElement();
if (typeElement != null) {
for (PsiParameter superParameter : superParameters) {
if (isNullableNotNullCollectionConflict(parameter.getType(), superParameter.getType(), holder.getFile(), new HashSet<>())) {
reportProblem(holder, typeElement, "nullable.stuff.error.overriding.nullable.with.notnull");
if(checkNestedGenericClasses(holder, typeElement, parameter.getType(), superParameter.getType(),
ConflictNestedTypeProblem.OVERRIDING_NESTED_TYPE_PROBLEM)){
break;
}
}
@@ -1368,6 +1335,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
private enum ConflictNestedTypeProblem{
RETURN_NESTED_TYPE_PROBLEM("returning.a.class.with.notnull.arguments", "returning.a.class.with.nullable.arguments"),
ASSIGNMENT_NESTED_TYPE_PROBLEM("assigning.a.class.with.notnull.elements", "assigning.a.class.with.nullable.elements"),
OVERRIDING_NESTED_TYPE_PROBLEM("overriding.a.class.with.notnull.elements", "overriding.a.class.with.nullable.elements"),
;
@NotNull
@@ -1377,11 +1345,15 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
@NotNull
@PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
private final String nullToNotNullProblemMessage;
@NotNull
@PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
private final String complexProblem;
ConflictNestedTypeProblem(@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String notNullToNullProblemMessage,
@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String nullToNotNullProblemMessage) {
this.notNullToNullProblemMessage = notNullToNullProblemMessage;
this.nullToNotNullProblemMessage = nullToNotNullProblemMessage;
this.complexProblem = "complex.problem.with.nullability";
}
@NotNull
@@ -1395,5 +1367,11 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
String nullToNotNullProblem() {
return nullToNotNullProblemMessage;
}
@NotNull
@PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
String complexProblem() {
return complexProblem;
}
}
}

View File

@@ -211,7 +211,11 @@ public final class PsiCapturedWildcardType extends PsiType.Stub {
return bound;
}
else {
return isCapture() && capture ? PsiUtil.captureToplevelWildcards(myUpperBound, myContext) : myUpperBound;
PsiType type = isCapture() && capture ? PsiUtil.captureToplevelWildcards(myUpperBound, myContext) : myUpperBound;
if (bound != null) {
type = type.withNullability(type.getNullability().meet(bound.getNullability()));
}
return type;
}
}

View File

@@ -11,7 +11,10 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Helper methods to compute nullability of Java types.
@@ -117,18 +120,13 @@ public final class JavaTypeNullabilityUtil {
public static @NotNull NullabilityConflictContext getNullabilityConflictInAssignment(@Nullable PsiType leftType,
@Nullable PsiType rightType,
boolean checkNotNullToNull) {
return getNullabilityConflictInAssignment(leftType, rightType, checkNotNullToNull, false);
return getNullabilityConflictInAssignment(leftType, rightType, 0, checkNotNullToNull);
}
private static @NotNull NullabilityConflictContext getNullabilityConflictInAssignment(@Nullable PsiType leftType,
@Nullable PsiType rightType,
boolean checkNotNullToNull,
boolean checkConflictInInitialType) {
if (checkConflictInInitialType) {
NullabilityConflictContext context = getNullabilityConflictTypeContext(leftType, rightType);
if (isAllowedNullabilityConflictType(checkNotNullToNull, context)) return context;
}
@Nullable PsiType rightType,
int level,
boolean checkNotNullToNull) {
if (leftType == null || TypeConversionUtil.isNullType(leftType) ||
rightType == null || TypeConversionUtil.isNullType(rightType)
) {
@@ -136,34 +134,79 @@ public final class JavaTypeNullabilityUtil {
}
if (rightType instanceof PsiIntersectionType) {
return getNullabilityConflictInTypeArguments(leftType, rightType, checkNotNullToNull);
return getNullabilityConflictInTypeArguments(leftType, rightType, checkNotNullToNull, level);
}
if (rightType instanceof PsiCapturedWildcardType) {
return getNullabilityConflictInAssignment(leftType, ((PsiCapturedWildcardType)rightType).getUpperBound(true), checkNotNullToNull,
false);
if (level > 0) {
NullabilityConflictContext context = getNullabilityConflictTypeContext(leftType, rightType);
if (isAllowedNullabilityConflictType(level > 1 && checkNotNullToNull, context)) return context;
}
return getNullabilityConflictInAssignment(leftType, ((PsiCapturedWildcardType)rightType).getUpperBound(true), level,
checkNotNullToNull
);
}
if (leftType instanceof PsiCapturedWildcardType) {
return getNullabilityConflictInAssignment(((PsiCapturedWildcardType)leftType).getLowerBound(), rightType, checkNotNullToNull, false);
}
if (leftType instanceof PsiWildcardType) {
return getNullabilityConflictInAssignment(GenericsUtil.getWildcardBound(leftType), rightType, checkNotNullToNull, false);
PsiWildcardType leftWildcard = ((PsiCapturedWildcardType)leftType).getWildcard();
return getNullabilityConflictForLeftWildCard(leftWildcard, rightType, level, checkNotNullToNull);
}
if (rightType instanceof PsiWildcardType) {
return getNullabilityConflictInAssignment(leftType, GenericsUtil.getWildcardBound(rightType), checkNotNullToNull, false);
if (level > 0) {
NullabilityConflictContext context = getNullabilityConflictTypeContext(leftType, rightType);
if (isAllowedNullabilityConflictType(level > 1 && checkNotNullToNull, context)) return context;
}
return getNullabilityConflictInAssignment(leftType, GenericsUtil.getWildcardBound(rightType), level, checkNotNullToNull);
}
if (leftType instanceof PsiWildcardType) {
PsiWildcardType leftWildcard = (PsiWildcardType)leftType;
return getNullabilityConflictForLeftWildCard(leftWildcard, rightType, level, checkNotNullToNull);
}
if (leftType instanceof PsiArrayType && rightType instanceof PsiArrayType) {
return getNullabilityConflictInAssignment(((PsiArrayType)leftType).getComponentType(),
((PsiArrayType)rightType).getComponentType(), checkNotNullToNull, true);
PsiType leftComponent = ((PsiArrayType)leftType).getComponentType();
PsiType rightComponent = ((PsiArrayType)rightType).getComponentType();
NullabilityConflictContext context = getNullabilityConflictTypeContext(leftComponent, rightComponent);
if (isAllowedNullabilityConflictType(level != 0 && checkNotNullToNull, context)) return context;
return getNullabilityConflictInAssignment(leftComponent,
rightComponent, level, checkNotNullToNull);
}
if (!(leftType instanceof PsiClassType) || !(rightType instanceof PsiClassType)) {
return NullabilityConflictContext.UNKNOWN;
}
return getNullabilityConflictInTypeArguments(leftType, rightType, checkNotNullToNull);
return getNullabilityConflictInTypeArguments(leftType, rightType, checkNotNullToNull, level);
}
private static @NotNull NullabilityConflictContext getNullabilityConflictForLeftWildCard(@Nullable PsiWildcardType leftWildcard,
@Nullable PsiType rightType,
int level,
boolean checkNotNullToNull) {
if (leftWildcard == null || rightType == null) return NullabilityConflictContext.UNKNOWN;
PsiType leftBound = GenericsUtil.getWildcardBound(leftWildcard);
if (leftWildcard.isSuper()) {
if (rightType instanceof PsiWildcardType && ((PsiWildcardType)rightType).isSuper()) {
rightType = GenericsUtil.getWildcardBound(rightType);
}
if (level > 0) {
NullabilityConflictContext context = getNullabilityConflictTypeContext(rightType, leftBound);
if (isAllowedNullabilityConflictType(level > 1 && checkNotNullToNull, context)) {
context = new NullabilityConflictContext(NullabilityConflict.COMPLEX, leftBound, rightType);
return context;
}
}
NullabilityConflictContext context = getNullabilityConflictInAssignment(rightType, leftBound, level, checkNotNullToNull);
if (context.nullabilityConflict != NullabilityConflict.UNKNOWN) {
context = new NullabilityConflictContext(NullabilityConflict.COMPLEX, leftBound, rightType);
}
return context;
}
else {
if (level > 0) {
NullabilityConflictContext context = getNullabilityConflictTypeContext(leftBound, rightType);
if (isAllowedNullabilityConflictType(level > 1 && checkNotNullToNull, context)) return context;
}
return getNullabilityConflictInAssignment(leftBound, rightType, level, checkNotNullToNull);
}
}
/**
@@ -176,8 +219,9 @@ public final class JavaTypeNullabilityUtil {
* @return first inconsistency in nullability inside generic class type arguments.
*/
private static @NotNull NullabilityConflictContext getNullabilityConflictInTypeArguments(@NotNull PsiType leftType,
@NotNull PsiType rightType,
boolean checkNotNullToNull) {
@NotNull PsiType rightType,
boolean checkNotNullToNull,
int level) {
if (isRawType(leftType) || isRawType(rightType)) return NullabilityConflictContext.UNKNOWN;
PsiClass leftClass = PsiTypesUtil.getPsiClass(leftType);
if (leftClass == null) return NullabilityConflictContext.UNKNOWN;
@@ -193,11 +237,13 @@ public final class JavaTypeNullabilityUtil {
PsiType leftParameterType = leftParameterTypeList.get(i);
PsiType rightParameterType = rightParameterTypeList.get(i);
NullabilityConflictContext contextTheCurrentCheck = getNullabilityConflictTypeContext(leftParameterType, rightParameterType);
if (isAllowedNullabilityConflictType(checkNotNullToNull, contextTheCurrentCheck)) return contextTheCurrentCheck;
NullabilityConflictContext context = getNullabilityConflictInAssignment(
leftParameterType,
rightParameterType,
checkNotNullToNull,
true
level + 1, checkNotNullToNull
);
if (context.nullabilityConflict != NullabilityConflict.UNKNOWN) return context;
}
@@ -257,7 +303,6 @@ public final class JavaTypeNullabilityUtil {
private final @NotNull NullabilityConflict nullabilityConflict;
private final @Nullable PsiType expectedType;
private final @Nullable PsiType actualType;
public static final NullabilityConflictContext UNKNOWN = new NullabilityConflictContext(NullabilityConflict.UNKNOWN, null, null);
public NullabilityConflictContext(@NotNull NullabilityConflict nullabilityConflict, @Nullable PsiType expectedType, @Nullable PsiType actualType) {
@@ -341,6 +386,10 @@ public final class JavaTypeNullabilityUtil {
* Attempt to assign not-null to a null type, example: {@code Container<@Nullable> c <- Container<@NotNull T>}
*/
NOT_NULL_TO_NULL,
/**
* Incompatible types, usually, related to {@code ? super Something}
*/
COMPLEX,
/**
* There is no conflict or it is unknown
*/

View File

@@ -183,7 +183,8 @@ public final class PsiSubstitutorImpl implements PsiSubstitutor {
if (type.isExtends()) {
if (newBound.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
return PsiWildcardType.createUnbounded(type.getManager());
return PsiWildcardType.createUnbounded(type.getManager())
.withNullability(newBound.getNullability());
}
return PsiWildcardType.createExtends(type.getManager(), newBound);
}

View File

@@ -0,0 +1,91 @@
package org.example;
import org.jspecify.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
class DefaultNotNullTypeParameterOverrides {
static void callNullable(Lib<? extends @Nullable Object> l) {
}
static void callNonnull(Lib<? extends @NotNull Object> l) {
}
static void callSuperNullable(Lib<? super @Nullable Object> l) {
}
static void callSuperNonnull(Lib<? super @NotNull Object> l) {
}
static void simple(Lib<@Nullable Object> nullable,
Lib<@NotNull Object> notnull,
Lib<? extends @Nullable Object> extNullable,
Lib<? extends @NotNull Object> extNotNullable) {
callNullable(nullable);
callNullable(notnull);
callNullable(extNullable);
callNullable(extNotNullable);
callNonnull(<warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">nullable</warning>);
callNonnull(notnull);
callNonnull(<warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">extNullable</warning>);
callNonnull(extNotNullable);
callSuperNullable(nullable);
callSuperNullable(<warning descr="Incompatible type arguments due to nullability">notnull</warning>);
callSuperNonnull(nullable);
callSuperNonnull(notnull);
}
static void callLibNullable(Lib<Lib<? extends @Nullable Object>> l) {
}
static void callLibNonnull(Lib<Lib<? extends @NotNull Object>> l) {
}
static void nested(Lib<Lib<? extends @Nullable Object>> extNullable,
Lib<Lib<? extends @NotNull Object>> extNotNullable) {
callLibNullable(extNullable);
callLibNullable(<warning descr="Assigning a class with not-null type arguments when a class with nullable type arguments is expected">extNotNullable</warning>);
callLibNonnull(<warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">extNullable</warning>);
callLibNonnull(extNotNullable);
}
interface SuperInt<T extends @Nullable Object> {
@NullMarked
void nonNull(Lib<? extends Object> lib);
Lib<@Nullable T> nullReturn();
}
@NullMarked
interface ChildInt<T extends Object & Runnable> extends
SuperInt<T> {
default void testWildcard() {
nonNull(<warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">nullReturn()</warning>);
}
}
@NullMarked
interface CaptureTest {
<L> void nonNullExpected(Lib<L> l);
default void captured(Lib<? extends Object> l) {
nonNullExpected(l);
}
}
static class Lib<T extends @Nullable Object> {
}
}

View File

@@ -0,0 +1,91 @@
package org.example;
import org.jspecify.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
class DefaultNotNullTypeParameterOverrides {
static void callNullable(Lib<? extends @Nullable Object> l) {
}
static void callNonnull(Lib<? extends @NotNull Object> l) {
}
static void callSuperNullable(Lib<? super @Nullable Object> l) {
}
static void callSuperNonnull(Lib<? super @NotNull Object> l) {
}
static void simple(Lib<@Nullable Object> nullable,
Lib<@NotNull Object> notnull,
Lib<? extends @Nullable Object> extNullable,
Lib<? extends @NotNull Object> extNotNullable) {
callNullable(nullable);
callNullable(notnull);
callNullable(extNullable);
callNullable(extNotNullable);
callNonnull(<warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">nullable</warning>);
callNonnull(notnull);
callNonnull(<warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">extNullable</warning>);
callNonnull(extNotNullable);
callSuperNullable(nullable);
callSuperNullable(<warning descr="Incompatible type arguments due to nullability">notnull</warning>);
callSuperNonnull(nullable);
callSuperNonnull(notnull);
}
static void callLibNullable(Lib<Lib<? extends @Nullable Object>> l) {
}
static void callLibNonnull(Lib<Lib<? extends @NotNull Object>> l) {
}
static void nested(Lib<Lib<? extends @Nullable Object>> extNullable,
Lib<Lib<? extends @NotNull Object>> extNotNullable) {
callLibNullable(extNullable);
callLibNullable(extNotNullable);
callLibNonnull(<warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">extNullable</warning>);
callLibNonnull(extNotNullable);
}
interface SuperInt<T extends @Nullable Object> {
@NullMarked
void nonNull(Lib<? extends Object> lib);
Lib<@Nullable T> nullReturn();
}
@NullMarked
interface ChildInt<T extends Object & Runnable> extends
SuperInt<T> {
default void testWildcard() {
nonNull(<warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">nullReturn()</warning>);
}
}
@NullMarked
interface CaptureTest {
<L> void nonNullExpected(Lib<L> l);
default void captured(Lib<? extends Object> l) {
nonNullExpected(l);
}
}
static class Lib<T extends @Nullable Object> {
}
}

View File

@@ -14,17 +14,17 @@ abstract class Parent {
class Child extends Parent {
@Override
@Nullable <warning descr="Overriding a collection of non-null elements with a collection of nullable elements">String @NotNull []</warning> getStrings() {
@Nullable <warning descr="Overriding a class with nullable type arguments when a class with not-null type arguments is expected">String @NotNull []</warning> getStrings() {
throw new UnsupportedOperationException();
}
@Override
@NotNull <warning descr="Overriding a collection of non-null elements with a collection of nullable elements">List<@Nullable String></warning> getStringList() {
@NotNull <warning descr="Overriding a class with nullable type arguments when a class with not-null type arguments is expected">List<@Nullable String></warning> getStringList() {
throw new UnsupportedOperationException();
}
void foo(@NotNull <warning descr="Overriding a collection of nullable elements with a collection of non-null elements">String @NotNull []</warning> p1,
@NotNull <warning descr="Overriding a collection of nullable elements with a collection of non-null elements">List<@NotNull String></warning> p2) {
void foo(@NotNull <warning descr="Overriding a class with nullable type arguments when a class with not-null type arguments is expected">String @NotNull []</warning> p1,
@NotNull <warning descr="Overriding a class with nullable type arguments when a class with not-null type arguments is expected">List<@NotNull String></warning> p2) {
}
}

View File

@@ -6,7 +6,7 @@ class JC {
void testList() {
List<@Nullable String> nullableList = new ArrayList<>();
print(<warning descr="Assigning a collection of nullable elements into a collection of non-null elements">nullableList</warning>);
print(<warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">nullableList</warning>);
List<@NotNull String> <warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">list2</warning> = nullableList;
@@ -28,8 +28,8 @@ class JC {
List<@NotNull String> testReturnValue() {
List<@Nullable String> list = new ArrayList<>();
Supplier<List<@NotNull String>> supplier = () -> <warning descr="Assigning a collection of nullable elements into a collection of non-null elements">list</warning>;
Supplier<List<@NotNull String>> supplierRef = <warning descr="Assigning a collection of nullable elements into a collection of non-null elements">this::getNullableList</warning>;
Supplier<List<@NotNull String>> supplier = () -> <warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">list</warning>;
Supplier<List<@NotNull String>> supplierRef = <warning descr="Assigning a class with nullable type arguments when a class with not-null type arguments is expected">this::getNullableList</warning>;
Supplier<List<@NotNull String>> supplier3 = () -> { return <warning descr="Returning a class with nullable type arguments when a class with not-null type arguments is expected">list</warning>;};

View File

@@ -123,7 +123,13 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT
new Pair<>("AugmentedInferenceAgreesWithBaseInference.java", 33), // see: IDEA-377683
new Pair<>("NullnessUnspecifiedTypeParameter.java", 33), // see: IDEA-377683
new Pair<>("TypeVariableMinusNullVsTypeVariable.java", 28), // see: IDEA-377683
new Pair<>("TypeVariableMinusNullVsTypeVariable.java", 30) // see: IDEA-377683
new Pair<>("TypeVariableMinusNullVsTypeVariable.java", 30), // see: IDEA-377683
new Pair<>("ComplexParametric.java", 238), // see: IDEA-384752
new Pair<>("ComplexParametric.java", 243), // see: IDEA-384752
new Pair<>("ComplexParametric.java", 246), // see: IDEA-384752
new Pair<>("ComplexParametric.java", 261) // see: IDEA-384752
)
),
new SkipIndividuallyFilter( //cases to investigate later (with unspecified annotation and complicated to understand). (line number starts from 0)
@@ -184,8 +190,7 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT
new Pair<>("SuperVsObject.java", 24), // see: IDEA-379303
new Pair<>("SuperNullableForNonNullableTypeParameter.java", 27) // see: IDEA-379303
)
),
new CallWithParameterWithNestedGenericsFilter() // see: IDEA-377682
)
);
private static final LightProjectDescriptor PROJECT_DESCRIPTOR = new DefaultLightProjectDescriptor() {
@@ -580,8 +585,9 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT
"inspection.nullable.problems.at.local.variable" -> warnings.put(anchor, "jspecify_unrecognized_location");
case "inspection.nullable.problems.Nullable.method.overrides.NotNull",
"inspection.nullable.problems.NotNull.parameter.overrides.Nullable",
"assigning.a.collection.of.nullable.elements",
"assigning.a.collection.of.notnull.elements",
"complex.problem.with.nullability",
"assigning.a.class.with.nullable.elements",
"assigning.a.class.with.notnull.elements",
"returning.a.class.with.nullable.arguments",
"returning.a.class.with.notnull.arguments"
//, "non.null.type.argument.is.expected" //todo see IDEA-377707

View File

@@ -532,4 +532,20 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
public void testDefaultNotNullTypeParameterOverrides() {
doTest();
}
public void testCallIncompatibilitiesWithGeneric() {
myInspection.REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS = true;
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
doTest();
}
public void testCallIncompatibilitiesWithGenericWithNotNullToNull() {
myInspection.REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS = false;
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
doTest();
}
}