static PsiSubstitutor infer(PsiTypeParameter @NotNull [] typeParameters,
                              PsiParameter @NotNull [] parameters,
                              PsiExpression @NotNull [] arguments,
                              @NotNull PsiSubstitutor partialSubstitutor,
                              @NotNull PsiElement parent,
                              @NotNull ParameterTypeInferencePolicy policy,
                              @Nullable MethodCandidateInfo currentMethod) {
    final PsiExpressionList argumentList = InferenceSession.getArgumentList(parent);
    class NoContainer {
      PsiSubstitutor infer(boolean prohibitCaching) {
        final InferenceSession inferenceSession = new InferenceSession(typeParameters, partialSubstitutor, parent.getManager(), parent, policy);
        inferenceSession.initExpressionConstraints(parameters, arguments,
                                                   currentMethod != null ? currentMethod.getElement() : null,
                                                   currentMethod != null && currentMethod.isVarargs());
        return inferenceSession.performGuardedInference(parameters, arguments, parent, currentMethod, PsiSubstitutor.EMPTY,
                                                        prohibitCaching);
      }
    }
    if (parent instanceof PsiCall) {
      //overload resolution can't depend on outer call => should not traverse to top
      if (//in order to avoid caching of candidates' 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.isOverloadCheck(argumentList)) {
        final PsiCall topLevelCall = PsiResolveHelper.ourGraphGuard.doPreventingRecursion(parent, false,
                                                                                          () -> {
                                                                                            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() ||
              !policy.equals(DefaultParameterTypeInferencePolicy.INSTANCE)) {
            session = startTopLevelInference(topLevelCall, policy, currentMethod);
          }
          else {
            session = CachedValuesManager.getCachedValue(topLevelCall,
                                                         () -> new CachedValueProvider.Result<>(
                                                           startTopLevelInference(topLevelCall, DefaultParameterTypeInferencePolicy.INSTANCE, null),
                                                           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, currentMethod);
                    break;
                  }
                }
              }
            }
          }

          if (session != null) {
            final PsiSubstitutor childSubstitutor = inferNested(parameters, arguments, (PsiCall)parent, currentMethod, session);
            if (childSubstitutor != null) return childSubstitutor;

            return new NoContainer().infer(true);
          }
          else if (topLevelCall instanceof PsiMethodCallExpression) {
            return new InferenceSession(typeParameters, partialSubstitutor, parent.getManager(), parent, policy).prepareSubstitution();
          }
        }
      }
    }

    return new NoContainer().infer(false);
  }
static PsiSubstitutor infer(PsiTypeParameter @NotNull [] typeParameters,
                              PsiParameter @NotNull [] parameters,
                              PsiExpression @NotNull [] arguments,
                              @NotNull PsiSubstitutor partialSubstitutor,
                              @NotNull PsiElement parent,
                              @NotNull ParameterTypeInferencePolicy policy,
                              @Nullable MethodCandidateInfo currentMethod)