@NotNull
  private static PsiSubstitutor replaceVariables(Collection<InferenceVariable> inferenceVariables) {
    final List<InferenceVariable> targetVars = new ArrayList<InferenceVariable>();
    PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
    final InferenceVariable[] oldVars =
        inferenceVariables.toArray(new InferenceVariable[inferenceVariables.size()]);
    for (InferenceVariable variable : oldVars) {
      final InferenceVariable newVariable =
          new InferenceVariable(
              variable.getCallContext(), variable.getParameter(), variable.getName());
      substitutor =
          substitutor.put(
              variable,
              JavaPsiFacade.getElementFactory(variable.getProject()).createType(newVariable));
      targetVars.add(newVariable);
      if (variable.isThrownBound()) {
        newVariable.setThrownBound();
      }
    }

    for (int i = 0; i < targetVars.size(); i++) {
      InferenceVariable var = targetVars.get(i);
      for (InferenceBound boundType : InferenceBound.values()) {
        for (PsiType bound : oldVars[i].getBounds(boundType)) {
          var.addBound(substitutor.substitute(bound), boundType, null);
        }
      }
    }
    return substitutor;
  }
  public static List<List<InferenceVariable>> resolveOrder(
      Collection<InferenceVariable> vars, InferenceSession session) {
    Map<InferenceVariable, InferenceGraphNode<InferenceVariable>> nodes =
        new HashMap<InferenceVariable, InferenceGraphNode<InferenceVariable>>();
    for (InferenceVariable var : vars) {
      nodes.put(var, new InferenceGraphNode<InferenceVariable>(var));
    }

    for (InferenceVariable var : vars) {
      if (var.getInstantiation() != PsiType.NULL) continue;
      final InferenceGraphNode<InferenceVariable> node = nodes.get(var);
      final Set<InferenceVariable> dependencies = var.getDependencies(session);
      for (InferenceVariable dependentVariable : dependencies) {
        final InferenceGraphNode<InferenceVariable> dependency = nodes.get(dependentVariable);
        if (dependency != null) {
          node.addDependency(dependency);
        }
      }
    }
    final ArrayList<InferenceGraphNode<InferenceVariable>> acyclicNodes = initNodes(nodes.values());
    return ContainerUtil.map(
        acyclicNodes,
        new Function<InferenceGraphNode<InferenceVariable>, List<InferenceVariable>>() {
          @Override
          public List<InferenceVariable> fun(InferenceGraphNode<InferenceVariable> node) {
            return node.getValue();
          }
        });
  }
  private static CompoundInitialState createState(InferenceSession topLevelSession) {
    final PsiSubstitutor topInferenceSubstitutor =
        replaceVariables(topLevelSession.getInferenceVariables());
    final Map<PsiElement, InitialInferenceState> nestedStates =
        new LinkedHashMap<PsiElement, InitialInferenceState>();

    final InferenceSessionContainer copy =
        new InferenceSessionContainer() {
          @Override
          public PsiSubstitutor findNestedSubstitutor(
              PsiElement arg, @Nullable PsiSubstitutor defaultSession) {
            // for the case foo(bar(a -> m())): top level inference won't touch lambda "a -> m()"
            // for the case foo(a -> bar(b -> m())): top level inference would go till nested lambda
            // "b -> m()" and the state from top level could be found here by "bar(b -> m())"
            // but proceeding with additional constraints from saved point would produce new
            // expression constraints with different inference variables (could be found in
            // myNestedSessions)
            // which won't be found in the system if we won't reject stored sessions in such cases
            final PsiSubstitutor substitutor = super.findNestedSubstitutor(arg, null);
            if (substitutor != null) {
              return substitutor;
            }

            final InitialInferenceState state =
                nestedStates.get(PsiTreeUtil.getParentOfType(arg, PsiCall.class));
            if (state != null) {
              return state.getInferenceSubstitutor();
            }
            return super.findNestedSubstitutor(arg, defaultSession);
          }
        };
    final Map<PsiElement, InferenceSession> nestedSessions =
        topLevelSession.getInferenceSessionContainer().myNestedSessions;
    for (Map.Entry<PsiElement, InferenceSession> entry : nestedSessions.entrySet()) {
      nestedStates.put(
          entry.getKey(),
          entry
              .getValue()
              .createInitialState(
                  copy, topLevelSession.getInferenceVariables(), topInferenceSubstitutor));
    }

    PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
    for (InferenceVariable variable : topLevelSession.getInferenceVariables()) {
      final PsiType instantiation = variable.getInstantiation();
      if (instantiation != PsiType.NULL) {
        final PsiClass psiClass =
            PsiUtil.resolveClassInClassTypeOnly(topInferenceSubstitutor.substitute(variable));
        if (psiClass instanceof InferenceVariable) {
          substitutor = substitutor.put((PsiTypeParameter) psiClass, instantiation);
        }
      }
    }

    return new CompoundInitialState(substitutor, nestedStates);
  }
 public boolean hasCaptureConstraints(Iterable<InferenceVariable> variables) {
   for (InferenceVariable variable : variables) {
     final PsiTypeParameter parameter = variable.getParameter();
     for (Pair<PsiTypeParameter[], PsiClassType> capture : myCaptures) {
       for (PsiTypeParameter typeParameter : capture.first) {
         if (parameter == typeParameter) {
           return true;
         }
       }
     }
   }
   return false;
 }
 public void forgetCaptures(List<InferenceVariable> variables) {
   for (InferenceVariable variable : variables) {
     final PsiTypeParameter parameter = variable.getParameter();
     for (Iterator<Pair<PsiTypeParameter[], PsiClassType>> iterator = myCaptures.iterator();
         iterator.hasNext(); ) {
       Pair<PsiTypeParameter[], PsiClassType> capture = iterator.next();
       for (PsiTypeParameter typeParameter : capture.first) {
         if (parameter == typeParameter) {
           iterator.remove();
           break;
         }
       }
     }
   }
 }
  boolean isFullyIncorporated() {
    boolean needFurtherIncorporation = false;
    for (InferenceVariable inferenceVariable : mySession.getInferenceVariables()) {
      if (inferenceVariable.getInstantiation() != PsiType.NULL) continue;
      final List<PsiType> eqBounds = inferenceVariable.getBounds(InferenceBound.EQ);
      final List<PsiType> upperBounds = inferenceVariable.getBounds(InferenceBound.UPPER);
      final List<PsiType> lowerBounds = inferenceVariable.getBounds(InferenceBound.LOWER);
      needFurtherIncorporation |=
          crossVariables(inferenceVariable, upperBounds, lowerBounds, InferenceBound.LOWER);
      needFurtherIncorporation |=
          crossVariables(inferenceVariable, lowerBounds, upperBounds, InferenceBound.UPPER);

      needFurtherIncorporation |= eqCrossVariables(inferenceVariable, eqBounds);
    }
    return !needFurtherIncorporation;
  }
 public void collectCaptureDependencies(
     InferenceVariable variable, Set<InferenceVariable> dependencies) {
   final PsiTypeParameter parameter = variable.getParameter();
   for (Pair<PsiTypeParameter[], PsiClassType> capture : myCaptures) {
     for (PsiTypeParameter typeParameter : capture.first) {
       if (typeParameter == parameter) {
         collectAllVariablesOnBothSides(dependencies, capture);
         break;
       }
     }
   }
 }
 /** a = b imply every bound of a matches a bound of b and vice versa */
 private boolean eqCrossVariables(InferenceVariable inferenceVariable, List<PsiType> eqBounds) {
   boolean needFurtherIncorporation = false;
   for (PsiType eqBound : eqBounds) {
     final InferenceVariable inferenceVar = mySession.getInferenceVariable(eqBound);
     if (inferenceVar != null) {
       for (InferenceBound inferenceBound : InferenceBound.values()) {
         for (PsiType bound : inferenceVariable.getBounds(inferenceBound)) {
           if (mySession.getInferenceVariable(bound) != inferenceVar) {
             needFurtherIncorporation |= inferenceVar.addBound(bound, inferenceBound);
           }
         }
         for (PsiType bound : inferenceVar.getBounds(inferenceBound)) {
           if (mySession.getInferenceVariable(bound) != inferenceVariable) {
             needFurtherIncorporation |= inferenceVariable.addBound(bound, inferenceBound);
           }
         }
       }
     }
   }
   return needFurtherIncorporation;
 }
  /** a < b & S <: a & b <: T imply S <: b & a <: T */
  private boolean crossVariables(
      InferenceVariable inferenceVariable,
      List<PsiType> upperBounds,
      List<PsiType> lowerBounds,
      InferenceBound inferenceBound) {

    final InferenceBound oppositeBound =
        inferenceBound == InferenceBound.LOWER ? InferenceBound.UPPER : InferenceBound.LOWER;
    boolean result = false;
    for (PsiType upperBound : upperBounds) {
      final InferenceVariable inferenceVar = mySession.getInferenceVariable(upperBound);
      if (inferenceVar != null && inferenceVariable != inferenceVar) {

        for (PsiType lowerBound : lowerBounds) {
          result |= inferenceVar.addBound(lowerBound, inferenceBound);
        }

        for (PsiType varUpperBound : inferenceVar.getBounds(oppositeBound)) {
          result |= inferenceVariable.addBound(varUpperBound, oppositeBound);
        }
      }
    }
    return result;
  }
  public boolean incorporate() {
    final Collection<InferenceVariable> inferenceVariables = mySession.getInferenceVariables();
    final PsiSubstitutor substitutor =
        mySession.retrieveNonPrimitiveEqualsBounds(inferenceVariables);
    for (InferenceVariable inferenceVariable : inferenceVariables) {
      if (inferenceVariable.getInstantiation() != PsiType.NULL) continue;
      final List<PsiType> eqBounds = inferenceVariable.getBounds(InferenceBound.EQ);
      final List<PsiType> upperBounds = inferenceVariable.getBounds(InferenceBound.UPPER);
      final List<PsiType> lowerBounds = inferenceVariable.getBounds(InferenceBound.LOWER);

      eqEq(eqBounds);

      upDown(lowerBounds, upperBounds, substitutor);
      upDown(eqBounds, upperBounds, substitutor);
      upDown(lowerBounds, eqBounds, substitutor);

      upUp(upperBounds);
    }

    for (Pair<PsiTypeParameter[], PsiClassType> capture : myCaptures) {
      final PsiClassType right = capture.second;
      final PsiClass gClass = right.resolve();
      LOG.assertTrue(gClass != null);
      final PsiTypeParameter[] parameters = capture.first;
      PsiType[] typeArgs = right.getParameters();
      if (parameters.length != typeArgs.length) continue;
      for (int i = 0; i < typeArgs.length; i++) {
        PsiType aType = typeArgs[i];
        if (aType instanceof PsiCapturedWildcardType) {
          aType = ((PsiCapturedWildcardType) aType).getWildcard();
        }
        final InferenceVariable inferenceVariable = mySession.getInferenceVariable(parameters[i]);
        LOG.assertTrue(inferenceVariable != null);

        final List<PsiType> eqBounds = inferenceVariable.getBounds(InferenceBound.EQ);
        final List<PsiType> upperBounds = inferenceVariable.getBounds(InferenceBound.UPPER);
        final List<PsiType> lowerBounds = inferenceVariable.getBounds(InferenceBound.LOWER);

        if (aType instanceof PsiWildcardType) {

          for (PsiType eqBound : eqBounds) {
            if (!isInferenceVariableOrFreshTypeParameter(eqBound)) return false;
          }

          final PsiClassType[] paramBounds = inferenceVariable.getParameter().getExtendsListTypes();

          PsiType glb = null;
          for (PsiClassType paramBound : paramBounds) {
            if (glb == null) {
              glb = paramBound;
            } else {
              glb = GenericsUtil.getGreatestLowerBound(glb, paramBound);
            }
          }

          if (!((PsiWildcardType) aType).isBounded()) {

            for (PsiType upperBound : upperBounds) {
              if (glb != null && mySession.getInferenceVariable(upperBound) == null) {
                addConstraint(
                    new StrictSubtypingConstraint(
                        upperBound, mySession.substituteWithInferenceVariables(glb)));
              }
            }

            for (PsiType lowerBound : lowerBounds) {
              if (isInferenceVariableOrFreshTypeParameter(lowerBound)) return false;
            }

          } else if (((PsiWildcardType) aType).isExtends()) {

            final PsiType extendsBound = ((PsiWildcardType) aType).getExtendsBound();

            for (PsiType upperBound : upperBounds) {
              if (mySession.getInferenceVariable(upperBound) == null) {
                if (paramBounds.length == 1
                        && paramBounds[0].equalsToText(CommonClassNames.JAVA_LANG_OBJECT)
                    || paramBounds.length == 0) {
                  addConstraint(new StrictSubtypingConstraint(upperBound, extendsBound));
                } else if (extendsBound.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)
                    && glb != null) {
                  addConstraint(
                      new StrictSubtypingConstraint(
                          upperBound, mySession.substituteWithInferenceVariables(glb)));
                }
              }
            }

            for (PsiType lowerBound : lowerBounds) {
              if (isInferenceVariableOrFreshTypeParameter(lowerBound)) return false;
            }

          } else {
            LOG.assertTrue(((PsiWildcardType) aType).isSuper());
            final PsiType superBound = ((PsiWildcardType) aType).getSuperBound();

            for (PsiType upperBound : upperBounds) {
              if (glb != null && mySession.getInferenceVariable(upperBound) == null) {
                addConstraint(
                    new StrictSubtypingConstraint(
                        mySession.substituteWithInferenceVariables(glb), upperBound));
              }
            }

            for (PsiType lowerBound : lowerBounds) {
              if (mySession.getInferenceVariable(lowerBound) == null) {
                addConstraint(new StrictSubtypingConstraint(lowerBound, superBound));
              }
            }
          }
        } else {
          inferenceVariable.addBound(aType, InferenceBound.EQ);
        }
      }
    }
    return true;
  }