@Contract("_, !null -> !null")
 public PsiSubstitutor findNestedSubstitutor(
     PsiElement arg, @Nullable PsiSubstitutor defaultSession) {
   InferenceSession session =
       myNestedSessions.get(PsiTreeUtil.getParentOfType(arg, PsiCall.class));
   return session == null ? defaultSession : session.getInferenceSubstitution();
 }
  public Set<InferenceVariable> getDependencies(InferenceSession session) {
    final Set<InferenceVariable> dependencies = new LinkedHashSet<InferenceVariable>();
    for (List<PsiType> boundTypes : myBounds.values()) {
      if (boundTypes != null) {
        for (PsiType bound : boundTypes) {
          session.collectDependencies(bound, dependencies);
        }
      }
    }

    if (!session.hasCapture(this) && dependencies.isEmpty()) {
      return dependencies;
    }

    if (!session.hasCapture(this)) {
      return dependencies;
    }

    for (Iterator<InferenceVariable> iterator = dependencies.iterator(); iterator.hasNext(); ) {
      if (!session.hasCapture(iterator.next())) {
        iterator.remove();
      }
    }
    session.collectCaptureDependencies(this, dependencies);
    return dependencies;
  }
  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);
  }
 protected void collectAllVariablesOnBothSides(
     Set<InferenceVariable> dependencies, Pair<PsiTypeParameter[], PsiClassType> capture) {
   mySession.collectDependencies(capture.second, dependencies);
   for (PsiTypeParameter psiTypeParameter : capture.first) {
     final InferenceVariable var = mySession.getInferenceVariable(psiTypeParameter);
     if (var != null) {
       dependencies.add(var);
     }
   }
 }
 void registerNestedSession(
     InferenceSession session, PsiType returnType, PsiExpression returnExpression) {
   final PsiSubstitutor callSession =
       findNestedSubstitutor(((PsiCallExpression) returnExpression).getArgumentList(), null);
   if (callSession == null) {
     final InferenceSession inferenceSession =
         ExpressionCompatibilityConstraint.reduceExpressionCompatibilityConstraint(
             session, returnExpression, returnType);
     if (inferenceSession != null && inferenceSession != session) {
       registerNestedSession(inferenceSession);
       session.propagateVariables(
           inferenceSession.getInferenceVariables(),
           inferenceSession.getRestoreNameSubstitution());
     }
   }
 }
 private static Boolean isInferenceVariableOrFreshTypeParameter(PsiType eqBound) {
   final PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(eqBound);
   if (psiClass instanceof InferenceVariable
       || psiClass instanceof PsiTypeParameter
           && InferenceSession.isFreshVariable((PsiTypeParameter) psiClass)) return true;
   return false;
 }
 public boolean hasInstantiation(InferenceSession session) {
   List<PsiType> bounds = getBounds(InferenceBound.EQ);
   if (bounds != null) {
     for (PsiType bound : bounds) {
       if (session.isProperType(bound)) return true;
     }
   }
   return false;
 }
 /** 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;
 }
  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;
  }
 /**
  * If two bounds have the form α <: S and α <: T, and if for some generic class or interface, G,
  * there exists a supertype (4.10) of S of the form G<S1, ..., Sn> and a supertype of T of the
  * form G<T1, ..., Tn>, then for all i, 1 ≤ i ≤ n, if Si and Ti are types (not wildcards), the
  * constraint ⟨Si = Ti⟩ is implied.
  */
 private boolean upUp(List<PsiType> upperBounds) {
   return InferenceSession.findParameterizationOfTheSameGenericClass(
           upperBounds,
           new Processor<Pair<PsiType, PsiType>>() {
             @Override
             public boolean process(Pair<PsiType, PsiType> pair) {
               final PsiType sType = pair.first;
               final PsiType tType = pair.second;
               if (!(sType instanceof PsiWildcardType)
                   && !(tType instanceof PsiWildcardType)
                   && sType != null
                   && tType != null) {
                 addConstraint(new TypeEqualityConstraint(sType, tType));
               }
               return true;
             }
           })
       != null;
 }
  /** 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;
  }
  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);
  }
 public void registerNestedSession(InferenceSession session) {
   myNestedSessions.put(session.getContext(), session);
   myNestedSessions.putAll(session.getInferenceSessionContainer().myNestedSessions);
 }
  private static PsiSubstitutor inferNested(
      final PsiTypeParameter[] typeParameters,
      @NotNull final PsiParameter[] parameters,
      @NotNull final PsiExpression[] arguments,
      final PsiSubstitutor partialSubstitutor,
      @NotNull final PsiCall parent,
      @NotNull final ParameterTypeInferencePolicy policy,
      final MethodCandidateInfo.CurrentCandidateProperties properties,
      final InferenceSession parentSession) {
    final CompoundInitialState compoundInitialState = createState(parentSession);
    InitialInferenceState initialInferenceState = compoundInitialState.getInitialState(parent);
    if (initialInferenceState != null) {
      final InferenceSession childSession = new InferenceSession(initialInferenceState);
      final List<String> errorMessages = parentSession.getIncompatibleErrorMessages();
      if (errorMessages != null) {
        return childSession.prepareSubstitution();
      }
      return childSession.collectAdditionalAndInfer(
          parameters, arguments, properties, compoundInitialState.getInitialSubstitutor());
    }

    // we do not investigate lambda return expressions when lambda's return type is already inferred
    // (proper)
    // this way all calls from lambda's return expressions won't appear in nested sessions
    else {
      PsiElement gParent = PsiUtil.skipParenthesizedExprUp(parent.getParent());
      // find the nearest parent which appears in the map and start inference with a provided target
      // type for a nested lambda
      while (true) {
        if (gParent instanceof PsiReturnStatement) { // process code block lambda
          final PsiElement returnContainer = gParent.getParent();
          if (returnContainer instanceof PsiCodeBlock) {
            gParent = returnContainer.getParent();
          }
        }
        if (gParent instanceof PsiLambdaExpression) {
          final PsiCall call = PsiTreeUtil.getParentOfType(gParent, PsiCall.class);
          if (call != null) {
            initialInferenceState = compoundInitialState.getInitialState(call);
            if (initialInferenceState != null) {
              final int idx = LambdaUtil.getLambdaIdx(call.getArgumentList(), gParent);
              final PsiMethod method = call.resolveMethod();
              if (method != null && idx > -1) {
                final PsiType parameterType =
                    PsiTypesUtil.getParameterType(
                        method.getParameterList().getParameters(), idx, true);
                final PsiType parameterTypeInTermsOfSession =
                    initialInferenceState.getInferenceSubstitutor().substitute(parameterType);
                final PsiType lambdaTargetType =
                    compoundInitialState
                        .getInitialSubstitutor()
                        .substitute(parameterTypeInTermsOfSession);
                return LambdaUtil.performWithLambdaTargetType(
                    (PsiLambdaExpression) gParent,
                    lambdaTargetType,
                    new Producer<PsiSubstitutor>() {
                      @Nullable
                      @Override
                      public PsiSubstitutor produce() {
                        if (call.equals(PsiTreeUtil.getParentOfType(parent, PsiCall.class, true))) {
                          // parent was mentioned in the top inference session
                          // just proceed with the target type
                          final InferenceSession inferenceSession =
                              new InferenceSession(
                                  typeParameters,
                                  partialSubstitutor,
                                  parent.getManager(),
                                  parent,
                                  policy);
                          inferenceSession.initExpressionConstraints(parameters, arguments, parent);
                          return inferenceSession.infer(parameters, arguments, parent);
                        }
                        // one of the grand parents were found in the top inference session
                        // start from it as it is the top level call
                        final InferenceSession sessionInsideLambda =
                            startTopLevelInference(call, policy);
                        return inferNested(
                            typeParameters,
                            parameters,
                            arguments,
                            partialSubstitutor,
                            parent,
                            policy,
                            properties,
                            sessionInsideLambda);
                      }
                    });
              }
            } else {
              gParent = PsiUtil.skipParenthesizedExprUp(call.getParent());
              continue;
            }
          }
        }
        break;
      }
    }
    return null;
  }
  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;
  }
 private void addConstraint(ConstraintFormula constraint) {
   mySession.addConstraint(constraint);
 }