Files
openide/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java
T
2026-02-12 20:01:14 +00:00

1461 lines
79 KiB
Java

// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.nullable;
import com.intellij.codeInsight.AnnotationTargetUtil;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInsight.NullabilityAnnotationInfo;
import com.intellij.codeInsight.NullabilitySource;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.TypeNullability;
import com.intellij.codeInsight.daemon.impl.quickfix.MoveAnnotationOnStaticMemberQualifyingTypeFix;
import com.intellij.codeInsight.intention.AddAnnotationModCommandAction;
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
import com.intellij.codeInsight.intention.AddTypeAnnotationFix;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.InspectionsBundle;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.RemoveAnnotationQuickFix;
import com.intellij.codeInspection.UpdateInspectionOptionFix;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.codeInspection.dataFlow.java.inst.MethodCallInstruction;
import com.intellij.codeInspection.util.InspectionMessage;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.openapi.command.undo.UndoUtil;
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.WriteExternalException;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.JavaResolveResult;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.LambdaUtil;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiAnnotationOwner;
import com.intellij.psi.PsiArrayType;
import com.intellij.psi.PsiAssignmentExpression;
import com.intellij.psi.PsiCallExpression;
import com.intellij.psi.PsiCatchSection;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassObjectAccessExpression;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiDiamondType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiEnumConstant;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiExpressionList;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiJavaModule;
import com.intellij.psi.PsiLambdaExpression;
import com.intellij.psi.PsiLocalVariable;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiMethodReferenceExpression;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiNewExpression;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.PsiPackageStatement;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiReceiverParameter;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiReferenceList;
import com.intellij.psi.PsiReferenceParameterList;
import com.intellij.psi.PsiReturnStatement;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.PsiTypeParameter;
import com.intellij.psi.PsiTypes;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.PsiWildcardType;
import com.intellij.psi.SyntheticElement;
import com.intellij.psi.TypeAnnotationProvider;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.search.JavaNullMethodArgumentUtil;
import com.intellij.psi.impl.search.JavaOverridingMethodsSearcher;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PropertyUtilBase;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.JavaTypeNullabilityUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.TypeUtils;
import one.util.streamex.StreamEx;
import org.jdom.Element;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import static com.intellij.codeInsight.AnnotationUtil.CHECK_EXTERNAL;
import static com.intellij.codeInsight.AnnotationUtil.CHECK_HIERARCHY;
import static com.intellij.codeInsight.AnnotationUtil.CHECK_TYPE;
import static com.intellij.codeInsight.AnnotationUtil.NOT_NULL;
import static com.intellij.codeInsight.AnnotationUtil.findAnnotation;
import static com.intellij.codeInsight.AnnotationUtil.getRelatedType;
import static com.intellij.codeInsight.AnnotationUtil.getSuperAnnotationOwners;
import static com.intellij.codeInsight.AnnotationUtil.isAnnotated;
import static com.intellij.codeInsight.AnnotationUtil.isAnnotatingApplicable;
import static com.intellij.codeInspection.nullable.AnnotateAsNullMarkedFix.createAnnotateAsNullMarkedFix;
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
import static com.intellij.patterns.PsiJavaPatterns.psiMethod;
import static com.intellij.util.ObjectUtils.tryCast;
import static java.util.Objects.requireNonNull;
public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspectionTool {
/**
* @deprecated the field remains to minimize changes to users' inspection profiles.
*/
@Deprecated @SuppressWarnings("WeakerAccess") public boolean REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL = true;
@SuppressWarnings("WeakerAccess") public boolean REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL = true;
@SuppressWarnings("WeakerAccess") public boolean REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE = true;
/**
* @deprecated the field remains to minimize changes to users' inspection profiles.
*/
@Deprecated @SuppressWarnings("WeakerAccess") public boolean REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL = true;
@SuppressWarnings("WeakerAccess") public boolean REPORT_NOT_ANNOTATED_GETTER = true;
@SuppressWarnings("WeakerAccess") public boolean IGNORE_EXTERNAL_SUPER_NOTNULL;
@SuppressWarnings("WeakerAccess") public boolean REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED;
@SuppressWarnings("WeakerAccess") public boolean REPORT_NULLABILITY_ANNOTATION_ON_LOCALS = true;
@SuppressWarnings("WeakerAccess") public boolean REPORT_NOT_ANNOTATED_INSTANTIATION_NOT_NULL_TYPE = false;
@SuppressWarnings("WeakerAccess") public boolean REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS = false;
/**
* @deprecated the field remains to minimize changes to users' inspection profiles.
*/
@Deprecated @SuppressWarnings("WeakerAccess") public boolean REPORT_NOT_ANNOTATED_SETTER_PARAMETER = true;
/**
* @deprecated field remains for test
*/
@Deprecated @SuppressWarnings("WeakerAccess") public boolean REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true;
/**
* @deprecated the field remains to minimize changes to users' inspection profiles.
*/
@Deprecated @SuppressWarnings("WeakerAccess") public boolean REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD = true;
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;
private static final Logger LOG = Logger.getInstance(NullableStuffInspectionBase.class);
@Override
public void writeSettings(@NotNull Element node) throws WriteExternalException {
super.writeSettings(node);
for (Element child : new ArrayList<>(node.getChildren())) {
String name = child.getAttributeValue("name");
String value = child.getAttributeValue("value");
if ("IGNORE_EXTERNAL_SUPER_NOTNULL".equals(name) && "false".equals(value) ||
"REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED".equals(name) && "false".equals(value) ||
"REQUIRE_NOTNULL_FIELDS_INITIALIZED".equals(name) && "true".equals(value) ||
"REPORT_NULLABILITY_ANNOTATION_ON_LOCALS".equals(name) && "true".equals(value) ||
"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_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER".equals(name) && "true".equals(value)) {
node.removeContent(child);
}
}
}
@Override
public @NotNull PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, boolean isOnTheFly) {
final PsiFile file = holder.getFile();
if (!PsiUtil.isAvailable(JavaFeature.ANNOTATIONS, file) || nullabilityAnnotationsNotAvailable(file)) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
private final NullableNotNullManager manager = NullableNotNullManager.getInstance(holder.getProject());
private final List<String> nullables = manager.getNullables();
private final List<String> notNulls = manager.getNotNulls();
@Override
public void visitMethod(@NotNull PsiMethod method) {
checkNullableStuffForMethod(method, holder);
}
@Override
public void visitClass(@NotNull PsiClass aClass) {
if (aClass.isRecord()) {
PsiMethod constructor = JavaPsiRecordUtil.findCanonicalConstructor(aClass);
if (constructor instanceof SyntheticElement) {
checkParameters(constructor, holder, List.of(), manager);
}
}
checkConflictingContainerAnnotations(holder, aClass.getModifierList());
}
@Override
public void visitPackageStatement(@NotNull PsiPackageStatement statement) {
checkConflictingContainerAnnotations(holder, statement.getAnnotationList());
}
@Override
public void visitModule(@NotNull PsiJavaModule module) {
checkConflictingContainerAnnotations(holder, module.getModifierList());
}
@Override
public void visitNewExpression(@NotNull PsiNewExpression expression) {
if (expression.isArrayCreation()) return;
super.visitNewExpression(expression);
PsiJavaCodeReferenceElement ref = expression.getClassOrAnonymousClassReference();
if (ref == null) return;
if (!(ref.resolve() instanceof PsiClass cls)) return;
String qualifiedName = cls.getQualifiedName();
if (qualifiedName == null) return;
PsiExpressionList list = expression.getArgumentList();
if (list == null) return;
if (!(expression.getType() instanceof PsiClassType type) || type.getParameterCount() != 1) return;
PsiType typeParameter = type.getParameters()[0];
if (!(typeParameter instanceof PsiClassType classType) ||
DfaPsiUtil.getTypeNullability(typeParameter) != Nullability.NOT_NULL) return;
boolean matched = switch (qualifiedName) {
case "java.util.concurrent.atomic.AtomicReference" -> list.getExpressionCount() == 0;
case "java.util.concurrent.atomic.AtomicReferenceArray" ->
list.getExpressionCount() == 1 && PsiTypes.intType().equals(list.getExpressionTypes()[0]);
case "java.lang.ThreadLocal" -> list.getExpressionCount() == 0 && expression.getAnonymousClass() == null;
default -> false;
};
if (!matched) return;
AddTypeAnnotationFix fix = null;
if (classType.getPsiContext() instanceof PsiJavaCodeReferenceElement typeRef &&
typeRef.getParent() instanceof PsiTypeElement typeElement && typeElement.getType().equals(classType) &&
typeElement.acceptsAnnotations()) {
fix = new AddTypeAnnotationFix(typeElement, manager.getDefaultAnnotation(Nullability.NULLABLE, expression), notNulls);
}
holder.problem(expression,
JavaAnalysisBundle.message("inspection.nullable.problems.constructor.not.compatible.non.null.type.argument"))
.maybeFix(fix)
.register();
}
@Override
public void visitMethodReferenceExpression(@NotNull PsiMethodReferenceExpression expression) {
checkMethodReference(expression, holder);
JavaResolveResult result = expression.advancedResolve(false);
PsiElement target = result.getElement();
if (target instanceof PsiMethod) {
checkNestedGenericClasses(holder, expression,
LambdaUtil.getFunctionalInterfaceReturnType(expression),
result.getSubstitutor().substitute(((PsiMethod)target).getReturnType()),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
}
@Override
public void visitField(@NotNull PsiField field) {
final PsiType type = field.getType();
final Annotated annotated = check(field, holder, type);
if (TypeConversionUtil.isPrimitiveAndNotNull(type)) {
return;
}
Project project = holder.getProject();
if (annotated.isDeclaredNotNull ^ annotated.isDeclaredNullable) {
final String anno =
manager.getDefaultAnnotation(annotated.isDeclaredNotNull ? Nullability.NOT_NULL : Nullability.NULLABLE, field);
final List<String> annoToRemove = annotated.isDeclaredNotNull ? nullables : notNulls;
checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder);
checkConstructorParameters(field, annotated, anno, annoToRemove, holder);
}
PsiExpression initializer = field.getInitializer();
PsiElement identifyingElement = field.getIdentifyingElement();
if (initializer != null && identifyingElement != null) {
checkNestedGenericClasses(holder, identifyingElement, field.getType(), initializer.getType(),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
}
@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());
}
@Override
public void visitAnnotation(@NotNull PsiAnnotation annotation) {
NullabilityAnnotationWrapper wrapper = NullabilityAnnotationWrapper.from(annotation);
if (wrapper == null) return;
PsiType targetType = wrapper.targetType();
PsiType type = wrapper.type();
PsiModifierListOwner listOwner = wrapper.listOwner();
checkRedundantInContainerScope(wrapper);
if (type != null &&
wrapper.nullability() == Nullability.NOT_NULL &&
PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter) {
PsiType notAnnotated = type.annotate(TypeAnnotationProvider.EMPTY);
TypeNullability notAnnotatedNullability = notAnnotated.getNullability();
if (notAnnotatedNullability.nullability() == Nullability.NOT_NULL &&
notAnnotatedNullability.source() instanceof NullabilitySource.ExtendsBound) {
reportProblem(holder, annotation,
new RemoveAnnotationQuickFix(annotation, null),
"inspection.nullable.problems.redundant.annotation.inherited.notnull");
}
}
if (type instanceof PsiPrimitiveType) {
LocalQuickFix additionalFix = null;
if (targetType instanceof PsiArrayType && !targetType.hasAnnotations()) {
additionalFix = new MoveAnnotationToArrayFix();
}
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.primitive.type.annotation", LocalQuickFix.notNullElements(additionalFix));
}
if (type instanceof PsiClassType classType) {
PsiElement context = classType.getPsiContext();
// outer type/package
if (context instanceof PsiJavaCodeReferenceElement outerCtx) {
PsiElement parent = context.getParent();
if (parent instanceof PsiJavaCodeReferenceElement) {
if (outerCtx.resolve() instanceof PsiPackage) {
ModCommandAction action = new MoveAnnotationOnStaticMemberQualifyingTypeFix(annotation);
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.applied.to.package",
LocalQuickFix.from(action));
}
else {
// If outer is qualifier of static member then don't report problem as it is already reported
// as ANNOTATION_NOT_ALLOWED_STATIC which contains exactly the same fix "Move annotation".
if (!PsiImplUtil.isTypeQualifierOfStaticMember(outerCtx)) {
ModCommandAction action = new MoveAnnotationOnStaticMemberQualifyingTypeFix(annotation);
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.outer.type",
LocalQuickFix.from(action));
}
}
}
if (parent instanceof PsiReferenceList) {
PsiElement firstChild = parent.getFirstChild();
if ((PsiUtil.isJavaToken(firstChild, JavaTokenType.EXTENDS_KEYWORD) ||
PsiUtil.isJavaToken(firstChild, JavaTokenType.IMPLEMENTS_KEYWORD)) &&
!(parent.getParent() instanceof PsiTypeParameter)) {
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.reference.list");
}
if (PsiUtil.isJavaToken(firstChild, JavaTokenType.THROWS_KEYWORD)) {
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.throws");
}
}
}
}
if (type instanceof PsiArrayType && annotation.getParent() instanceof PsiTypeElement parent &&
parent.getType().equals(type) && !manager.canAnnotateLocals(wrapper.qualifiedName())) {
checkIllegalLocalAnnotation(annotation, parent.getParent());
}
if (listOwner instanceof PsiMethod method && method.isConstructor()) {
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.constructor");
}
if (listOwner instanceof PsiClass && AnnotationTargetUtil.findAnnotationTarget(annotation, PsiAnnotation.TargetType.TYPE) == null) {
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.class");
}
if (listOwner instanceof PsiEnumConstant) {
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.enum.constant");
}
if (!manager.canAnnotateLocals(wrapper.qualifiedName()) && !(targetType instanceof PsiArrayType)) {
checkIllegalLocalAnnotation(annotation, listOwner);
}
if (type instanceof PsiWildcardType && manager.isTypeUseAnnotationLocationRestricted(wrapper.qualifiedName())) {
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.wildcard");
}
if (wrapper.owner() instanceof PsiTypeParameter && manager.isTypeUseAnnotationLocationRestricted(wrapper.qualifiedName())) {
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.type.parameter");
}
if (listOwner instanceof PsiReceiverParameter && wrapper.nullability() != Nullability.NOT_NULL) {
reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.receiver.annotation");
}
checkOppositeAnnotationConflict(annotation, wrapper.nullability());
if (NOT_NULL.equals(wrapper.qualifiedName())) {
PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue("exception");
if (value instanceof PsiClassObjectAccessExpression classObjectAccessExpression) {
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(classObjectAccessExpression.getOperand().getType());
if (psiClass != null && !hasStringConstructor(psiClass)) {
reportProblem(holder, value, "custom.exception.class.should.have.a.constructor");
}
}
}
}
private void checkRedundantInContainerScope(NullabilityAnnotationWrapper wrapper) {
if (REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER) {
NullabilityAnnotationInfo containerInfo = wrapper.findContainerInfoForRedundantAnnotation();
if (containerInfo != null) {
reportRedundantInContainerScope(wrapper.annotation(), containerInfo);
}
}
}
private void reportRedundantInContainerScope(@NotNull PsiAnnotation annotation, @NotNull NullabilityAnnotationInfo containerInfo) {
PsiJavaCodeReferenceElement containerName = containerInfo.getAnnotation().getNameReferenceElement();
if (containerName != null) {
LocalQuickFix updateOptionFix = LocalQuickFix.from(
new UpdateInspectionOptionFix(NullableStuffInspectionBase.this,
"REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER",
JavaAnalysisBundle.message("inspection.nullable.problems.turn.off.redundant.annotation.under.container"),
false));
reportProblem(holder, annotation,
LocalQuickFix.notNullElements(new RemoveAnnotationQuickFix(annotation, null), updateOptionFix),
"inspection.nullable.problems.redundant.annotation.under.container", containerName.getReferenceName());
}
}
private void checkIllegalLocalAnnotation(@NotNull PsiAnnotation annotation, @Nullable PsiElement owner) {
if (!REPORT_NULLABILITY_ANNOTATION_ON_LOCALS) return;
if (owner instanceof PsiLocalVariable ||
owner instanceof PsiParameter parameter &&
parameter.getDeclarationScope() instanceof PsiCatchSection) {
reportIncorrectLocation(holder, annotation, (PsiVariable)owner, "inspection.nullable.problems.at.local.variable");
}
}
private void checkOppositeAnnotationConflict(PsiAnnotation annotation, Nullability nullability) {
PsiAnnotationOwner owner = annotation.getOwner();
if (owner == null) return;
PsiModifierListOwner listOwner = owner instanceof PsiModifierList modifierList
? tryCast(modifierList.getParent(), PsiModifierListOwner.class)
: null;
Condition<PsiAnnotation> filter = anno ->
anno != annotation && manager.getAnnotationNullability(anno.getQualifiedName()).filter(n -> n != nullability).isPresent();
PsiAnnotation oppositeAnno = ContainerUtil.find(owner.getAnnotations(), filter);
if (oppositeAnno == null && listOwner != null) {
NullabilityAnnotationInfo result = manager.findNullabilityAnnotationInfo(
listOwner, ContainerUtil.filter(Nullability.values(), n -> n != nullability));
oppositeAnno = result == null || result.isContainer() ? null : result.getAnnotation();
}
if (oppositeAnno != null &&
Objects.equals(getRelatedType(annotation), getRelatedType(oppositeAnno))) {
reportProblem(holder, annotation, new RemoveAnnotationQuickFix(annotation, listOwner),
"inspection.nullable.problems.Nullable.NotNull.conflict",
getPresentableAnnoName(annotation), getPresentableAnnoName(oppositeAnno));
}
}
private static boolean hasStringConstructor(PsiClass aClass) {
for (PsiMethod method : aClass.getConstructors()) {
PsiParameterList list = method.getParameterList();
if (list.getParametersCount() == 1 &&
requireNonNull(list.getParameter(0)).getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
return true;
}
}
return false;
}
@Override
public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement reference) {
super.visitReferenceElement(reference);
checkNullableNotNullInstantiationConflict(reference);
PsiElement list = reference.getParent();
PsiElement psiClass = list instanceof PsiReferenceList ? list.getParent() : null;
PsiElement intf = reference.resolve();
if (psiClass instanceof PsiClass && list == ((PsiClass)psiClass).getImplementsList() &&
intf instanceof PsiClass && ((PsiClass)intf).isInterface()) {
String error = checkIndirectInheritance(psiClass, (PsiClass)intf);
if (error != null) {
holder.registerProblem(reference, error);
}
}
}
private void checkNullableNotNullInstantiationConflict(PsiJavaCodeReferenceElement reference) {
PsiElement element = reference.resolve();
if (element instanceof PsiClass) {
PsiTypeParameter[] typeParameters = ((PsiClass)element).getTypeParameters();
PsiTypeElement[] typeArguments = getReferenceTypeArguments(reference);
if (typeParameters.length > 0 && typeParameters.length == typeArguments.length && !(typeArguments[0].getType() instanceof PsiDiamondType)) {
for (int i = 0; i < typeParameters.length; i++) {
PsiTypeElement typeArgument = typeArguments[i];
Project project = element.getProject();
PsiType type = typeArgument.getType();
if (TypeNullability.ofTypeParameter(typeParameters[i]).nullability() != Nullability.NOT_NULL) continue;
TypeNullability nullability = type.getNullability();
Nullability typeNullability = nullability.nullability();
if (typeNullability != Nullability.NOT_NULL &&
!(typeNullability == Nullability.UNKNOWN && type instanceof PsiWildcardType wildcardType && !wildcardType.isExtends())) {
String annotationToAdd = manager.getDefaultAnnotation(Nullability.NOT_NULL, reference);
PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(annotationToAdd, element.getResolveScope());
List<LocalQuickFix> fixes = new ArrayList<>();
if (annotationClass != null &&
AnnotationTargetUtil.findAnnotationTarget(annotationClass, PsiAnnotation.TargetType.TYPE_USE) != null) {
fixes.add(LocalQuickFix.from(new AddTypeAnnotationFix(typeArgument, annotationToAdd, manager.getNullables())));
}
fixes.add(LocalQuickFix.from(createAnnotateAsNullMarkedFix(typeArgument, manager.getNullables()), false));
ProblemHighlightType level =
nullability == TypeNullability.UNKNOWN && !REPORT_NOT_ANNOTATED_INSTANTIATION_NOT_NULL_TYPE ?
ProblemHighlightType.INFORMATION :
ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
if (!isOnTheFly && level == ProblemHighlightType.INFORMATION) continue;
holder.registerProblem(typeArgument,
JavaAnalysisBundle.message("non.null.type.argument.is.expected"),
level,
fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
}
}
}
}
}
private static PsiTypeElement[] getReferenceTypeArguments(PsiJavaCodeReferenceElement reference) {
PsiReferenceParameterList typeArgList = reference.getParameterList();
return typeArgList == null ? PsiTypeElement.EMPTY_ARRAY : typeArgList.getTypeParameterElements();
}
@Override
public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) {
PsiExpression rExpression = expression.getRExpression();
if (rExpression == null) return;
checkNestedGenericClasses(holder, expression.getOperationSign(),
expression.getLExpression().getType(),
rExpression.getType(),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
@Override
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
PsiIdentifier identifier = variable.getNameIdentifier();
if (identifier == null) return;
PsiExpression initializer = variable.getInitializer();
if (initializer == null) return;
checkNestedGenericClasses(holder, identifier, variable.getType(), initializer.getType(),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
@Override
public void visitReturnStatement(@NotNull PsiReturnStatement statement) {
PsiExpression returnValue = statement.getReturnValue();
if (returnValue == null) return;
checkNestedGenericClasses(holder, returnValue,
PsiTypesUtil.getMethodReturnType(statement), returnValue.getType(),
ConflictNestedTypeProblem.RETURN_NESTED_TYPE_PROBLEM);
}
@Override
public void visitLambdaExpression(@NotNull PsiLambdaExpression lambda) {
super.visitLambdaExpression(lambda);
PsiElement body = lambda.getBody();
if (body instanceof PsiExpression psiExpression) {
checkNestedGenericClasses(holder, body, LambdaUtil.getFunctionalInterfaceReturnType(lambda), psiExpression.getType(), ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
}
@Override
public void visitCallExpression(@NotNull PsiCallExpression callExpression) {
PsiExpressionList argList = callExpression.getArgumentList();
JavaResolveResult result = callExpression.resolveMethodGenerics();
PsiMethod method = (PsiMethod)result.getElement();
if (method == null || argList == null) return;
PsiSubstitutor substitutor = result.getSubstitutor();
PsiParameter[] parameters = method.getParameterList().getParameters();
PsiExpression[] arguments = argList.getExpressions();
for (int i = 0; i < arguments.length; i++) {
PsiExpression argument = arguments[i];
if (i < parameters.length &&
(i < parameters.length - 1 || !MethodCallInstruction.isVarArgCall(method, substitutor, arguments, parameters))) {
PsiType expectedType = substitutor.substitute(parameters[i].getType());
checkNestedGenericClasses(holder, argument, expectedType, argument.getType(),
ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
}
}
}
};
}
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 -> null;
case NOT_NULL_TO_NULL -> problem.notNullToNullProblem();
case NULL_TO_NOT_NULL -> problem.nullToNotNullProblem();
case COMPLEX -> problem.complexProblem();
};
if (messageKey == null) return false;
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());
List<PsiAnnotation> conflictingAnnotations = manager.getConflictingContainerAnnotations(list);
if (!conflictingAnnotations.isEmpty()) {
for (PsiAnnotation annotation : conflictingAnnotations) {
reportProblem(holder, annotation, "conflicting.nullability.annotations");
}
}
}
private void reportProblem(@NotNull ProblemsHolder holder, PsiElement anchor, String messageKey, Object... args) {
reportProblem(holder, anchor, LocalQuickFix.EMPTY_ARRAY, messageKey, args);
}
private void reportProblem(@NotNull ProblemsHolder holder, @NotNull PsiElement anchor, @Nullable LocalQuickFix fix,
@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String messageKey, Object... args) {
reportProblem(holder, anchor, fix == null ? LocalQuickFix.EMPTY_ARRAY : new LocalQuickFix[] {fix}, messageKey, args);
}
private void reportProblem(@NotNull ProblemsHolder holder, @NotNull PsiElement anchor, @Nullable ModCommandAction fix,
@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String messageKey, Object... args) {
reportProblem(holder, anchor, fix == null ? LocalQuickFix.EMPTY_ARRAY : new LocalQuickFix[] {LocalQuickFix.from(fix)}, messageKey, args);
}
protected void reportProblem(@NotNull ProblemsHolder holder, @NotNull PsiElement anchor, @NotNull LocalQuickFix @NotNull [] fixes,
@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String messageKey, Object... args) {
reportProblem(holder, anchor, fixes, messageKey, args, messageKey, args);
}
protected void reportProblem(@NotNull ProblemsHolder holder, @NotNull PsiElement anchor, @NotNull LocalQuickFix @NotNull [] fixes,
@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String descriptionKey, @NotNull Object @NotNull[] descriptionArgs,
@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String tooltipKey, @NotNull Object @NotNull[] tooltipArgs) {
ProblemsHolder.ProblemBuilder builder = holder.problem(anchor, JavaAnalysisBundle.message(descriptionKey, descriptionArgs))
.tooltip(JavaAnalysisBundle.message(tooltipKey, tooltipArgs))
.highlight(ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
for (LocalQuickFix quickFix : fixes) {
builder.fix(quickFix);
}
builder.register();
}
private @Nullable @InspectionMessage String checkIndirectInheritance(PsiElement psiClass, PsiClass intf) {
for (PsiMethod intfMethod : intf.getAllMethods()) {
PsiClass intfMethodClass = intfMethod.getContainingClass();
PsiMethod overridingMethod = intfMethodClass == null ? null :
JavaOverridingMethodsSearcher.findOverridingMethod((PsiClass)psiClass, intfMethod, intfMethodClass);
PsiClass overridingMethodClass = overridingMethod == null ? null : overridingMethod.getContainingClass();
if (overridingMethodClass != null && overridingMethodClass != psiClass) {
String error = checkIndirectInheritance(intfMethod, intfMethodClass, overridingMethod, overridingMethodClass);
if (error != null) return error;
}
}
return null;
}
private @Nullable @InspectionMessage String checkIndirectInheritance(PsiMethod intfMethod,
PsiClass intfMethodClass,
PsiMethod overridingMethod,
PsiClass overridingMethodClass) {
if (isNullableOverridingNotNull(Annotated.from(overridingMethod), intfMethod)) {
return JavaAnalysisBundle.message("inspection.message.nullable.method.implements.non.null.method",
overridingMethod.getName(), overridingMethodClass.getName(), intfMethodClass.getName());
}
if (isNonAnnotatedOverridingNotNull(overridingMethod, intfMethod)) {
return JavaAnalysisBundle.message("inspection.message.non.annotated.method.implements.non.null.method",
overridingMethod.getName(), overridingMethodClass.getName(), intfMethodClass.getName());
}
PsiParameter[] overridingParameters = overridingMethod.getParameterList().getParameters();
PsiParameter[] superParameters = intfMethod.getParameterList().getParameters();
if (overridingParameters.length == superParameters.length) {
NullableNotNullManager manager = getNullityManager(intfMethod);
for (int i = 0; i < overridingParameters.length; i++) {
PsiParameter parameter = overridingParameters[i];
List<PsiParameter> supers = Collections.singletonList(superParameters[i]);
if (findNullableSuperForNotNullParameter(parameter, supers) != null) {
return JavaAnalysisBundle.message("inspection.message.non.null.parameter.should.not.override.nullable.parameter",
parameter.getName(), overridingMethod.getName(), overridingMethodClass.getName(), intfMethodClass.getName());
}
if (findNotNullSuperForNonAnnotatedParameter(manager, parameter, supers) != null) {
return JavaAnalysisBundle.message("inspection.message.non.annotated.parameter.should.not.override.non.null.parameter",
parameter.getName(), overridingMethod.getName(), overridingMethodClass.getName(), intfMethodClass.getName());
}
if (isNotNullParameterOverridingNonAnnotated(manager, parameter, supers)) {
return JavaAnalysisBundle.message("inspection.message.non.null.parameter.should.not.override.non.annotated.parameter",
parameter.getName(), overridingMethod.getName(), overridingMethodClass.getName(), intfMethodClass.getName());
}
}
}
return null;
}
private void checkMethodReference(PsiMethodReferenceExpression expression, @NotNull ProblemsHolder holder) {
PsiMethod superMethod = LambdaUtil.getFunctionalInterfaceMethod(expression);
PsiMethod targetMethod = tryCast(expression.resolve(), PsiMethod.class);
if (superMethod == null || targetMethod == null) return;
PsiElement refName = expression.getReferenceNameElement();
assert refName != null;
if (isNullableOverridingNotNull(check(targetMethod, holder, expression.getType()), superMethod)) {
reportProblem(holder, refName, "inspection.nullable.problems.Nullable.method.overrides.NotNull",
getPresentableAnnoName(targetMethod), getPresentableAnnoName(superMethod));
}
}
protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) {
return null;
}
private static boolean nullabilityAnnotationsNotAvailable(final PsiFile file) {
final Project project = file.getProject();
final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
return ContainerUtil.find(NullableNotNullManager.getInstance(project).getNullables(), s -> facade.findClass(s, scope) != null) == null;
}
private void checkAccessors(PsiField field,
Annotated annotated,
Project project,
NullableNotNullManager manager, final String anno, final List<String> annoToRemove, @NotNull ProblemsHolder holder) {
String propName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD);
final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC);
final PsiMethod getter = PropertyUtilBase.findPropertyGetter(field.getContainingClass(), propName, isStatic, false);
final PsiIdentifier nameIdentifier = getter == null ? null : getter.getNameIdentifier();
if (nameIdentifier != null && nameIdentifier.isPhysical()) {
if (PropertyUtil.getFieldOfGetter(getter) == field) {
AddAnnotationModCommandAction getterAnnoFix = new AddAnnotationModCommandAction(anno, getter, ArrayUtilRt.toStringArray(annoToRemove));
if (REPORT_NOT_ANNOTATED_GETTER) {
if (!hasNullability(manager, getter) && !TypeConversionUtil.isPrimitiveAndNotNull(getter.getReturnType())) {
reportProblem(holder, nameIdentifier, getterAnnoFix, "inspection.nullable.problems.annotated.field.getter.not.annotated",
StringUtil.getShortName(anno));
}
}
if (annotated.isDeclaredNotNull && isNullableNotInferred(getter, false) ||
annotated.isDeclaredNullable && isNotNullNotInferred(getter, false, false)) {
reportProblem(holder, nameIdentifier, getterAnnoFix,
"inspection.nullable.problems.annotated.field.getter.conflict", getPresentableAnnoName(field),
getPresentableAnnoName(getter));
}
}
}
final PsiClass containingClass = field.getContainingClass();
final PsiMethod setter = PropertyUtilBase.findPropertySetter(containingClass, propName, isStatic, false);
if (setter != null && setter.isPhysical() && PropertyUtil.getFieldOfSetter(setter) == field) {
final PsiParameter[] parameters = setter.getParameterList().getParameters();
assert parameters.length == 1 : setter.getText();
final PsiParameter parameter = parameters[0];
LOG.assertTrue(parameter != null, setter.getText());
@NotNull ModCommandAction addAnnoFix = createAddAnnotationFix(anno, annoToRemove, parameter);
if (REPORT_NOT_ANNOTATED_GETTER && !hasNullability(manager, parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) {
final PsiIdentifier parameterName = parameter.getNameIdentifier();
assertValidElement(setter, parameter, parameterName);
reportProblem(holder, parameterName, addAnnoFix,
"inspection.nullable.problems.annotated.field.setter.parameter.not.annotated", getPresentableAnnoName(field));
}
if (PropertyUtil.isSimpleSetter(setter)) {
if (annotated.isDeclaredNotNull && isNullableNotInferred(parameter, false)) {
final PsiIdentifier parameterName = parameter.getNameIdentifier();
assertValidElement(setter, parameter, parameterName);
reportProblem(holder, parameterName, addAnnoFix,
"inspection.nullable.problems.annotated.field.setter.parameter.conflict",
getPresentableAnnoName(field), getPresentableAnnoName(parameter));
}
}
}
}
private static @NotNull ModCommandAction createAddAnnotationFix(@NotNull String anno, @NotNull List<String> annoToRemove, @NotNull PsiParameter parameter) {
return new AddAnnotationModCommandAction(anno, parameter, ArrayUtilRt.toStringArray(annoToRemove));
}
@Contract("_,_,null -> fail")
private static void assertValidElement(PsiMethod setter, PsiParameter parameter, PsiIdentifier nameIdentifier1) {
LOG.assertTrue(nameIdentifier1 != null && nameIdentifier1.isPhysical(), setter.getText());
LOG.assertTrue(parameter.isPhysical(), setter.getText());
}
private void checkConstructorParameters(PsiField field, Annotated annotated,
String anno, List<String> annoToRemove, @NotNull ProblemsHolder holder) {
List<PsiExpression> initializers = DfaPsiUtil.findAllConstructorInitializers(field);
if (initializers.isEmpty()) return;
if (REPORT_NOT_ANNOTATED_GETTER) {
reportConstructorParameterFromField(field, anno, annoToRemove, holder, initializers);
}
if (field.hasModifierProperty(PsiModifier.FINAL)) {
checkFinalFieldInitializedNotNull(field, annotated, holder, initializers);
}
}
private void checkFinalFieldInitializedNotNull(@NotNull PsiField field, @NotNull Annotated annotated,
@NotNull ProblemsHolder holder, @NotNull List<PsiExpression> initializers) {
List<PsiParameter> notNullParams = new ArrayList<>();
for (PsiExpression rhs : initializers) {
if (rhs instanceof PsiReferenceExpression ref) {
PsiElement target = ref.resolve();
if (isConstructorParameter(target) && target.isPhysical()) {
PsiParameter parameter = (PsiParameter)target;
if (annotated.isDeclaredNullable && isNotNullNotInferred(parameter, false, false)) {
notNullParams.add(parameter);
}
}
}
}
if (notNullParams.size() != initializers.size()) {
// it's not the case that the field is final and @Nullable and always initialized via @NotNull parameters,
// so there might be other initializers that could justify it being nullable,
// so don't highlight field and constructor parameter annotation inconsistency
return;
}
PsiIdentifier nameIdentifier = field.getNameIdentifier();
if (nameIdentifier.isPhysical()) {
reportProblem(holder, nameIdentifier, AddAnnotationModCommandAction.createAddNotNullFix(field),
"0.field.is.always.initialized.not.null", getPresentableAnnoName(field));
}
}
private void reportConstructorParameterFromField(@NotNull PsiField field,
@NotNull String anno,
@NotNull List<String> annoToRemove,
@NotNull ProblemsHolder holder,
@NotNull List<PsiExpression> initializers) {
Map<PsiMethod, List<PsiExpression>> ctorToInitializers = StreamEx.of(initializers)
.mapToEntry(e -> PsiTreeUtil.getParentOfType(e, PsiMethod.class), e -> e)
.nonNullKeys()
.filterKeys(PsiMethod::isConstructor)
.grouping();
ctorToInitializers.forEach((ctor, exprs) -> {
List<PsiParameter> parameters = StreamEx.of(exprs)
.map(e -> PsiUtil.skipParenthesizedExprDown(e) instanceof PsiReferenceExpression ref
? tryCast(ref.resolve(), PsiParameter.class)
: null)
.distinct()
.limit(2)
.toList();
if (parameters.size() != 1) return;
PsiParameter parameter = parameters.getFirst();
if (parameter != null && parameter.getDeclarationScope() == ctor
&& !hasNullability(NullableNotNullManager.getInstance(field.getProject()), parameter)
&& !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) {
final PsiIdentifier nameIdentifier = parameter.getNameIdentifier();
if (nameIdentifier != null && nameIdentifier.isPhysical()) {
reportProblem(holder, nameIdentifier, createAddAnnotationFix(anno, annoToRemove, parameter),
"inspection.nullable.problems.annotated.field.constructor.parameter.not.annotated",
StringUtil.getShortName(anno));
}
}
});
}
private static boolean isConstructorParameter(@Nullable PsiElement parameter) {
return parameter instanceof PsiParameter && psiElement(PsiParameterList.class).withParent(psiMethod().constructor(true)).accepts(parameter.getParent());
}
private static @NotNull String getPresentableAnnoName(@NotNull PsiModifierListOwner owner) {
NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject());
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner);
String name = info == null ? null : info.getAnnotation().getQualifiedName();
if (name == null) {
return "???";
}
return StringUtil.getShortName(name);
}
public static String getPresentableAnnoName(@NotNull PsiAnnotation annotation) {
return StringUtil.getShortName(StringUtil.notNullize(annotation.getQualifiedName(), "???"));
}
/**
* @return true if the owner has a @NotNull or @Nullable annotation,
* or is in scope of @ParametersAreNullableByDefault or ParametersAreNonnullByDefault
*/
private static boolean hasNullability(@NotNull NullableNotNullManager manager, @NotNull PsiModifierListOwner owner) {
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner);
return info != null && info.getNullability() != Nullability.UNKNOWN && info.getInheritedFrom() == null;
}
private static final class Annotated {
private final boolean isDeclaredNotNull;
private final boolean isDeclaredNullable;
private final @Nullable PsiAnnotation notNull;
private final @Nullable PsiAnnotation nullable;
private Annotated(@Nullable PsiAnnotation notNull, @Nullable PsiAnnotation nullable) {
this.isDeclaredNotNull = notNull != null;
this.isDeclaredNullable = nullable != null;
this.notNull = notNull;
this.nullable = nullable;
}
static @NotNull Annotated from(@NotNull PsiModifierListOwner owner) {
NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject());
NullabilityAnnotationInfo notNullInfo = manager.findNullabilityAnnotationInfo(owner, Collections.singleton(Nullability.NOT_NULL));
NullabilityAnnotationInfo nullableInfo = manager.findNullabilityAnnotationInfo(owner, Collections.singleton(Nullability.NULLABLE));
PsiAnnotation notNullAnno = notNullInfo == null || (notNullInfo.isContainer() && nullableInfo != null && !nullableInfo.isContainer())
? null : notNullInfo.getAnnotation();
PsiAnnotation nullableAnno = nullableInfo == null || (nullableInfo.isContainer() && notNullInfo != null && !notNullInfo.isContainer())
? null : nullableInfo.getAnnotation();
return new Annotated(notNullAnno, nullableAnno);
}
}
private Annotated check(final PsiModifierListOwner owner, final ProblemsHolder holder, PsiType type) {
Annotated annotated = Annotated.from(owner);
PsiAnnotation annotation = annotated.notNull == null ? annotated.nullable : annotated.notNull;
if (annotation != null && !annotation.isPhysical() && type instanceof PsiPrimitiveType) {
reportIncorrectLocation(holder, annotation, owner, "inspection.nullable.problems.primitive.type.annotation");
}
if (owner instanceof PsiParameter parameter) {
Nullability expectedNullability = DfaPsiUtil.inferParameterNullability(parameter);
if (annotated.notNull != null && expectedNullability == Nullability.NULLABLE) {
reportParameterNullabilityMismatch(parameter, annotated.notNull, holder, "parameter.can.be.null");
}
else if (annotated.nullable != null && expectedNullability == Nullability.NOT_NULL) {
reportParameterNullabilityMismatch(parameter, annotated.nullable, holder, "parameter.is.always.not.null");
}
}
return annotated;
}
private void reportParameterNullabilityMismatch(@NotNull PsiParameter owner,
@NotNull PsiAnnotation annotation,
@NotNull ProblemsHolder holder,
@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String messageKey) {
if (annotation.isPhysical() && !PsiTreeUtil.isAncestor(owner, annotation, true)) return;
PsiElement anchor = annotation.isPhysical() ? annotation : owner.getNameIdentifier();
if (anchor != null && !anchor.getTextRange().isEmpty()) {
reportProblem(holder, anchor, new RemoveAnnotationQuickFix(annotation, owner), messageKey);
}
}
private void reportIncorrectLocation(ProblemsHolder holder, PsiAnnotation annotation,
@Nullable PsiModifierListOwner listOwner,
@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String messageKey,
@NotNull LocalQuickFix @NotNull ... additionalFixes) {
RemoveAnnotationQuickFix removeFix = new RemoveAnnotationQuickFix(annotation, listOwner, true);
MoveAnnotationToBoundFix moveToBoundFix = MoveAnnotationToBoundFix.create(annotation);
LocalQuickFix[] fixes = moveToBoundFix == null ? new LocalQuickFix[]{removeFix} : new LocalQuickFix[]{moveToBoundFix, removeFix};
reportProblem(holder, !annotation.isPhysical() && listOwner != null ? listOwner.getNavigationElement() : annotation,
ArrayUtil.mergeArrays(additionalFixes, fixes), messageKey);
}
@Override
public @NotNull String getGroupDisplayName() {
return InspectionsBundle.message("group.names.probable.bugs");
}
@Override
public @NotNull String getShortName() {
return "NullableProblems";
}
private void checkNullableStuffForMethod(PsiMethod method, final ProblemsHolder holder) {
Annotated annotated = check(method, holder, method.getReturnType());
List<PsiMethod> superMethods = ContainerUtil.map(
method.findSuperMethodSignaturesIncludingStatic(true), MethodSignatureBackedByPsiMethod::getMethod);
final NullableNotNullManager nullableManager = NullableNotNullManager.getInstance(holder.getProject());
checkSupers(method, holder, annotated, superMethods);
checkParameters(method, holder, superMethods, nullableManager);
checkOverriders(method, holder, annotated, nullableManager);
checkConflictingContainerAnnotations(holder, method.getModifierList());
}
private void checkSupers(PsiMethod method,
ProblemsHolder holder,
Annotated annotated,
List<? extends PsiMethod> superMethods) {
PsiIdentifier identifier = method.getNameIdentifier();
if (identifier == null) return;
for (PsiMethod superMethod : superMethods) {
if (isNullableOverridingNotNull(annotated, superMethod)) {
PsiAnnotation annotation = findAnnotation(method, getNullityManager(method).getNullables(), true);
reportProblem(holder, annotation != null ? annotation : identifier,
"inspection.nullable.problems.Nullable.method.overrides.NotNull",
getPresentableAnnoName(method), getPresentableAnnoName(superMethod));
break;
}
if (isNonAnnotatedOverridingNotNull(method, superMethod)) {
reportProblem(holder, identifier, createFixForNonAnnotatedOverridesNotNull(method),
"inspection.nullable.problems.method.overrides.NotNull", getPresentableAnnoName(superMethod));
break;
}
PsiTypeElement returnTypeElement = method.getReturnTypeElement();
if (returnTypeElement != null &&
checkNestedGenericClasses(holder, returnTypeElement, superMethod.getReturnType(), method.getReturnType(),
ConflictNestedTypeProblem.OVERRIDING_NESTED_TYPE_PROBLEM)) {
break;
}
}
}
private static NullableNotNullManager getNullityManager(PsiMethod method) {
return NullableNotNullManager.getInstance(method.getProject());
}
private static @Nullable LocalQuickFix createFixForNonAnnotatedOverridesNotNull(PsiMethod method) {
NullableNotNullManager nullableManager = getNullityManager(method);
return isAnnotatingApplicable(method, nullableManager.getDefaultNotNull())
? LocalQuickFix.from(AddAnnotationModCommandAction.createAddNotNullFix(method))
: null;
}
private boolean isNullableOverridingNotNull(Annotated methodInfo, PsiMethod superMethod) {
return REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE && methodInfo.isDeclaredNullable && isNotNullNotInferred(superMethod, true, false);
}
private boolean isNonAnnotatedOverridingNotNull(PsiMethod method, PsiMethod superMethod) {
if (REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL &&
!(method.getReturnType() instanceof PsiPrimitiveType) &&
!method.isConstructor()) {
NullableNotNullManager manager = getNullityManager(method);
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(method);
if ((info == null || info.isInferred() ||
(info.getInheritedFrom() != null && manager.findContainerAnnotation(method) == null)) &&
isNotNullNotInferred(superMethod, true, IGNORE_EXTERNAL_SUPER_NOTNULL) &&
!hasInheritableNotNull(superMethod)) {
return true;
}
}
return false;
}
private static boolean hasInheritableNotNull(PsiModifierListOwner owner) {
return isAnnotated(owner, "javax.annotation.constraints.NotNull", CHECK_HIERARCHY | CHECK_TYPE);
}
private void checkParameters(PsiMethod method,
ProblemsHolder holder,
List<? extends PsiMethod> superMethods,
NullableNotNullManager nullableManager) {
PsiParameter[] parameters = method.getParameterList().getParameters();
for (int i = 0; i < parameters.length; i++) {
PsiParameter parameter = parameters[i];
if (parameter.getType() instanceof PsiPrimitiveType) continue;
List<PsiParameter> superParameters = getSuperParameters(superMethods, parameters, i);
checkSuperParameterAnnotations(holder, nullableManager, parameter, superParameters);
checkNullLiteralArgumentOfNotNullParameterUsages(method, holder, nullableManager, i, parameter);
}
}
private static @NotNull List<PsiParameter> getSuperParameters(List<? extends PsiMethod> superMethods, PsiParameter[] parameters, int i) {
List<PsiParameter> superParameters = new ArrayList<>();
for (PsiMethod superMethod : superMethods) {
PsiParameter[] _superParameters = superMethod.getParameterList().getParameters();
if (_superParameters.length == parameters.length) {
superParameters.add(_superParameters[i]);
}
}
return superParameters;
}
private void checkSuperParameterAnnotations(ProblemsHolder holder,
NullableNotNullManager nullableManager,
PsiParameter parameter,
List<PsiParameter> superParameters) {
PsiIdentifier nameIdentifier = parameter.getNameIdentifier();
if (nameIdentifier == null) return;
PsiParameter nullableSuper = findNullableSuperForNotNullParameter(parameter, superParameters);
if (nullableSuper != null) {
PsiAnnotation annotation = findAnnotation(parameter, nullableManager.getNotNulls(), true);
reportProblem(holder, annotation != null ? annotation : nameIdentifier,
"inspection.nullable.problems.NotNull.parameter.overrides.Nullable",
getPresentableAnnoName(parameter), getPresentableAnnoName(nullableSuper));
}
PsiParameter notNullSuper = findNotNullSuperForNonAnnotatedParameter(nullableManager, parameter, superParameters);
if (notNullSuper != null) {
LocalQuickFix fix = isAnnotatingApplicable(parameter, nullableManager.getDefaultAnnotation(Nullability.NOT_NULL, parameter))
? LocalQuickFix.from(AddAnnotationModCommandAction.createAddNotNullFix(parameter))
: null;
reportProblem(holder, nameIdentifier, fix,
"inspection.nullable.problems.parameter.overrides.NotNull", getPresentableAnnoName(notNullSuper));
}
if (isNotNullParameterOverridingNonAnnotated(nullableManager, parameter, superParameters)) {
NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter);
assert info != null;
PsiAnnotation notNullAnnotation = info.getAnnotation();
boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true);
final LocalQuickFix fix = physical ? new RemoveAnnotationQuickFix(notNullAnnotation, parameter) : null;
reportProblem(holder, physical ? notNullAnnotation : nameIdentifier, fix,
"inspection.nullable.problems.NotNull.parameter.overrides.not.annotated", getPresentableAnnoName(parameter));
}
PsiTypeElement typeElement = parameter.getTypeElement();
if (typeElement != null) {
for (PsiParameter superParameter : superParameters) {
if(checkNestedGenericClasses(holder, typeElement, parameter.getType(), superParameter.getType(),
ConflictNestedTypeProblem.OVERRIDING_NESTED_TYPE_PROBLEM)){
break;
}
}
}
}
private @Nullable PsiParameter findNotNullSuperForNonAnnotatedParameter(NullableNotNullManager nullableManager,
PsiParameter parameter,
List<? extends PsiParameter> superParameters) {
return REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL && !hasNullability(nullableManager, parameter)
? ContainerUtil.find(superParameters,
sp -> isNotNullNotInferred(sp, false, IGNORE_EXTERNAL_SUPER_NOTNULL) && !hasInheritableNotNull(sp))
: null;
}
private @Nullable PsiParameter findNullableSuperForNotNullParameter(PsiParameter parameter, List<? extends PsiParameter> superParameters) {
if (!REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE || !isNotNullNotInferred(parameter, false, false)) return null;
PsiClass derived = PsiUtil.getContainingClass(parameter);
for (PsiParameter superParameter : superParameters) {
PsiClass base = PsiUtil.getContainingClass(superParameter);
PsiSubstitutor substitutor = base == null || derived == null ? PsiSubstitutor.EMPTY :
TypeConversionUtil.getSuperClassSubstitutor(base, derived, PsiSubstitutor.EMPTY);
PsiType superType = substitutor.substitute(superParameter.getType());
if (DfaPsiUtil.getElementNullabilityForRead(superType, superParameter) == Nullability.NULLABLE) {
return superParameter;
}
}
return null;
}
private boolean isNotNullParameterOverridingNonAnnotated(NullableNotNullManager nullableManager,
PsiParameter parameter,
List<? extends PsiParameter> superParameters) {
if (!REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED) return false;
NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter);
return info != null && info.getNullability() == Nullability.NOT_NULL && !info.isInferred() &&
ContainerUtil.exists(superParameters, sp -> isSuperNotAnnotated(nullableManager, parameter, sp));
}
private static boolean isSuperNotAnnotated(NullableNotNullManager nullableManager, PsiParameter parameter, PsiParameter superParameter) {
if (hasNullability(nullableManager, superParameter)) return false;
if (ContainerUtil.exists(getSuperAnnotationOwners(superParameter),
superSuperParameter -> hasNullability(nullableManager, superSuperParameter))) {
return false;
}
PsiType type = superParameter.getType();
if (TypeUtils.isTypeParameter(type)) {
PsiClass childClass = PsiUtil.getContainingClass(parameter);
PsiClass superClass = PsiUtil.getContainingClass(superParameter);
if (superClass != null && childClass != null) {
PsiType substituted =
TypeConversionUtil.getSuperClassSubstitutor(superClass, childClass, PsiSubstitutor.EMPTY).substitute(type);
return DfaPsiUtil.getTypeNullability(substituted) == Nullability.UNKNOWN;
}
}
return true;
}
private void checkNullLiteralArgumentOfNotNullParameterUsages(PsiMethod method,
ProblemsHolder holder,
NullableNotNullManager nullableManager,
int parameterIdx,
PsiParameter parameter) {
if (!REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER || !holder.isOnTheFly()) return;
PsiVariable owner = parameter.isPhysical() ? parameter : JavaPsiRecordUtil.getComponentForCanonicalConstructorParameter(parameter);
if (owner == null) return;
PsiElement elementToHighlight = null;
NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(owner);
if (info != null && !info.isInferred()) {
if (info.getNullability() == Nullability.NOT_NULL) {
PsiAnnotation notNullAnnotation = info.getAnnotation();
boolean physical = PsiTreeUtil.isAncestor(owner, notNullAnnotation, true);
elementToHighlight = physical ? notNullAnnotation : owner.getNameIdentifier();
}
}
else {
info = DfaPsiUtil.getTypeNullabilityInfo(owner.getType());
if (info != null && info.getNullability() == Nullability.NOT_NULL) {
elementToHighlight = owner.getNameIdentifier();
}
}
if (elementToHighlight == null || !JavaNullMethodArgumentUtil.hasNullArgument(method, parameterIdx)) return;
reportProblem(holder, elementToHighlight, createNavigateToNullParameterUsagesFix(parameter),
"inspection.nullable.problems.NotNull.parameter.receives.null.literal",
StringUtil.getShortName(requireNonNull(info.getAnnotation().getQualifiedName())));
}
private void checkOverriders(@NotNull PsiMethod method,
@NotNull ProblemsHolder holder,
@NotNull Annotated annotated,
@NotNull NullableNotNullManager nullableManager) {
PsiParameter[] parameters = method.getParameterList().getParameters();
if (REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS) {
boolean[] checkParameter = new boolean[parameters.length];
boolean[] parameterQuickFixSuggested = new boolean[parameters.length];
boolean hasAnnotatedParameter = false;
for (int i = 0; i < parameters.length; i++) {
PsiParameter parameter = parameters[i];
checkParameter[i] = isNotNullNotInferred(parameter, false, false) &&
!hasInheritableNotNull(parameter) &&
!(parameter.getType() instanceof PsiPrimitiveType);
hasAnnotatedParameter |= checkParameter[i];
}
boolean checkReturnType = annotated.isDeclaredNotNull && !hasInheritableNotNull(method) && !(method.getReturnType() instanceof PsiPrimitiveType);
if (hasAnnotatedParameter || checkReturnType) {
PsiMethod[] overridings =
OverridingMethodsSearch.search(method).toArray(PsiMethod.EMPTY_ARRAY);
boolean methodQuickFixSuggested = false;
for (PsiMethod overriding : overridings) {
if (shouldSkipOverriderAsGenerated(overriding)) continue;
final String defaultNotNull = nullableManager.getDefaultAnnotation(Nullability.NOT_NULL, overriding);
if (!methodQuickFixSuggested
&& checkReturnType
&& !isNotNullNotInferred(overriding, false, false)
&& (isNullableNotInferred(overriding, false) || !isNullableNotInferred(overriding, true))
&& AddAnnotationPsiFix.isAvailable(overriding, defaultNotNull)) {
PsiIdentifier identifier = method.getNameIdentifier();//load tree
NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(method);
if (info != null) {
PsiAnnotation annotation = info.getAnnotation();
final String[] annotationsToRemove = ArrayUtilRt.toStringArray(nullableManager.getNullables());
LocalQuickFix fix = isAnnotatingApplicable(overriding, defaultNotNull)
? new MyAnnotateMethodFix(Nullability.NOT_NULL, annotationsToRemove)
: null;
PsiElement psiElement = annotation;
if (!annotation.isPhysical() || !PsiTreeUtil.isAncestor(method, annotation, true)) {
psiElement = identifier;
if (psiElement == null) continue;
}
reportProblem(holder, psiElement, fix, "nullable.stuff.problems.overridden.methods.are.not.annotated");
methodQuickFixSuggested = true;
}
}
if (hasAnnotatedParameter) {
PsiParameter[] psiParameters = overriding.getParameterList().getParameters();
for (int i = 0; i < psiParameters.length; i++) {
if (parameterQuickFixSuggested[i]) continue;
PsiParameter parameter = psiParameters[i];
if (checkParameter[i] &&
!isNotNullNotInferred(parameter, false, false) &&
!isNullableNotInferred(parameter, false) &&
AddAnnotationPsiFix.isAvailable(parameter, defaultNotNull)) {
PsiIdentifier identifier = parameters[i].getNameIdentifier(); //be sure that corresponding tree element available
NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameters[i]);
PsiElement psiElement = info == null ? null : info.getAnnotation();
if (psiElement == null || !psiElement.isPhysical()) {
psiElement = identifier;
if (psiElement == null) continue;
}
LocalQuickFix fix = isAnnotatingApplicable(parameter, defaultNotNull)
? new AnnotateOverriddenMethodParameterFix(Nullability.NOT_NULL)
: null;
reportProblem(holder, psiElement, fix,
"nullable.stuff.problems.overridden.method.parameters.are.not.annotated");
parameterQuickFixSuggested[i] = true;
}
}
}
}
}
}
}
public static boolean shouldSkipOverriderAsGenerated(PsiMethod overriding) {
if (Registry.is("idea.report.nullity.missing.in.generated.overriders")) return false;
PsiFile file = overriding.getContainingFile();
VirtualFile virtualFile = file != null ? file.getVirtualFile() : null;
return virtualFile != null && GeneratedSourcesFilter.isGeneratedSourceByAnyFilter(virtualFile, overriding.getProject());
}
private static boolean isNotNullNotInferred(@NotNull PsiModifierListOwner owner, boolean checkBases, boolean skipExternal) {
Project project = owner.getProject();
NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner);
if (info == null || info.isInferred() || info.getNullability() != Nullability.NOT_NULL) return false;
if (!checkBases && info.getInheritedFrom() != null) return false;
if (skipExternal && info.isExternal()) return false;
return true;
}
public static boolean isNullableNotInferred(@NotNull PsiModifierListOwner owner, boolean checkBases) {
Project project = owner.getProject();
NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner);
return info != null && !info.isInferred() && info.getNullability() == Nullability.NULLABLE &&
(checkBases || info.getInheritedFrom() == null);
}
private static class MyAnnotateMethodFix implements LocalQuickFix {
private final @NotNull Nullability myNullability;
private final String[] myAnnotationsToRemove;
MyAnnotateMethodFix(@NotNull Nullability nullability, String @NotNull ... annotationsToRemove) {
myNullability = nullability;
myAnnotationsToRemove = annotationsToRemove.length == 0 ? ArrayUtilRt.EMPTY_STRING_ARRAY : annotationsToRemove;
}
@Override
public @NotNull String getFamilyName() {
return JavaAnalysisBundle.message("inspection.annotate.overridden.method.quickfix.family.name");
}
@Override
public boolean startInWriteAction() {
return false;
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement psiElement = descriptor.getPsiElement();
PsiMethod method = PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class);
if (method == null) return;
final List<PsiMethod> toAnnotate = new ArrayList<>();
NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
if (!AnnotateOverriddenMethodParameterFix.processModifiableInheritorsUnderProgress(method, (Consumer<? super PsiMethod>)psiMethod -> {
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(psiMethod);
if (info != null && info.getNullability() == myNullability && !info.isInferred() && info.getInheritedFrom() == null) return;
String annotation = manager.getDefaultAnnotation(myNullability, psiMethod);
if (isAnnotatingApplicable(psiMethod, annotation) &&
!isAnnotated(psiMethod, annotation, CHECK_EXTERNAL | CHECK_TYPE)) {
toAnnotate.add(psiMethod);
}
})) {
return;
}
FileModificationService.getInstance().preparePsiElementsForWrite(toAnnotate);
for (PsiMethod psiMethod : toAnnotate) {
String annotation = manager.getDefaultAnnotation(myNullability, psiMethod);
AddAnnotationPsiFix fix = new AddAnnotationPsiFix(annotation, psiMethod, myAnnotationsToRemove);
fix.invoke(psiMethod.getProject(), psiMethod.getContainingFile(), psiMethod, psiMethod);
}
UndoUtil.markPsiFileForUndo(method.getContainingFile());
}
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
return IntentionPreviewInfo.EMPTY;
}
@Override
public @NotNull String getName() {
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.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
@PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
private final String notNullToNullProblemMessage;
@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
@PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
String notNullToNullProblem() {
return notNullToNullProblemMessage;
}
@NotNull
@PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
String nullToNotNullProblem() {
return nullToNotNullProblemMessage;
}
@NotNull
@PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
String complexProblem() {
return complexProblem;
}
}
}