[java-inspections] Report incompatible instantiation

Fixes 'nice to have' parts of IDEA-372223 Nullability inference for generic parameters

GitOrigin-RevId: fe9490c352ee417b8a4146076632ef42790a4d25
This commit is contained in:
Tagir Valeev
2025-06-11 14:40:00 +02:00
committed by intellij-monorepo-bot
parent 4466a6556b
commit b56c24001e
5 changed files with 140 additions and 38 deletions

View File

@@ -240,6 +240,8 @@ inspection.nullable.problems.annotated.field.setter.parameter.not.annotated=Sett
inspection.nullable.problems.method.overrides.NotNull=Not annotated method overrides method annotated with @{0}
inspection.nullable.problems.parameter.overrides.NotNull=Not annotated parameter overrides @{0} parameter
inspection.nullable.problems.primitive.type.annotation=Primitive type members cannot be annotated
inspection.nullable.problems.nullable.instantiation.of.notnull=Not-null type parameter ''{0}'' cannot be instantiated with @{1} type
inspection.nullable.problems.nullable.instantiation.of.notnull.container=Not-null type parameter ''{0}'' cannot be instantiated under @{1}
inspection.nullable.problems.redundant.annotation.under.container=Redundant nullability annotation in the scope of @{0}
inspection.nullable.problems.receiver.annotation=Receiver parameter is inherently not-null
inspection.nullable.problems.applied.to.package=Annotation on fully-qualified name must be placed before the last component

View File

