PsiElementVisitor (@NotNull final ProblemsHolder holder, boolean isOnTheFly) { final PsiFile file = holder.getFile(); if (!PsiUtil.isLanguageLevel5OrHigher(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 visitMethodReferenceExpression(@NotNull PsiMethodReferenceExpression expression) { checkMethodReference(expression, holder); JavaResolveResult result = expression.advancedResolve(false); PsiElement target = result.getElement(); if (target instanceof PsiMethod) { checkCollectionNullityOnAssignment(expression, LambdaUtil.getFunctionalInterfaceReturnType(expression), result.getSubstitutor().substitute(((PsiMethod)target).getReturnType())); } } @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 = annotated.isDeclaredNotNull ? manager.getDefaultNotNull() : manager.getDefaultNullable(); final List<String> annoToRemove = annotated.isDeclaredNotNull ? nullables : notNulls; if (!checkNonStandardAnnotations(field, annotated, manager, anno, holder)) return; checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder); checkConstructorParameters(field, annotated, manager, anno, annoToRemove, holder); } } @Override public void visitParameter(@NotNull PsiParameter parameter) { check(parameter, holder, parameter.getType()); } @Override public void visitAnnotation(@NotNull PsiAnnotation annotation) { String qualifiedName = annotation.getQualifiedName(); Optional<Nullability> nullabilityOptional = manager.getAnnotationNullability(qualifiedName); if (nullabilityOptional.isEmpty()) return; Nullability nullability = nullabilityOptional.get(); PsiType type = AnnotationUtil.getRelatedType(annotation); PsiAnnotationOwner owner = annotation.getOwner(); PsiModifierListOwner listOwner = owner instanceof PsiModifierList ? tryCast(((PsiModifierList)owner).getParent(), PsiModifierListOwner.class) : null; if (type instanceof PsiPrimitiveType) { PsiType targetType = listOwner instanceof PsiMethod ? ((PsiMethod)listOwner).getReturnType() : listOwner instanceof PsiVariable ? ((PsiVariable)listOwner).getType() : null; LocalQuickFix additionalFix = null; if (targetType instanceof PsiArrayType && targetType.getAnnotations().length == 0) { additionalFix = new MoveAnnotationToArrayFix(); } reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.primitive.type.annotation", LocalQuickFix.notNullElements(additionalFix)); } if (type instanceof PsiClassType) { PsiElement context = ((PsiClassType)type).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 { 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 (listOwner instanceof PsiMethod && ((PsiMethod)listOwner).isConstructor()) { reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.constructor"); } if (listOwner instanceof PsiEnumConstant) { reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.enum.constant"); } if ((listOwner instanceof PsiLocalVariable || listOwner instanceof PsiParameter && ((PsiParameter)listOwner).getDeclarationScope() instanceof PsiCatchSection) && !manager.canAnnotateLocals(qualifiedName)) { reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.local.variable"); } if (type instanceof PsiWildcardType && manager.isTypeUseAnnotationLocationRestricted(qualifiedName)) { reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.wildcard"); } if (owner instanceof PsiTypeParameter && manager.isTypeUseAnnotationLocationRestricted(qualifiedName)) { reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.at.type.parameter"); } if (listOwner instanceof PsiReceiverParameter && nullability != Nullability.NOT_NULL) { reportIncorrectLocation(holder, annotation, listOwner, "inspection.nullable.problems.receiver.annotation"); } checkOppositeAnnotationConflict(annotation, nullability); if (AnnotationUtil.NOT_NULL.equals(qualifiedName)) { PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue("exception"); if (value instanceof PsiClassObjectAccessExpression) { PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(((PsiClassObjectAccessExpression)value).getOperand().getType()); if (psiClass != null && !hasStringConstructor(psiClass)) { reportProblem(holder, value, "custom.exception.class.should.have.a.constructor"); } } } } 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) { oppositeAnno = manager.findExplicitNullabilityAnnotation( listOwner, ContainerUtil.filter(Nullability.values(), n -> n != nullability)); } if (oppositeAnno != null && Objects.equals(AnnotationUtil.getRelatedType(annotation), AnnotationUtil.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 && Objects.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 (DfaPsiUtil.getTypeNullability(JavaPsiFacade.getElementFactory(project).createType(typeParameters[i])) == Nullability.NOT_NULL) { Nullability typeNullability = DfaPsiUtil.getTypeNullability(type); if (typeNullability != Nullability.NOT_NULL && !(typeNullability == Nullability.UNKNOWN && type instanceof PsiWildcardType && !((PsiWildcardType)type).isExtends())) { String annotationToAdd = manager.getDefaultNotNull(); PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(annotationToAdd, element.getResolveScope()); AddTypeAnnotationFix fix = null; if (annotationClass != null && AnnotationTargetUtil.findAnnotationTarget(annotationClass, PsiAnnotation.TargetType.TYPE_USE) != null) { fix = new AddTypeAnnotationFix(annotationToAdd, manager.getNullables()); } reportProblem(holder, typeArgument, fix, "non.null.type.argument.is.expected"); } } } } } } 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) { checkCollectionNullityOnAssignment(expression.getOperationSign(), expression.getLExpression().getType(), expression.getRExpression()); } @Override public void visitLocalVariable(@NotNull PsiLocalVariable variable) { PsiIdentifier identifier = variable.getNameIdentifier(); if (identifier != null) { checkCollectionNullityOnAssignment(identifier, variable.getType(), variable.getInitializer()); } } @Override public void visitReturnStatement(@NotNull PsiReturnStatement statement) { PsiExpression returnValue = statement.getReturnValue(); if (returnValue == null) return; checkCollectionNullityOnAssignment(statement.getReturnValue(), PsiTypesUtil.getMethodReturnType(statement), returnValue); } @Override public void visitLambdaExpression(@NotNull PsiLambdaExpression lambda) { super.visitLambdaExpression(lambda); PsiElement body = lambda.getBody(); if (body instanceof PsiExpression) { checkCollectionNullityOnAssignment(body, LambdaUtil.getFunctionalInterfaceReturnType(lambda), (PsiExpression)body); } } @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))) { checkCollectionNullityOnAssignment(argument, substitutor.substitute(parameters[i].getType()), argument); } } } private void checkCollectionNullityOnAssignment(@NotNull PsiElement errorElement, @Nullable PsiType expectedType, @Nullable PsiExpression assignedExpression) { if (assignedExpression == null) return; checkCollectionNullityOnAssignment(errorElement, expectedType, assignedExpression.getType()); } private void checkCollectionNullityOnAssignment(@NotNull PsiElement errorElement, @Nullable PsiType expectedType, @Nullable PsiType assignedType) { if (isNullableNotNullCollectionConflict(expectedType, assignedType, file, new HashSet<>())) { reportProblem(holder, errorElement, "assigning.a.collection.of.nullable.elements"); } } }; }