private KotlinTypeInfo getTypeOfLastExpressionInBlock(
      @NotNull KtExpression statementExpression,
      @NotNull ExpressionTypingContext context,
      @NotNull CoercionStrategy coercionStrategyForLastExpression,
      @NotNull ExpressionTypingInternals blockLevelVisitor) {
    if (context.expectedType != NO_EXPECTED_TYPE) {
      KotlinType expectedType;
      if (context.expectedType == UNIT_EXPECTED_TYPE
          || // the first check is necessary to avoid invocation 'isUnit(UNIT_EXPECTED_TYPE)'
          (coercionStrategyForLastExpression == COERCION_TO_UNIT
              && KotlinBuiltIns.isUnit(context.expectedType))) {
        expectedType = UNIT_EXPECTED_TYPE;
      } else {
        expectedType = context.expectedType;
      }

      return blockLevelVisitor.getTypeInfo(
          statementExpression, context.replaceExpectedType(expectedType), true);
    }
    KotlinTypeInfo result = blockLevelVisitor.getTypeInfo(statementExpression, context, true);
    if (coercionStrategyForLastExpression == COERCION_TO_UNIT) {
      boolean mightBeUnit = false;
      if (statementExpression instanceof KtDeclaration) {
        mightBeUnit = true;
      }
      if (statementExpression instanceof KtBinaryExpression) {
        KtBinaryExpression binaryExpression = (KtBinaryExpression) statementExpression;
        IElementType operationType = binaryExpression.getOperationToken();
        //noinspection SuspiciousMethodCalls
        if (operationType == KtTokens.EQ
            || OperatorConventions.ASSIGNMENT_OPERATIONS.containsKey(operationType)) {
          mightBeUnit = true;
        }
      }
      if (mightBeUnit) {
        // ExpressionTypingVisitorForStatements should return only null or Unit for declarations and
        // assignments,
        // but (for correct assignment / initialization analysis) data flow info must be preserved
        assert result.getType() == null || KotlinBuiltIns.isUnit(result.getType());
        result = result.replaceType(expressionTypingComponents.builtIns.getUnitType());
      }
    }
    return result;
  }
  /**
   * Visits block statements propagating data flow information from the first to the last.
   * Determines block returned type and data flow information at the end of the block AND at the
   * nearest jump point from the block beginning.
   */
  /*package*/ KotlinTypeInfo getBlockReturnedTypeWithWritableScope(
      @NotNull LexicalWritableScope scope,
      @NotNull List<? extends KtElement> block,
      @NotNull CoercionStrategy coercionStrategyForLastExpression,
      @NotNull ExpressionTypingContext context) {
    if (block.isEmpty()) {
      return TypeInfoFactoryKt.createTypeInfo(
          expressionTypingComponents.builtIns.getUnitType(), context);
    }

    ExpressionTypingInternals blockLevelVisitor =
        new ExpressionTypingVisitorDispatcher.ForBlock(
            expressionTypingComponents, annotationChecker, scope);
    ExpressionTypingContext newContext =
        context.replaceScope(scope).replaceExpectedType(NO_EXPECTED_TYPE);

    KotlinTypeInfo result = TypeInfoFactoryKt.noTypeInfo(context);
    // Jump point data flow info
    DataFlowInfo beforeJumpInfo = newContext.dataFlowInfo;
    boolean jumpOutPossible = false;
    for (Iterator<? extends KtElement> iterator = block.iterator(); iterator.hasNext(); ) {
      KtElement statement = iterator.next();
      if (!(statement instanceof KtExpression)) {
        continue;
      }
      KtExpression statementExpression = (KtExpression) statement;
      if (!iterator.hasNext()) {
        result =
            getTypeOfLastExpressionInBlock(
                statementExpression,
                newContext.replaceExpectedType(context.expectedType),
                coercionStrategyForLastExpression,
                blockLevelVisitor);
        if (result.getType() != null
            && statementExpression.getParent() instanceof KtBlockExpression) {
          DataFlowValue lastExpressionValue =
              DataFlowValueFactory.createDataFlowValue(
                  statementExpression, result.getType(), context);
          DataFlowValue blockExpressionValue =
              DataFlowValueFactory.createDataFlowValue(
                  (KtBlockExpression) statementExpression.getParent(), result.getType(), context);
          result =
              result.replaceDataFlowInfo(
                  result.getDataFlowInfo().assign(blockExpressionValue, lastExpressionValue));
        }
      } else {
        result =
            blockLevelVisitor.getTypeInfo(
                statementExpression,
                newContext.replaceContextDependency(ContextDependency.INDEPENDENT),
                true);
      }

      DataFlowInfo newDataFlowInfo = result.getDataFlowInfo();
      // If jump is not possible, we take new data flow info before jump
      if (!jumpOutPossible) {
        beforeJumpInfo = result.getJumpFlowInfo();
        jumpOutPossible = result.getJumpOutPossible();
      }
      if (newDataFlowInfo != context.dataFlowInfo) {
        newContext = newContext.replaceDataFlowInfo(newDataFlowInfo);
        // We take current data flow info if jump there is not possible
      }
      blockLevelVisitor =
          new ExpressionTypingVisitorDispatcher.ForBlock(
              expressionTypingComponents, annotationChecker, scope);
    }
    return result.replaceJumpOutPossible(jumpOutPossible).replaceJumpFlowInfo(beforeJumpInfo);
  }