@@ -192,6 +192,43 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
}
}
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
super.visitMethodCallExpression(call);
PsiReferenceParameterList parameterList = call.getMethodExpression().getParameterList();
if (parameterList == null) return;
PsiType[] parameterization = parameterList.getTypeArguments();
if (parameterization.length == 0) return;
PsiMethod method = call.resolveMethod();
if (method == null) return;
PsiTypeParameter[] typeParameters = method.getTypeParameters();
if (typeParameters.length != parameterization.length) return;
for (int i = 0; i < typeParameters.length; i++) {
PsiTypeParameter typeParameter = typeParameters[i];
PsiType instance = parameterization[i];
TypeNullability nullability = TypeNullability.ofTypeParameter(typeParameter);
if (nullability.nullability() != Nullability.NOT_NULL) continue;
TypeNullability instanceNullability = instance.getNullability();
if (instanceNullability.nullability() == Nullability.NOT_NULL) continue;
NullabilitySource source = instanceNullability.source();
if (source instanceof NullabilitySource.ExplicitAnnotation explicit) {
PsiAnnotation anchor = explicit.annotation();
PsiJavaCodeReferenceElement ref = anchor.getNameReferenceElement();
if (ref != null) {
reportProblem(holder, anchor, "inspection.nullable.problems.nullable.instantiation.of.notnull",
typeParameter.getName(), ref.getReferenceName());
}
} else if (source instanceof NullabilitySource.ContainerAnnotation container) {
PsiElement anchor = parameterList.getTypeParameterElements()[i];
PsiJavaCodeReferenceElement ref = container.annotation().getNameReferenceElement();
if (ref != null) {
reportProblem(holder, anchor, "inspection.nullable.problems.nullable.instantiation.of.notnull.container",
typeParameter.getName(), ref.getReferenceName());
}
}
}
}
@Override
public void visitParameter(@NotNull PsiParameter parameter) {
check(parameter, holder, parameter.getType());
@@ -369,10 +406,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
PsiTypeElement typeArgument = typeArguments[i];
Project project = element.getProject();
PsiType type = typeArgument.getType();
String parameterName = typeParameters[i].getName();
if (parameterName == null) continue;
PsiType parameterType = JavaPsiFacade.getElementFactory(project).createTypeFromText(parameterName, typeParameters[i]);
if (DfaPsiUtil.getTypeNullability(parameterType) != Nullability.NOT_NULL) continue;
if (TypeNullability.ofTypeParameter(typeParameters[i]).nullability() != Nullability.NOT_NULL) continue;
Nullability typeNullability = DfaPsiUtil.getTypeNullability(type);
if (typeNullability != Nullability.NOT_NULL &&
!(typeNullability == Nullability.UNKNOWN && type instanceof PsiWildcardType wildcardType && !wildcardType.isExtends())) {

View File

@@ -1,6 +1,9 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeParameter;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -105,12 +108,25 @@ public final class TypeNullability {
}
return this.nullability() == Nullability.NULLABLE ? this : other;
}
public static @NotNull TypeNullability ofTypeParameter(@NotNull PsiTypeParameter parameter) {
NullableNotNullManager manager = NullableNotNullManager.getInstance(parameter.getProject());
if (manager != null) {
NullabilityAnnotationInfo typeUseNullability = manager.findDefaultTypeUseNullability(parameter);
if (typeUseNullability != null) {
return typeUseNullability.toTypeNullability();
}
}
return intersect(ContainerUtil.map(parameter.getSuperTypes(), PsiType::getNullability)).inherited();
}
/**
* @param collection type nullabilities to intersect
* @return the intersection of the type nullabilities in the collection
*/
public static @NotNull TypeNullability intersect(@NotNull Collection<@NotNull TypeNullability> collection) {
if (collection.isEmpty()) return UNKNOWN;
if (collection.size() == 1) return collection.iterator().next();
Map<Nullability, Set<NullabilitySource>> map = collection.stream().collect(Collectors.groupingBy(
TypeNullability::nullability, Collectors.mapping(TypeNullability::source, Collectors.toSet())));
Set<NullabilitySource> sources = map.get(Nullability.NOT_NULL);

View File

@@ -0,0 +1,39 @@
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.annotation.ElementType;
import java.util.function.Supplier;
@NullMarked
class Main {
public static void main(String[] args) {
Main.<<warning descr="Not-null type parameter 'T' cannot be instantiated with @Nullable type">@Nullable</warning> Object>fNonNullBound(() -> getNullableObject());
Main.<<warning descr="Not-null type parameter 'T' cannot be instantiated with @Nullable type">@Nullable</warning> Object>fNonNullBound(Main::getNullableObject);
}
static <T extends @Nullable Object> T fNullableBound(Supplier<T> supplier){
return supplier.get();
}
static <T> T fNonNullBound(Supplier<T> supplier){
return supplier.get();
}
static @Nullable Object getNullableObject() {
return null;
}
@NullableScope
static class NullableScopeClass {
void test() {
Main.<<warning descr="Not-null type parameter 'T' cannot be instantiated under @NullableScope">Object</warning>>fNonNullBound(Main::getNullableObject);
}
}
}
@javax.annotation.meta.TypeQualifierDefault(ElementType.TYPE_USE)
@javax.annotation.Nullable
@java.lang.annotation.Target(ElementType.TYPE_USE)
@interface NullableScope {}

View File

@@ -21,6 +21,10 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import static com.intellij.java.codeInspection.DataFlowInspectionTest.addJavaxNullabilityAnnotations;
import static com.intellij.java.codeInspection.DataFlowInspectionTestCase.addJSpecifyNullMarked;
import static com.intellij.java.codeInspection.DataFlowInspectionTestCase.setupTypeUseAnnotations;
public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTestCase {
private NullableStuffInspection myInspection = new NullableStuffInspection();
@@ -71,12 +75,12 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
public void testProblems() { doTest();}
public void testAnnotatingPrimitivesTypeUse() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testTypeParameterShouldNotWarn() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
@@ -113,7 +117,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testOverriddenMethodsWithDefaults() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
myInspection.REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true;
doTest();
@@ -142,13 +146,13 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
public void testNotNullByDefaultParameterOverridesNotAnnotated() {
myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true;
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
doTest();
}
public void testNullableCalledWithNullUnderNotNullByDefault() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
doTest();
}
@@ -161,7 +165,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testHonorSuperParameterDefault() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
myFixture.addFileToProject("foo/package-info.java", "@javax.annotation.ParametersAreNonnullByDefault package foo;");
@@ -172,7 +176,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testHonorThisParameterDefault() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
myFixture.addFileToProject("foo/package-info.java", "@javax.annotation.ParametersAreNonnullByDefault package foo;");
@@ -182,7 +186,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testHonorCustomDefault() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
myFixture.addClass("package foo;" +
"import static java.lang.annotation.ElementType.*;" +
"@javax.annotation.meta.TypeQualifierDefault({PARAMETER, FIELD, METHOD, LOCAL_VARIABLE}) " +
@@ -197,7 +201,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testOverrideCustomDefault() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
myFixture.addClass("package custom;" +
"public @interface CheckForNull {}");
@@ -231,7 +235,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testHonorParameterDefaultInSetters() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
myFixture.addFileToProject("foo/package-info.java", "@javax.annotation.ParametersAreNonnullByDefault package foo;");
@@ -241,7 +245,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testNullableDefaultOnClassVsNonnullOnPackage() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
myFixture.addFileToProject("foo/package-info.java", "@NonnullByDefault package foo;");
@@ -251,7 +255,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testDefaultOverridesExplicit() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
doTest();
}
@@ -264,54 +268,54 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testForeachParameterNullability() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testPassingNullableCollectionWhereNotNullIsExpected() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testPassingNullableMapWhereNotNullIsExpected() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testPassingNullableMapValueWhereNotNullIsExpected() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testOverridingNotNullCollectionWithNullable() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testNotNullCollectionItemWithNullableSuperType() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testNotNullTypeArgumentWithNullableSuperType() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testNullableTypeArgumentSOE() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testOverrideGenericMethod() {
myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true;
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testTypeUseNotNullOverriding() {
myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true;
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
@@ -336,23 +340,23 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
public void testTypeUseArrayAnnotation() {
myInspection.REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true;
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testIncorrectPlacement() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testInheritTypeUse() {
myInspection.REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true;
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testMismatchOnArrayElementTypeUse() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
@@ -379,7 +383,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testQuickFixOnTypeArgument() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
NullableNotNullManager manager = NullableNotNullManager.getInstance(getProject());
String oldDefault = manager.getDefaultNotNull();
try {
@@ -396,7 +400,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testQuickFixOnTypeArgumentNullable() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
NullableNotNullManager manager = NullableNotNullManager.getInstance(getProject());
String oldDefault = manager.getDefaultNotNull();
try {
@@ -414,19 +418,19 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
}
public void testMapComputeLambdaAnnotation() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("typeUse", myFixture);
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testDisableOnLocals() {
DataFlowInspectionTestCase.setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
public void testOverriddenWithNullMarked() {
myInspection.REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true;
DataFlowInspectionTestCase.addJSpecifyNullMarked(myFixture);
DataFlowInspectionTestCase.setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
@@ -459,11 +463,18 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
String nullable = nnnManager.getDefaultNullable();
nnnManager.setDefaultNullable("org.jspecify.annotations.Nullable");
Disposer.register(myFixture.getTestRootDisposable(), () -> nnnManager.setDefaultNullable(nullable));
DataFlowInspectionTestCase.addJSpecifyNullMarked(myFixture);
DataFlowInspectionTestCase.setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
IntentionAction action = myFixture.findSingleIntention("Fix all '@NotNull/@Nullable problems' problems in file");
myFixture.launchAction(action);
myFixture.checkResultByFile(getTestName(false) + "_after.java");
}
public void testIncompatibleInstantiation() {
addJSpecifyNullMarked(myFixture);
addJavaxNullabilityAnnotations(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
}