/**
   * 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);
  }
  public KotlinTypeInfo visitIfExpression(
      KtIfExpression ifExpression,
      ExpressionTypingContext contextWithExpectedType,
      boolean isStatement) {
    ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE);
    KtExpression condition = ifExpression.getCondition();
    DataFlowInfo conditionDataFlowInfo = checkCondition(context.scope, condition, context);

    KtExpression elseBranch = ifExpression.getElse();
    KtExpression thenBranch = ifExpression.getThen();

    LexicalWritableScope thenScope = newWritableScopeImpl(context, "Then scope");
    LexicalWritableScope elseScope = newWritableScopeImpl(context, "Else scope");
    DataFlowInfo thenInfo =
        components
            .dataFlowAnalyzer
            .extractDataFlowInfoFromCondition(condition, true, context)
            .and(conditionDataFlowInfo);
    DataFlowInfo elseInfo =
        components
            .dataFlowAnalyzer
            .extractDataFlowInfoFromCondition(condition, false, context)
            .and(conditionDataFlowInfo);

    if (elseBranch == null) {
      if (thenBranch != null) {
        KotlinTypeInfo result =
            getTypeInfoWhenOnlyOneBranchIsPresent(
                thenBranch,
                thenScope,
                thenInfo,
                elseInfo,
                contextWithExpectedType,
                ifExpression,
                isStatement);
        // If jump was possible, take condition check info as the jump info
        return result.getJumpOutPossible()
            ? result.replaceJumpOutPossible(true).replaceJumpFlowInfo(conditionDataFlowInfo)
            : result;
      }
      return TypeInfoFactoryKt.createTypeInfo(
          components.dataFlowAnalyzer.checkImplicitCast(
              components.builtIns.getUnitType(),
              ifExpression,
              contextWithExpectedType,
              isStatement),
          thenInfo.or(elseInfo));
    }
    if (thenBranch == null) {
      return getTypeInfoWhenOnlyOneBranchIsPresent(
          elseBranch,
          elseScope,
          elseInfo,
          thenInfo,
          contextWithExpectedType,
          ifExpression,
          isStatement);
    }
    KtPsiFactory psiFactory = KtPsiFactoryKt.KtPsiFactory(ifExpression);
    KtBlockExpression thenBlock = psiFactory.wrapInABlockWrapper(thenBranch);
    KtBlockExpression elseBlock = psiFactory.wrapInABlockWrapper(elseBranch);
    Call callForIf =
        createCallForSpecialConstruction(
            ifExpression, ifExpression, Lists.newArrayList(thenBlock, elseBlock));
    MutableDataFlowInfoForArguments dataFlowInfoForArguments =
        createDataFlowInfoForArgumentsForIfCall(callForIf, thenInfo, elseInfo);
    ResolvedCall<FunctionDescriptor> resolvedCall =
        components.controlStructureTypingUtils.resolveSpecialConstructionAsCall(
            callForIf,
            ResolveConstruct.IF,
            Lists.newArrayList("thenBranch", "elseBranch"),
            Lists.newArrayList(false, false),
            contextWithExpectedType,
            dataFlowInfoForArguments);

    BindingContext bindingContext = context.trace.getBindingContext();
    KotlinTypeInfo thenTypeInfo =
        BindingContextUtils.getRecordedTypeInfo(thenBranch, bindingContext);
    KotlinTypeInfo elseTypeInfo =
        BindingContextUtils.getRecordedTypeInfo(elseBranch, bindingContext);
    assert thenTypeInfo != null
        : "'Then' branch of if expression  was not processed: " + ifExpression;
    assert elseTypeInfo != null
        : "'Else' branch of if expression  was not processed: " + ifExpression;
    boolean loopBreakContinuePossible =
        thenTypeInfo.getJumpOutPossible() || elseTypeInfo.getJumpOutPossible();

    KotlinType thenType = thenTypeInfo.getType();
    KotlinType elseType = elseTypeInfo.getType();
    DataFlowInfo thenDataFlowInfo = thenTypeInfo.getDataFlowInfo();
    DataFlowInfo elseDataFlowInfo = elseTypeInfo.getDataFlowInfo();

    boolean jumpInThen = thenType != null && KotlinBuiltIns.isNothing(thenType);
    boolean jumpInElse = elseType != null && KotlinBuiltIns.isNothing(elseType);

    DataFlowInfo resultDataFlowInfo;
    if (thenType == null && elseType == null) {
      resultDataFlowInfo = thenDataFlowInfo.or(elseDataFlowInfo);
    } else if (thenType == null || (jumpInThen && !jumpInElse)) {
      resultDataFlowInfo = elseDataFlowInfo;
    } else if (elseType == null || (jumpInElse && !jumpInThen)) {
      resultDataFlowInfo = thenDataFlowInfo;
    } else {
      resultDataFlowInfo = thenDataFlowInfo.or(elseDataFlowInfo);
    }

    KotlinType resultType = resolvedCall.getResultingDescriptor().getReturnType();
    // If break or continue was possible, take condition check info as the jump info
    return TypeInfoFactoryKt.createTypeInfo(
        components.dataFlowAnalyzer.checkImplicitCast(
            resultType, ifExpression, contextWithExpectedType, isStatement),
        resultDataFlowInfo,
        loopBreakContinuePossible,
        conditionDataFlowInfo);
  }