@Override
    @Nullable
    public PsiType fun(final PsiMethodCallExpression call) {
      PsiReferenceExpression methodExpression = call.getMethodExpression();
      PsiType theOnly = null;
      final JavaResolveResult[] results = methodExpression.multiResolve(false);
      LanguageLevel languageLevel = PsiUtil.getLanguageLevel(call);

      final PsiElement callParent = PsiUtil.skipParenthesizedExprUp(call.getParent());
      final PsiExpressionList parentArgList;
      if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
        parentArgList =
            callParent instanceof PsiConditionalExpression
                    && !PsiPolyExpressionUtil.isPolyExpression((PsiExpression) callParent)
                ? null
                : PsiTreeUtil.getParentOfType(call, PsiExpressionList.class);
      } else {
        parentArgList = null;
      }
      final MethodCandidateInfo.CurrentCandidateProperties properties =
          MethodCandidateInfo.getCurrentMethod(parentArgList);
      final boolean genericMethodCall =
          properties != null && properties.getInfo().isToInferApplicability();

      for (int i = 0; i < results.length; i++) {
        final JavaResolveResult candidateInfo = results[i];

        if (genericMethodCall
            && PsiPolyExpressionUtil.isMethodCallPolyExpression(
                call, (PsiMethod) candidateInfo.getElement())) {
          if (callParent instanceof PsiAssignmentExpression) {
            return null;
          }
          LOG.error("poly expression evaluation during overload resolution");
        }

        final PsiType type = getResultType(call, methodExpression, candidateInfo, languageLevel);
        if (type == null) {
          return null;
        }

        if (i == 0) {
          theOnly = type;
        } else if (!theOnly.equals(type)) {
          return null;
        }
      }

      return PsiClassImplUtil.correctType(theOnly, call.getResolveScope());
    }
  static PsiSubstitutor infer(
      @NotNull PsiTypeParameter[] typeParameters,
      @NotNull PsiParameter[] parameters,
      @NotNull PsiExpression[] arguments,
      @NotNull PsiSubstitutor partialSubstitutor,
      @NotNull final PsiElement parent,
      @NotNull final ParameterTypeInferencePolicy policy) {
    if (parent instanceof PsiCall) {
      final PsiExpressionList argumentList = ((PsiCall) parent).getArgumentList();
      final MethodCandidateInfo.CurrentCandidateProperties properties =
          MethodCandidateInfo.getCurrentMethod(argumentList);
      // overload resolution can't depend on outer call => should not traverse to top
      if (properties != null
          && !properties.isApplicabilityCheck()
          &&
          // in order to to avoid caching of candidates's errors on parent (!) , so check for
          // overload resolution is left here
          // But overload resolution can depend on type of lambda parameter. As it can't depend on
          // lambda body,
          // traversing down would stop at lambda level and won't take into account overloaded
          // method
          !MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argumentList)) {
        final PsiCall topLevelCall =
            PsiResolveHelper.ourGraphGuard.doPreventingRecursion(
                parent,
                false,
                new Computable<PsiCall>() {
                  @Override
                  public PsiCall compute() {
                    if (parent instanceof PsiExpression
                        && !PsiPolyExpressionUtil.isPolyExpression((PsiExpression) parent)) {
                      return null;
                    }
                    return LambdaUtil.treeWalkUp(parent);
                  }
                });
        if (topLevelCall != null) {

          InferenceSession session;
          if (MethodCandidateInfo.isOverloadCheck()
              || !PsiDiamondType.ourDiamondGuard.currentStack().isEmpty()
              || LambdaUtil.isLambdaParameterCheck()) {
            session = startTopLevelInference(topLevelCall, policy);
          } else {
            session =
                CachedValuesManager.getCachedValue(
                    topLevelCall,
                    new CachedValueProvider<InferenceSession>() {
                      @Nullable
                      @Override
                      public Result<InferenceSession> compute() {
                        return new Result<InferenceSession>(
                            startTopLevelInference(topLevelCall, policy),
                            PsiModificationTracker.MODIFICATION_COUNT);
                      }
                    });

            if (session != null) {
              // reject cached top level session if it was based on wrong candidate: check nested
              // session if candidate (it's type parameters) are the same
              // such situations are avoided when overload resolution is performed
              // (MethodCandidateInfo.isOverloadCheck above)
              // but situations when client code iterates through
              // PsiResolveHelper.getReferencedMethodCandidates or similar are impossible to guess
              final Map<PsiElement, InferenceSession> sessions =
                  session.getInferenceSessionContainer().myNestedSessions;
              final InferenceSession childSession = sessions.get(parent);
              if (childSession != null) {
                for (PsiTypeParameter parameter : typeParameters) {
                  if (!childSession
                      .getInferenceSubstitution()
                      .getSubstitutionMap()
                      .containsKey(parameter)) {
                    session = startTopLevelInference(topLevelCall, policy);
                    break;
                  }
                }
              }
            }
          }

          if (session != null) {
            final PsiSubstitutor childSubstitutor =
                inferNested(
                    typeParameters,
                    parameters,
                    arguments,
                    partialSubstitutor,
                    (PsiCall) parent,
                    policy,
                    properties,
                    session);
            if (childSubstitutor != null) return childSubstitutor;
          } else if (topLevelCall instanceof PsiMethodCallExpression) {
            return new InferenceSession(
                    typeParameters, partialSubstitutor, parent.getManager(), parent, policy)
                .prepareSubstitution();
          }
        }
      }
    }

    final InferenceSession inferenceSession =
        new InferenceSession(
            typeParameters, partialSubstitutor, parent.getManager(), parent, policy);
    inferenceSession.initExpressionConstraints(parameters, arguments, parent);
    return inferenceSession.infer(parameters, arguments, parent);
  }