@Override
  public boolean reduce(final InferenceSession session, List<? super ConstraintFormula> constraints) {
    if (!PsiPolyExpressionUtil.isPolyExpression(myExpression)) {
      return true;
    }
    PsiType myT = getCurrentType();
    if (myExpression instanceof PsiParenthesizedExpression) {
      constraints.add(new CheckedExceptionCompatibilityConstraint(((PsiParenthesizedExpression)myExpression).getExpression(), myT));
      return true;
    }
    if (myExpression instanceof PsiConditionalExpression) {
      final PsiExpression thenExpression = ((PsiConditionalExpression)myExpression).getThenExpression();
      if (thenExpression != null) {
        constraints.add(new CheckedExceptionCompatibilityConstraint(thenExpression, myT));
      }
      final PsiExpression elseExpression = ((PsiConditionalExpression)myExpression).getElseExpression();
      if (elseExpression != null) {
        constraints.add(new CheckedExceptionCompatibilityConstraint(elseExpression, myT));
      }
      return true;
    }
    if (myExpression instanceof PsiLambdaExpression || myExpression instanceof PsiMethodReferenceExpression) {
      if (!LambdaUtil.isFunctionalType(myT)) {
        session.registerIncompatibleErrorMessage(
          JavaPsiBundle.message("error.incompatible.type.not.a.functional.interface", session.getPresentableText(myT)));
        return false;
      }

      final PsiType groundTargetType = myExpression instanceof PsiLambdaExpression ? FunctionalInterfaceParameterizationUtil.getGroundTargetType(myT, (PsiLambdaExpression)myExpression, false) 
                                                                                   : FunctionalInterfaceParameterizationUtil.getGroundTargetType(myT);
      final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(groundTargetType);
      if (interfaceMethod == null) {
        session.registerIncompatibleErrorMessage(
          JavaPsiBundle.message("error.incompatible.type.no.valid.function.type.found", session.getPresentableText(myT)));
        return false;
      }

      final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, PsiUtil.resolveGenericsClassInType(groundTargetType));
      if (myExpression instanceof PsiLambdaExpression && !((PsiLambdaExpression)myExpression).hasFormalParameterTypes() ||
          myExpression instanceof PsiMethodReferenceExpression && !((PsiMethodReferenceExpression)myExpression).isExact()) {
        for (PsiParameter parameter : interfaceMethod.getParameterList().getParameters()) {
          final PsiType type = substitutor.substitute(parameter.getType());
          if (!session.isProperType(type)) {
            session.registerIncompatibleErrorMessage(
              JavaPsiBundle.message("error.incompatible.type.parameter.type.is.not.yet.inferred", session.getPresentableText(type)));
            return false;
          }
        }
      }

      final PsiType returnType = interfaceMethod.getReturnType();
      if (myExpression instanceof PsiLambdaExpression || !((PsiMethodReferenceExpression)myExpression).isExact()) {
        final PsiType type = substitutor.substitute(returnType);
        if (!session.isProperType(type)) {
          session.registerIncompatibleErrorMessage(
            JavaPsiBundle.message("error.incompatible.type.return.type.is.not.yet.inferred", session.getPresentableText(type)));
          return false;
        }
      }

      final List<PsiType>
        expectedThrownTypes = ContainerUtil.map(interfaceMethod.getThrowsList().getReferencedTypes(),
                                                type -> session.substituteWithInferenceVariables(substitutor.substitute(type)));
      final List<PsiType> expectedNonProperThrownTypes = new ArrayList<>();
      for (PsiType type : expectedThrownTypes) {
        if (!session.isProperType(type)) {
          expectedNonProperThrownTypes.add(type);
        }
      }
      
      final List<PsiType> thrownTypes = new ArrayList<>();
      final PsiElement body = myExpression instanceof PsiLambdaExpression ? ((PsiLambdaExpression)myExpression).getBody() : myExpression;
      if (body != null) {
        final List<PsiClassType> exceptions =  ExceptionUtil.getUnhandledExceptions(new PsiElement[] {body});
        thrownTypes.addAll(ContainerUtil.filter(exceptions, type -> !ExceptionUtil.isUncheckedException(type)));
      }

      if (expectedNonProperThrownTypes.isEmpty()) {
        for (PsiType thrownType : thrownTypes) {
          if (!isAddressed(expectedThrownTypes, thrownType)) {
            session.registerIncompatibleErrorMessage(
              JavaPsiBundle.message("error.incompatible.type.unhandled.exception", session.getPresentableText(thrownType)));
            return false;
          }
        }
      } else {
        final ArrayList<PsiType> expectedProperTypes = new ArrayList<>(expectedThrownTypes);
        expectedProperTypes.removeAll(expectedNonProperThrownTypes);
        for (PsiType thrownType : thrownTypes) {
          if (!isAddressed(expectedProperTypes, thrownType)) {
            for (PsiType expectedNonProperThrownType : expectedNonProperThrownTypes) {
              constraints.add(new StrictSubtypingConstraint(expectedNonProperThrownType, thrownType));
            }
          }
        }

        for (PsiType expectedNonProperThrownType : expectedNonProperThrownTypes) {
          final InferenceVariable variable = session.getInferenceVariable(expectedNonProperThrownType);
          //could be null for invalid code
          if (variable != null) {
            variable.setThrownBound();
          }
        }
      }
    }

    return true;
  }
@Override
  public boolean reduce(final InferenceSession session, List<? super ConstraintFormula> constraints)