@Override
  public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
    return new JavaElementVisitor() {
      @Override
      public void visitMethod(@NotNull PsiMethod method) {
        if (INVOKE.methodMatches(method)) {
          check(method);
        }
        if (method.getReturnType() instanceof PsiClassType) {
          PsiParameterList list = method.getParameterList();
          if (list.getParametersCount() == 3) {
            PsiParameter[] parameters = list.getParameters();
            if (EntryStream.zip(parameters, HANDLER_ARGUMENT_TYPES).allMatch((p, t) -> TypeUtils.typeEquals(t, p.getType())) &&
                SyntaxTraverser.psiTraverser(holder.getFile()).filter(PsiMethodReferenceExpression.class)
                  .filter(ref -> ref.isReferenceTo(method) && TypeUtils.typeEquals(HANDLER_CLASS, ref.getFunctionalInterfaceType()))
                  .first() != null) {
              check(method);
            }
          }
        }
      }

      @Override
      public void visitLambdaExpression(@NotNull PsiLambdaExpression lambda) {
        if (lambda.getParameterList().getParametersCount() != 3) return;
        PsiType type = lambda.getFunctionalInterfaceType();
        if (!InheritanceUtil.isInheritor(type, HANDLER_CLASS)) return;
        check(lambda);
      }

      private void check(PsiParameterListOwner method) {
        PsiElement body = method.getBody();
        if (body == null) return;
        PsiParameter methodParameter = method.getParameterList().getParameter(1);
        if (methodParameter == null) return;
        if (body instanceof PsiCodeBlock && !ControlFlowUtils.containsReturn(body)) return;
        if (!VariableAccessUtils.variableIsUsed(methodParameter, body)) {
          holder.registerProblem(Objects.requireNonNull(methodParameter.getNameIdentifier()),
                                 JavaAnalysisBundle.message("suspicious.invocation.handler.implementation.method.unused.message"));
          return;
        }
        InvocationHandlerAnalysisRunner runner = new InvocationHandlerAnalysisRunner(holder, body, methodParameter);
        DfaVariableValue methodName = runner.myDfaMethodName;
        if (methodName == null) return;
        Map<String, Map<PsiExpression, DfType>> returnMap = new TreeMap<>();
        RunnerResult result = runner.analyzeMethod(body, new JavaDfaListener() {
          @Override
          public void beforeValueReturn(@NotNull DfaValue value,
                                        @Nullable PsiExpression expression,
                                        @NotNull PsiElement context,
                                        @NotNull DfaMemoryState state) {
            if (context != method) return;
            String name = state.getDfType(methodName).getConstantOfType(String.class);
            if (name == null) {
              runner.cancel();
              return;
            }
            DfType type = state.getDfType(value);
            if (type instanceof DfPrimitiveType) {
              type = DfTypes.typedObject(((DfPrimitiveType)type).getPsiType().getBoxedType(body), Nullability.NOT_NULL);
            }
            returnMap.computeIfAbsent(name, k -> new HashMap<>()).merge(expression, type, DfType::join);
          }
        });
        if (result != RunnerResult.OK) return;
        Set<PsiExpression> reportedAnchors = new HashSet<>();
        returnMap.forEach((name, map) -> {
          DfType reduced = map.values().stream().reduce(DfType.BOTTOM, DfType::join);
          PsiType wantedType = getWantedType(body, name);
          if (wantedType == null) return;
          TypeConstraint wantedConstraint = TypeConstraints.exact(wantedType);
          if (reduced.meet(wantedConstraint.asDfType().meet(DfTypes.NOT_NULL_OBJECT)) != DfType.BOTTOM &&
              StreamEx.ofValues(map).map(DfaNullability::fromDfType).anyMatch(
                n -> n != DfaNullability.NULLABLE && n != DfaNullability.NULL)) {
            return;
          }
          map.forEach((expression, type) -> {
            if (reportedAnchors.contains(expression)) return;
            String message = null;
            TypeConstraint constraint = TypeConstraint.fromDfType(type);
            if (wantedConstraint.meet(constraint) == TypeConstraints.BOTTOM) {
              message = JavaAnalysisBundle.message("suspicious.invocation.handler.implementation.type.mismatch.message", name,
                                                   wantedConstraint.getPresentationText(null), constraint.getPresentationText(null));
            }
            DfaNullability nullability = DfaNullability.fromDfType(type);
            if (nullability == DfaNullability.NULL) {
              message = name.equals("toString") ?
                        JavaAnalysisBundle.message("suspicious.invocation.handler.implementation.null.returned.for.toString.message") :
                        JavaAnalysisBundle.message("suspicious.invocation.handler.implementation.null.returned.message", name);
            }
            if (message != null) {
              reportedAnchors.add(expression);
              holder.registerProblem(expression, message);
            }
          });
        });
      }

      private static @Nullable PsiType getWantedType(PsiElement body, String name) {
        return switch (name) {
          case "equals" -> PsiTypes.booleanType().getBoxedType(body);
          case "hashCode" -> PsiTypes.intType().getBoxedType(body);
          case "toString" -> PsiType.getJavaLangString(body.getManager(), body.getResolveScope());
          default -> null;
        };
      }
    };
  }
@Override
  public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly)