mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
[java-inspections] IDEA-377682 JSpecify. Nullable incompatibilities. Calls with generics
GitOrigin-RevId: 5b501cf6f6e99a675ad2e78283000a0be86fb3ff
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0c57575a17
commit
76d276b20d
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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>;};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user