diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties index 473b05c675ee..ddc5f8021ca1 100644 --- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties +++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties @@ -132,7 +132,14 @@ 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 +assigning.a.collection.of.nullable.elements=\ + \ + Assigning a class with nullable type arguments when a class with not-null type arguments is expected{0}\ + +assigning.a.collection.of.notnull.elements=\ + \ + Assigning a class with not-null type arguments when a class with nullable type arguments is expected{0}\ + returning.a.class.with.nullable.arguments=\ \ Returning a class with nullable type arguments when a class with not-null type arguments is expected{0}\ diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java index b5b1a07da245..98e264c07e38 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java @@ -87,6 +87,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true; @SuppressWarnings("WeakerAccess") public boolean REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER = true; + @SuppressWarnings("WeakerAccess") public boolean REPORT_CONFLICT_IN_ASSIGNMENTS = true; + private static final Logger LOG = Logger.getInstance(NullableStuffInspectionBase.class); @Override @@ -102,6 +104,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection "REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER".equals(name) && "true".equals(value) || "REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS".equals(name) && "false".equals(value) || "REPORT_NOT_ANNOTATED_INSTANTIATION_NOT_NULL_TYPE".equals(name) && "false".equals(value) || + "REPORT_CONFLICT_IN_ASSIGNMENTS".equals(name) && "true".equals(value) || "REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER".equals(name) && "true".equals(value)) { node.removeContent(child); } @@ -210,6 +213,12 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection checkConstructorParameters(field, annotated, anno, annoToRemove, holder); } + PsiExpression initializer = field.getInitializer(); + PsiElement identifyingElement = field.getIdentifyingElement(); + if (REPORT_CONFLICT_IN_ASSIGNMENTS && initializer != null && identifyingElement != null) { + checkNestedGenericClasses(identifyingElement, field.getType(), initializer.getType(), + ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM); + } } @Override @@ -483,13 +492,30 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection @Override public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) { - checkCollectionNullityOnAssignment(expression.getOperationSign(), expression.getLExpression().getType(), expression.getRExpression()); + PsiExpression rExpression = expression.getRExpression(); + if (rExpression == null) return; + if (REPORT_CONFLICT_IN_ASSIGNMENTS) { + checkNestedGenericClasses(expression.getOperationSign(), + expression.getLExpression().getType(), + rExpression.getType(), + ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM); + } + else { + checkCollectionNullityOnAssignment(expression.getOperationSign(), expression.getLExpression().getType(), expression.getRExpression()); + } } @Override public void visitLocalVariable(@NotNull PsiLocalVariable variable) { PsiIdentifier identifier = variable.getNameIdentifier(); - if (identifier != null) { + if (identifier == null) return; + PsiExpression initializer = variable.getInitializer(); + if (initializer == null) return; + if (REPORT_CONFLICT_IN_ASSIGNMENTS) { + checkNestedGenericClasses(identifier, variable.getType(), initializer.getType(), + ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM); + } + else { checkCollectionNullityOnAssignment(identifier, variable.getType(), variable.getInitializer()); } } @@ -498,8 +524,9 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection public void visitReturnStatement(@NotNull PsiReturnStatement statement) { PsiExpression returnValue = statement.getReturnValue(); if (returnValue == null) return; - - checkGenericClassOnReturn(PsiTypesUtil.getMethodReturnType(statement), returnValue); + checkNestedGenericClasses(returnValue, + PsiTypesUtil.getMethodReturnType(statement), returnValue.getType(), + ConflictNestedTypeProblem.RETURN_NESTED_TYPE_PROBLEM); } @Override @@ -530,17 +557,19 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } } - private void checkGenericClassOnReturn(@Nullable PsiType expectedType, - @NotNull PsiExpression returnValue) { - PsiType returnType = returnValue.getType(); + 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, returnType, + 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 ? - "returning.a.class.with.notnull.arguments" : "returning.a.class.with.nullable.arguments"; + problem.notNullToNullProblem() : problem.nullToNotNullProblem(); - reportProblem(holder, returnValue, LocalQuickFix.EMPTY_ARRAY, + reportProblem(holder, errorElement, LocalQuickFix.EMPTY_ARRAY, messageKey, new Object[]{""}, messageKey, new Object[]{NullableStuffInspectionUtil.getNullabilityConflictPresentation(context)}); } @@ -1347,4 +1376,37 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection return JavaAnalysisBundle.message("inspection.annotate.overridden.method.nullable.quickfix.name"); } } + + + 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.collection.of.notnull.elements", "assigning.a.collection.of.nullable.elements"), + ; + + @NotNull + @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) + private final String notNullToNullProblemMessage; + + @NotNull + @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) + private final String nullToNotNullProblemMessage; + + ConflictNestedTypeProblem(@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String notNullToNullProblemMessage, + @NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String nullToNotNullProblemMessage) { + this.notNullToNullProblemMessage = notNullToNullProblemMessage; + this.nullToNotNullProblemMessage = nullToNotNullProblemMessage; + } + + @NotNull + @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) + String notNullToNullProblem() { + return notNullToNullProblemMessage; + } + + @NotNull + @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) + String nullToNotNullProblem() { + return nullToNotNullProblemMessage; + } + } } diff --git a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java index 095ce25e4018..1ec54c99aae7 100644 --- a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java @@ -48,6 +48,8 @@ public class NullableStuffInspection extends NullableStuffInspectionBase { checkbox("REPORT_NOT_ANNOTATED_GETTER", JavaBundle.message("inspection.nullable.problems.not.annotated.getters.for.annotated.fields")), checkbox("REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER", JavaBundle.message("inspection.nullable.problems.notnull.parameters.with.null.literal.option")), checkbox("REPORT_NULLABILITY_ANNOTATION_ON_LOCALS", JavaBundle.message("inspection.nullable.problems.nullability.on.locals")), + checkbox("REPORT_CONFLICT_IN_ASSIGNMENTS", JavaBundle.message( + "inspection.assignment.conflicts")), checkbox("REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS", JavaBundle.message( "inspection.nullable.problems.notnull.to.nullable.assignment.conflicts")), checkbox("REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER", JavaBundle.message( diff --git a/java/java-tests/testData/inspection/nullableProblems/AssignmentIncompatibilitiesWithGeneric.java b/java/java-tests/testData/inspection/nullableProblems/AssignmentIncompatibilitiesWithGeneric.java new file mode 100644 index 000000000000..aa9717da4e34 --- /dev/null +++ b/java/java-tests/testData/inspection/nullableProblems/AssignmentIncompatibilitiesWithGeneric.java @@ -0,0 +1,54 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +class TestListWrapperAssignment { + + + ListArrayWrapper<@Nullable String> listByArrayWithNullField = ListArrayWrapper.createArray(new @Nullable String[] {"a", null, "c"}); + + ListArrayWrapper listWithNullField = ListArrayWrapper.createVarargs("a", null, "c"); + + public static void main(String[] args) { + ListArrayWrapper.createVarargs("a", "c").getList().forEach(s -> System.out.println(s.toUpperCase(Locale.ROOT))); + + ListArrayWrapper listArrayWrapper = ListArrayWrapper.createVarargs("a", "c"); + listArrayWrapper.getList().forEach(s -> System.out.println(s.toUpperCase(Locale.ROOT))); + + ListArrayWrapper<@Nullable String> listArrayWrapperNullable = ListArrayWrapper.createVarargs("a", "c"); + listArrayWrapperNullable.getList().forEach(s -> System.out.println(s.toUpperCase(Locale.ROOT))); + + ListArrayWrapper listWithNull = ListArrayWrapper.createVarargs("a", null, "c"); + + ListArrayWrapper<@Nullable String> listByArrayWithNull = ListArrayWrapper.createArray(new @Nullable String[] {"a", null, "c"}); + ListArrayWrapper varargs = ListArrayWrapper.createVarargs("1"); + ListArrayWrapper<@Nullable String> listByArrayWithoutNull = varargs; + ListArrayWrapper listByArrayWithNullCorrectInspection1 = ListArrayWrapper.createArray(new @Nullable String[] {"a", null, "c"}); + ListArrayWrapper<@Nullable String> listByArrayWithNullCorrectInspection2 = ListArrayWrapper.createArray(new String[] {"a", null, "c"}); + } + + static class ListArrayWrapper { + private final List list; + + @SafeVarargs + static ListArrayWrapper createVarargs(E... elements) { + return new ListArrayWrapper<>(new ArrayList<>(Arrays.asList(elements))); + } + + static ListArrayWrapper createArray(E[] array) { + return new ListArrayWrapper<>(new ArrayList<>(Arrays.asList(array))); + } + + private ListArrayWrapper(List list) { + this.list = list; + } + + List getList() { + return list; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors.java b/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors.java index 3f3145b68006..ab19ea2e791a 100644 --- a/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors.java +++ b/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors.java @@ -28,6 +28,6 @@ class Main { AtomicReferenceArray arr = new AtomicReferenceArray<>(10); AtomicReferenceArray arr2 = new AtomicReferenceArray<>(new String[10]); // technically wrong and should be highlighted, as array contains nulls @Nullable String[] data = new String[10]; - AtomicReferenceArray arr3 = new AtomicReferenceArray<>(data); // technically wrong and should be highlighted, as array contains nulls + AtomicReferenceArray arr3 = new AtomicReferenceArray<>(data); } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors_after.java b/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors_after.java index 7302949099c3..fd048a9211a5 100644 --- a/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors_after.java +++ b/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors_after.java @@ -28,6 +28,6 @@ class Main { AtomicReferenceArray<@Nullable String> arr = new AtomicReferenceArray<>(10); AtomicReferenceArray arr2 = new AtomicReferenceArray<>(new String[10]); // technically wrong and should be highlighted, as array contains nulls @Nullable String[] data = new String[10]; - AtomicReferenceArray arr3 = new AtomicReferenceArray<>(data); // technically wrong and should be highlighted, as array contains nulls + AtomicReferenceArray arr3 = new AtomicReferenceArray<>(data); } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/nullableProblems/PassingNullableCollectionWhereNotNullIsExpected.java b/java/java-tests/testData/inspection/nullableProblems/PassingNullableCollectionWhereNotNullIsExpected.java index d3129e9964bd..6de55c97050a 100644 --- a/java/java-tests/testData/inspection/nullableProblems/PassingNullableCollectionWhereNotNullIsExpected.java +++ b/java/java-tests/testData/inspection/nullableProblems/PassingNullableCollectionWhereNotNullIsExpected.java @@ -6,15 +6,15 @@ class JC { void testList() { List<@Nullable String> nullableList = new ArrayList<>(); - print(nullableList); + print(nullableList); - List<@NotNull String> list2 = nullableList; + List<@NotNull String> list2 = nullableList; List<@NotNull String> list3; - list2 = nullableList; + list2 = nullableList; List list4 = nullableList; - List list5 = nullableList; + List list5 = nullableList; } private static void print(List<@NotNull String> list) { @@ -28,8 +28,8 @@ class JC { List<@NotNull String> testReturnValue() { List<@Nullable String> list = new ArrayList<>(); - Supplier> supplier = () -> list; - Supplier> supplierRef = this::getNullableList; + Supplier> supplier = () -> list; + Supplier> supplierRef = this::getNullableList; Supplier> supplier3 = () -> { return list;}; @@ -41,6 +41,6 @@ class JC { class Test { public void test() { List nullableList = new ArrayList<>(); - List list2 = nullableList; + List list2 = nullableList; } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/nullableProblems/PassingNullableMapWhereNotNullIsExpected.java b/java/java-tests/testData/inspection/nullableProblems/PassingNullableMapWhereNotNullIsExpected.java index 71166267356b..c2789219bc48 100644 --- a/java/java-tests/testData/inspection/nullableProblems/PassingNullableMapWhereNotNullIsExpected.java +++ b/java/java-tests/testData/inspection/nullableProblems/PassingNullableMapWhereNotNullIsExpected.java @@ -4,11 +4,11 @@ import java.util.*; class JC { void testMap() { - Map<@NotNull String, @NotNull String> m1 = new HashMap<@Nullable String, String>(); - m1 = new HashMap(); + Map<@NotNull String, @NotNull String> m1 = new HashMap<@Nullable String, String>(); + m1 = new HashMap(); m1 = new HashMap(); - Map<@NotNull String, ? extends @NotNull String> m2 = new HashMap<@Nullable String, String>(); + Map<@NotNull String, ? extends @NotNull String> m2 = new HashMap<@Nullable String, String>(); } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/JSpecifyFilteredAnnotationTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/JSpecifyFilteredAnnotationTest.java index 411cb324e6cf..3541efad25c5 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/JSpecifyFilteredAnnotationTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/JSpecifyFilteredAnnotationTest.java @@ -113,11 +113,17 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT new Pair<>("MultiBoundTypeVariableUnionNullToSelf.java", 62), // see: IDEA-377697 - new Pair<>("WildcardCapturesToBoundOfTypeParameterNotToTypeVariableItself.java", 24), // see: IDEA-377699 + new Pair<>("WildcardCapturesToBoundOfTypeParameterNotToTypeVariableItself.java", 24) ,// see: IDEA-377699 new Pair<>("SelfType.java", 34), // see: IDEA-377707 (also see the commented case in warning matchers) new Pair<>("SelfType.java", 43), // see: IDEA-377707 (also see the commented case in warning matchers) - new Pair<>("OutOfBoundsTypeVariable.java", 21) // see: IDEA-377707 (also see the commented case in warning matchers) + new Pair<>("OutOfBoundsTypeVariable.java", 21), // see: IDEA-377707 (also see the commented case in warning matchers) + new Pair<>("TypeParameterBounds.java", 40), // see: IDEA-377707 + + 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 SkipIndividuallyFilter( //cases to investigate later (with unspecified annotation and complicated to understand). (line number starts from 0) @@ -179,8 +185,7 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT new Pair<>("SuperNullableForNonNullableTypeParameter.java", 27) // see: IDEA-379303 ) ), - new CallWithParameterWithNestedGenericsFilter(), // see: IDEA-377682 - new VariableWithNestedGenericsFilter() // see: IDEA-377683 + new CallWithParameterWithNestedGenericsFilter() // see: IDEA-377682 ); private static final LightProjectDescriptor PROJECT_DESCRIPTOR = new DefaultLightProjectDescriptor() { @@ -576,6 +581,7 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT 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", "returning.a.class.with.nullable.arguments", "returning.a.class.with.notnull.arguments" //, "non.null.type.argument.is.expected" //todo see IDEA-377707 diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java index 563efccecc5a..ff9e819657d2 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java @@ -516,6 +516,13 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest doTest(); } + public void testAssignmentIncompatibilitiesWithGeneric() { + myInspection.REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS = true; + addJSpecifyNullMarked(myFixture); + setupTypeUseAnnotations("org.jspecify.annotations", myFixture); + doTest(); + } + public void testArrayUnderContainerAnnotation() { myInspection.REPORT_NOT_ANNOTATED_INSTANTIATION_NOT_NULL_TYPE = true; addJSpecifyNullMarked(myFixture); diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index 56b134be6e79..74fac482fa04 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -564,6 +564,7 @@ inspection.nullable.problems.not.annotated.getters.for.annotated.fields=Report n inspection.nullable.problems.notnull.overrides.option=Report @NotNull ¶meters overriding non-annotated inspection.nullable.problems.notnull.parameters.with.null.literal.option=Report @NotNull parameters with null-literal argument usages inspection.nullable.problems.nullability.on.locals=Report nullability annotations on local variables when they are non-applicable +inspection.assignment.conflicts=Report nullability conflicts for nested types in assignment and variable declarations inspection.nullable.problems.notnull.to.nullable.assignment.conflicts=Report assignment of a not-null type argument to a nullable type argument inspection.nullable.problems.redundant.nullability.inside.container=Report redundant nullability annotation in the scope of annotated container inspection.optional.get.without.is.present.message={0}.#ref() without ''isPresent()'' check