Просмотр исходного кода

Semantic analysis for `if` expressions (#2893)

Add semantic analysis and semantics IR building for `if` expressions, and add the first parts of control flow handling to semantics IR. After discussion with @chandlerc, use [block arguments](https://en.wikipedia.org/wiki/Static_single-assignment_form#Block_arguments) to convey values from the two arms of the `if` to the result. For now, only a single block argument is supported, but we should revisit this as we explore more of the requirements of the Semantics IR form.

Functions can now contain multiple code blocks, so grab the entry block up-front instead of assuming the entry block will be at the top of the block stack when we reach the end of function emission.

Add trivial support for `bool` type literal, because without it we can't write testcases.
Richard Smith 2 лет назад
Родитель
Сommit
202d3f5993
43 измененных файлов с 639 добавлено и 154 удалено
  1. 1 0
      toolchain/driver/testdata/semantics_builtin_nodes.carbon
  2. 1 0
      toolchain/lexer/token_kind.def
  3. 24 0
      toolchain/lowering/lowering_handle.cpp
  4. 7 7
      toolchain/parser/parse_node_kind.def
  5. 38 22
      toolchain/parser/parser_handle_expression.cpp
  6. 21 13
      toolchain/parser/parser_state.def
  7. 7 7
      toolchain/parser/testdata/if_expression/basic.carbon
  8. 5 6
      toolchain/parser/testdata/if_expression/fail_condition_missing.carbon
  9. 5 5
      toolchain/parser/testdata/if_expression/fail_else_expr_missing.carbon
  10. 5 5
      toolchain/parser/testdata/if_expression/fail_else_missing.carbon
  11. 5 5
      toolchain/parser/testdata/if_expression/fail_then_expr_missing.carbon
  12. 5 6
      toolchain/parser/testdata/if_expression/fail_then_missing.carbon
  13. 11 11
      toolchain/parser/testdata/if_expression/precedence.carbon
  14. 3 0
      toolchain/semantics/semantics_builtin_kind.def
  15. 5 1
      toolchain/semantics/semantics_context.cpp
  16. 4 0
      toolchain/semantics/semantics_context.h
  17. 4 15
      toolchain/semantics/semantics_handle.cpp
  18. 10 14
      toolchain/semantics/semantics_handle_function.cpp
  19. 77 0
      toolchain/semantics/semantics_handle_if_expression.cpp
  20. 4 0
      toolchain/semantics/semantics_ir.cpp
  21. 13 0
      toolchain/semantics/semantics_node.h
  22. 6 0
      toolchain/semantics/semantics_node_block_stack.h
  23. 4 0
      toolchain/semantics/semantics_node_kind.def
  24. 3 1
      toolchain/semantics/testdata/basics/fail_name_lookup.carbon
  25. 10 4
      toolchain/semantics/testdata/function/call/fail_param_count.carbon
  26. 4 2
      toolchain/semantics/testdata/function/call/fail_param_type.carbon
  27. 5 3
      toolchain/semantics/testdata/function/call/more_param_ir.carbon
  28. 5 3
      toolchain/semantics/testdata/function/call/params_one.carbon
  29. 6 4
      toolchain/semantics/testdata/function/call/params_one_comma.carbon
  30. 5 3
      toolchain/semantics/testdata/function/call/params_two.carbon
  31. 6 4
      toolchain/semantics/testdata/function/call/params_two_comma.carbon
  32. 4 2
      toolchain/semantics/testdata/function/call/params_zero.carbon
  33. 3 1
      toolchain/semantics/testdata/function/definition/fail_param_name_conflict.carbon
  34. 9 3
      toolchain/semantics/testdata/function/definition/order.carbon
  35. 3 1
      toolchain/semantics/testdata/function/definition/params_one.carbon
  36. 3 1
      toolchain/semantics/testdata/function/definition/params_one_comma.carbon
  37. 3 1
      toolchain/semantics/testdata/function/definition/params_two.carbon
  38. 3 1
      toolchain/semantics/testdata/function/definition/params_two_comma.carbon
  39. 3 1
      toolchain/semantics/testdata/function/definition/params_zero.carbon
  40. 6 2
      toolchain/semantics/testdata/function/definition/same_param_name.carbon
  41. 80 0
      toolchain/semantics/testdata/if_expression/basic.carbon
  42. 93 0
      toolchain/semantics/testdata/if_expression/control_flow.carbon
  43. 120 0
      toolchain/semantics/testdata/if_expression/nested.carbon

+ 1 - 0
toolchain/driver/testdata/semantics_builtin_nodes.carbon

@@ -18,6 +18,7 @@
 // CHECK:STDOUT: nodes: [
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: nodeTypeType, type: typeTypeType},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: nodeInvalidType, type: typeInvalidType},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: nodeBoolType, type: typeTypeType},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: nodeIntegerType, type: typeTypeType},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: nodeFloatingPointType, type: typeTypeType},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: nodeStringType, type: typeTypeType},

+ 1 - 0
toolchain/lexer/token_kind.def

@@ -121,6 +121,7 @@ CARBON_KEYWORD_TOKEN(Api,                 "api")
 CARBON_KEYWORD_TOKEN(As,                  "as")
 CARBON_KEYWORD_TOKEN(Auto,                "auto")
 CARBON_KEYWORD_TOKEN(Base,                "base")
+CARBON_KEYWORD_TOKEN(Bool,                "bool")
 CARBON_KEYWORD_TOKEN(Break,               "break")
 CARBON_KEYWORD_TOKEN(Case,                "case")
 CARBON_KEYWORD_TOKEN(Class,               "class")

+ 24 - 0
toolchain/lowering/lowering_handle.cpp

@@ -37,6 +37,30 @@ auto LoweringHandleBindName(LoweringContext& /*context*/,
   // Probably need to do something here, but not necessary for now.
 }
 
+auto LoweringHandleBlockArg(LoweringContext& /*context*/,
+                            SemanticsNodeId /*node_id*/, SemanticsNode node)
+    -> void {
+  CARBON_FATAL() << "TODO: Add support: " << node;
+}
+
+auto LoweringHandleBranch(LoweringContext& /*context*/,
+                          SemanticsNodeId /*node_id*/, SemanticsNode node)
+    -> void {
+  CARBON_FATAL() << "TODO: Add support: " << node;
+}
+
+auto LoweringHandleBranchIf(LoweringContext& /*context*/,
+                            SemanticsNodeId /*node_id*/, SemanticsNode node)
+    -> void {
+  CARBON_FATAL() << "TODO: Add support: " << node;
+}
+
+auto LoweringHandleBranchWithArg(LoweringContext& /*context*/,
+                                 SemanticsNodeId /*node_id*/,
+                                 SemanticsNode node) -> void {
+  CARBON_FATAL() << "TODO: Add support: " << node;
+}
+
 auto LoweringHandleBuiltin(LoweringContext& /*context*/,
                            SemanticsNodeId /*node_id*/, SemanticsNode node)
     -> void {

+ 7 - 7
toolchain/parser/parse_node_kind.def

@@ -260,15 +260,15 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator, 2)
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(PostfixOperator, 1)
 
 // `if` expression + `then` + `else`:
-//   _external_: expression
+//     _external_: expression
+//   IfExpressionIf
+//     _external_: expression
 //   IfExpressionThen
 //   _external_: expression
-//   IfExpressionElse
-//   _external_: expression
-// IfExpression
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpression, 5)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionThen, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionElse, 0)
+// IfExpressionElse
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionIf, 1)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionThen, 1)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionElse, 3)
 
 // Struct literals, such as `{.a = 0}`:
 //   StructLiteralOrStructTypeLiteralStart

+ 38 - 22
toolchain/parser/parser_handle_expression.cpp

@@ -28,8 +28,8 @@ auto ParserHandleExpression(ParserContext& context) -> void {
     }
 
     if (context.PositionIs(TokenKind::If)) {
-      context.PushState(ParserState::ExpressionIfFinish);
-      context.PushState(ParserState::ExpressionThen);
+      context.PushState(ParserState::IfExpressionFinish);
+      context.PushState(ParserState::IfExpressionFinishCondition);
     } else {
       context.PushStateForExpressionLoop(ParserState::ExpressionLoopForPrefix,
                                          state.ambient_precedence,
@@ -64,6 +64,7 @@ auto ParserHandleExpressionInPostfix(ParserContext& context) -> void {
     case TokenKind::IntegerLiteral:
     case TokenKind::RealLiteral:
     case TokenKind::StringLiteral:
+    case TokenKind::Bool:
     case TokenKind::IntegerTypeLiteral:
     case TokenKind::UnsignedIntegerTypeLiteral:
     case TokenKind::FloatingPointTypeLiteral:
@@ -209,12 +210,15 @@ auto ParserHandleExpressionLoopForPrefix(ParserContext& context) -> void {
   context.PushState(state);
 }
 
-auto ParserHandleExpressionThen(ParserContext& context) -> void {
+auto ParserHandleIfExpressionFinishCondition(ParserContext& context) -> void {
   auto state = context.PopState();
 
-  if (context.ConsumeAndAddLeafNodeIf(TokenKind::Then,
-                                      ParseNodeKind::IfExpressionThen)) {
-    context.PushState(ParserState::ExpressionElse);
+  context.AddNode(ParseNodeKind::IfExpressionIf, state.token,
+                  state.subtree_start, state.has_error);
+
+  if (context.PositionIs(TokenKind::Then)) {
+    context.PushState(ParserState::IfExpressionFinishThen);
+    context.ConsumeChecked(TokenKind::Then);
     context.PushStateForExpression(*PrecedenceGroup::ForLeading(TokenKind::If));
   } else {
     // TODO: Include the location of the `if` token.
@@ -223,20 +227,24 @@ auto ParserHandleExpressionThen(ParserContext& context) -> void {
     if (!state.has_error) {
       context.emitter().Emit(*context.position(), ExpectedThenAfterIf);
     }
-    // Add placeholders for `then expression else expression`.
-    for (int i = 0; i != 4; ++i) {
-      context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position(),
-                          /*has_error=*/true);
-    }
+    // Add placeholders for `IfExpressionThen` and final `Expression`.
+    context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position(),
+                        /*has_error=*/true);
+    context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position(),
+                        /*has_error=*/true);
     context.ReturnErrorOnState();
   }
 }
 
-auto ParserHandleExpressionElse(ParserContext& context) -> void {
+auto ParserHandleIfExpressionFinishThen(ParserContext& context) -> void {
   auto state = context.PopState();
 
-  if (context.ConsumeAndAddLeafNodeIf(TokenKind::Else,
-                                      ParseNodeKind::IfExpressionElse)) {
+  context.AddNode(ParseNodeKind::IfExpressionThen, state.token,
+                  state.subtree_start, state.has_error);
+
+  if (context.PositionIs(TokenKind::Else)) {
+    context.PushState(ParserState::IfExpressionFinishElse);
+    context.ConsumeChecked(TokenKind::Else);
     context.PushStateForExpression(*PrecedenceGroup::ForLeading(TokenKind::If));
   } else {
     // TODO: Include the location of the `if` token.
@@ -245,20 +253,28 @@ auto ParserHandleExpressionElse(ParserContext& context) -> void {
     if (!state.has_error) {
       context.emitter().Emit(*context.position(), ExpectedElseAfterIf);
     }
-    // Add placeholders for `else expression`.
-    for (int i = 0; i != 2; ++i) {
-      context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position(),
-                          /*has_error=*/true);
-    }
+    // Add placeholder for the final `Expression`.
+    context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position(),
+                        /*has_error=*/true);
     context.ReturnErrorOnState();
   }
 }
 
-auto ParserHandleExpressionIfFinish(ParserContext& context) -> void {
+auto ParserHandleIfExpressionFinishElse(ParserContext& context) -> void {
+  auto else_state = context.PopState();
+
+  // Propagate the location of `else`.
+  auto if_state = context.PopState();
+  if_state.token = else_state.token;
+  if_state.has_error |= else_state.has_error;
+  context.PushState(if_state);
+}
+
+auto ParserHandleIfExpressionFinish(ParserContext& context) -> void {
   auto state = context.PopState();
 
-  context.AddNode(ParseNodeKind::IfExpression, state.token, state.subtree_start,
-                  state.has_error);
+  context.AddNode(ParseNodeKind::IfExpressionElse, state.token,
+                  state.subtree_start, state.has_error);
 }
 
 auto ParserHandleExpressionStatementFinish(ParserContext& context) -> void {

+ 21 - 13
toolchain/parser/parser_state.def

@@ -183,8 +183,8 @@ CARBON_PARSER_STATE_VARIANTS2(Designator, Expression, Struct)
 //
 // If `If`:
 //   1. Expression
-//   2. ExpressionThen
-//   3. ExpressionIfFinish
+//   2. IfExpressionCondition
+//   3. IfExpressionFinish
 // Else if valid prefix operator:
 //   1. Expression
 //   2. ExpressionLoopForPrefix
@@ -246,28 +246,36 @@ CARBON_PARSER_STATE(ExpressionLoopForBinary)
 //   1. ExpressionLoop
 CARBON_PARSER_STATE(ExpressionLoopForPrefix)
 
-// Completes an IfExpression.
-//
-// Always:
-//   (state done)
-CARBON_PARSER_STATE(ExpressionIfFinish)
-
-// Handles the `then` token in an `if` expression.
+// Completes the condition of an `if` expression and handles the `then` token.
 //
 // If `Then`:
 //   1. Expression
-//   2. ExpressionElse
+//   2. IfExpressionFinishThen
 // Else:
 //  (state done)
-CARBON_PARSER_STATE(ExpressionThen)
+CARBON_PARSER_STATE(IfExpressionFinishCondition)
 
-// Handles the `else` token in an `if` expression.
+// Completes the first alternative in an `if` expression and handles the `else`
+// token.
 //
 // If `Else`:
 //   1. Expression
+//   2. IfExpressionFinishElse
 // Else:
 //  (state done)
-CARBON_PARSER_STATE(ExpressionElse)
+CARBON_PARSER_STATE(IfExpressionFinishThen)
+
+// Completes the second alternative in an `if` expression.
+//
+// Always:
+//  (state done)
+CARBON_PARSER_STATE(IfExpressionFinishElse)
+
+// Completes an IfExpression.
+//
+// Always:
+//   (state done)
+CARBON_PARSER_STATE(IfExpressionFinish)
 
 // Handles the `;` for an expression statement, which is different from most
 // keyword statements.

+ 7 - 7
toolchain/parser/testdata/if_expression/basic.carbon

@@ -8,7 +8,7 @@
 // CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
 // CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
 // CHECK:STDOUT:         {kind: 'DeclaredName', text: 'b'},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'bool'},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'bool'},
 // CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ParameterListComma', text: ','},
 // CHECK:STDOUT:         {kind: 'DeclaredName', text: 'x'},
@@ -19,16 +19,16 @@
 // CHECK:STDOUT:         {kind: 'Literal', text: 'i32'},
 // CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 13},
-// CHECK:STDOUT:       {kind: 'NameReference', text: 'bool'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'bool'},
 // CHECK:STDOUT:     {kind: 'ReturnType', text: '->', subtree_size: 2},
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 18},
 // CHECK:STDOUT:     {kind: 'ReturnStatementStart', text: 'return'},
-// CHECK:STDOUT:       {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
-// CHECK:STDOUT:       {kind: 'NameReference', text: 'x'},
-// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'x'},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'NameReference', text: 'y'},
-// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'else', subtree_size: 6},
 // CHECK:STDOUT:   {kind: 'ReturnStatement', text: ';', subtree_size: 8},
 // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 27},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},

+ 5 - 6
toolchain/parser/testdata/if_expression/fail_condition_missing.carbon

@@ -14,14 +14,13 @@
 // CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
 // CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', has_error: yes, subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
-// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
-// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'if', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 11},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 17},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 5 - 5
toolchain/parser/testdata/if_expression/fail_else_expr_missing.carbon

@@ -14,12 +14,12 @@
 // CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
 // CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
-// CHECK:STDOUT:       {kind: 'NameReference', text: 'true'},
-// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
-// CHECK:STDOUT:       {kind: 'Literal', text: '1'},
-// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'else', has_error: yes, subtree_size: 6},
 // CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
 // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},

+ 5 - 5
toolchain/parser/testdata/if_expression/fail_else_missing.carbon

@@ -14,12 +14,12 @@
 // CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
 // CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
-// CHECK:STDOUT:       {kind: 'NameReference', text: 'true'},
-// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
-// CHECK:STDOUT:       {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'if', has_error: yes, subtree_size: 6},
 // CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
 // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},

+ 5 - 5
toolchain/parser/testdata/if_expression/fail_then_expr_missing.carbon

@@ -14,12 +14,12 @@
 // CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
 // CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
-// CHECK:STDOUT:       {kind: 'NameReference', text: 'true'},
-// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then', has_error: yes, subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'if', has_error: yes, subtree_size: 6},
 // CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
 // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},

+ 5 - 6
toolchain/parser/testdata/if_expression/fail_then_missing.carbon

@@ -14,14 +14,13 @@
 // CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
 // CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
-// CHECK:STDOUT:       {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
-// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
-// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'if', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 11},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 17},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 11 - 11
toolchain/parser/testdata/if_expression/precedence.carbon

@@ -8,25 +8,25 @@
 // CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
 // CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
 // CHECK:STDOUT:         {kind: 'DeclaredName', text: 'b'},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'bool'},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'bool'},
 // CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 5},
-// CHECK:STDOUT:       {kind: 'NameReference', text: 'bool'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'bool'},
 // CHECK:STDOUT:     {kind: 'ReturnType', text: '->', subtree_size: 2},
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 10},
 // CHECK:STDOUT:     {kind: 'ReturnStatementStart', text: 'return'},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'and', subtree_size: 3},
-// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'and', subtree_size: 3},
-// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else'},
+// CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'and', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', subtree_size: 4},
+// CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'and', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then', subtree_size: 4},
 // CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
 // CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
 // CHECK:STDOUT:       {kind: 'InfixOperator', text: 'or', subtree_size: 3},
-// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'else', subtree_size: 12},
 // CHECK:STDOUT:   {kind: 'ReturnStatement', text: ';', subtree_size: 14},
 // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 25},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},

+ 3 - 0
toolchain/semantics/semantics_builtin_kind.def

@@ -50,6 +50,9 @@ CARBON_SEMANTICS_BUILTIN_KIND(InvalidType, "<unknown>")
 // Keeping distinct placeholders can help find usages for later fixes.
 // -----------------------------------------------------------------------------
 
+// The type of bool literals and branch conditions, bool.
+CARBON_SEMANTICS_BUILTIN_KIND(BoolType, "bool")
+
 // The type of integer values and integer literals, currently always i32.
 CARBON_SEMANTICS_BUILTIN_KIND(IntegerType, "i32")
 

+ 5 - 1
toolchain/semantics/semantics_context.cpp

@@ -61,7 +61,11 @@ auto SemanticsContext::VerifyOnFinish() -> void {
 }
 
 auto SemanticsContext::AddNode(SemanticsNode node) -> SemanticsNodeId {
-  auto block = node_block_stack_.PeekForAdd();
+  return AddNodeToBlock(node_block_stack_.PeekForAdd(), node);
+}
+
+auto SemanticsContext::AddNodeToBlock(SemanticsNodeBlockId block,
+                                      SemanticsNode node) -> SemanticsNodeId {
   CARBON_VLOG() << "AddNode " << block << ": " << node << "\n";
   return semantics_ir_->AddNode(block, node);
 }

+ 4 - 0
toolchain/semantics/semantics_context.h

@@ -35,6 +35,10 @@ class SemanticsContext {
   // Adds a node to the current block, returning the produced ID.
   auto AddNode(SemanticsNode node) -> SemanticsNodeId;
 
+  // Adds a node to the given block, returning the produced ID.
+  auto AddNodeToBlock(SemanticsNodeBlockId block, SemanticsNode node)
+      -> SemanticsNodeId;
+
   // Pushes a parse tree node onto the stack, storing the SemanticsNode as the
   // result.
   auto AddNodeAndPush(ParseTree::Node parse_node, SemanticsNode node) -> void;

+ 4 - 15
toolchain/semantics/semantics_handle.cpp

@@ -178,21 +178,6 @@ auto SemanticsHandleIfConditionStart(SemanticsContext& context,
   return context.TODO(parse_node, "HandleIfConditionStart");
 }
 
-auto SemanticsHandleIfExpression(SemanticsContext& context,
-                                 ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleIfExpression");
-}
-
-auto SemanticsHandleIfExpressionElse(SemanticsContext& context,
-                                     ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleIfExpressionElse");
-}
-
-auto SemanticsHandleIfExpressionThen(SemanticsContext& context,
-                                     ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleIfExpressionThen");
-}
-
 auto SemanticsHandleIfStatement(SemanticsContext& context,
                                 ParseTree::Node parse_node) -> bool {
   return context.TODO(parse_node, "HandleIfStatement");
@@ -275,6 +260,10 @@ auto SemanticsHandleLiteral(SemanticsContext& context,
               id));
       break;
     }
+    case TokenKind::Bool: {
+      context.node_stack().Push(parse_node, SemanticsNodeId::BuiltinBoolType);
+      break;
+    }
     case TokenKind::IntegerTypeLiteral: {
       auto text = context.tokens().GetTokenText(token);
       if (text != "i32") {

+ 10 - 14
toolchain/semantics/semantics_handle_function.cpp

@@ -18,17 +18,10 @@ auto SemanticsHandleFunctionDefinition(SemanticsContext& context,
          ParseNodeKind::FunctionDefinitionStart) {
     context.node_stack().PopAndIgnore();
   }
-  auto function_id = context.node_stack().Pop<SemanticsFunctionId>(
-      ParseNodeKind::FunctionDefinitionStart);
-
+  context.node_stack().PopAndDiscardId(ParseNodeKind::FunctionDefinitionStart);
   context.return_scope_stack().pop_back();
   context.PopScope();
-  auto body_id = context.node_block_stack().Pop();
-  auto& function = context.semantics_ir().GetFunction(function_id);
-  CARBON_CHECK(!function.body_id.is_valid())
-      << "Already have function body: " << function.body_id;
-  function.body_id = body_id;
-
+  context.node_block_stack().Pop();
   return true;
 }
 
@@ -54,18 +47,21 @@ auto SemanticsHandleFunctionDefinitionStart(SemanticsContext& context,
   auto name_str = context.parse_tree().GetNodeText(name_node);
   auto name_id = context.semantics_ir().AddString(name_str);
 
-  // Add the callable, but with an invalid body for now. The body ID is only
-  // finalized on function completion.
+  // Create the entry block.
+  auto outer_block = context.node_block_stack().PeekForAdd();
+  context.node_block_stack().Push();
+
+  // Add the callable.
   auto function_id = context.semantics_ir().AddFunction(
       {.name_id = name_id,
        .param_refs_id = param_refs_id,
        .return_type_id = return_type_id,
-       .body_id = SemanticsNodeBlockId::Invalid});
-  auto decl_id = context.AddNode(
+       .body_id = context.node_block_stack().PeekForAdd()});
+  auto decl_id = context.AddNodeToBlock(
+      outer_block,
       SemanticsNode::FunctionDeclaration::Make(fn_node, function_id));
   context.AddNameToLookup(name_node, name_id, decl_id);
 
-  context.node_block_stack().Push();
   context.PushScope();
   for (auto ref_id : context.semantics_ir().GetNodeBlock(param_refs_id)) {
     auto ref = context.semantics_ir().GetNode(ref_id);

+ 77 - 0
toolchain/semantics/semantics_handle_if_expression.cpp

@@ -0,0 +1,77 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/semantics/semantics_context.h"
+
+namespace Carbon {
+
+auto SemanticsHandleIfExpressionIf(SemanticsContext& context,
+                                   ParseTree::Node if_node) -> bool {
+  auto cond_value_id = context.node_stack().Pop<SemanticsNodeId>();
+
+  context.node_stack().Push(if_node);
+
+  // Convert the condition to `bool`.
+  cond_value_id = context.ImplicitAsRequired(
+      if_node, cond_value_id,
+      context.CanonicalizeType(SemanticsNodeId::BuiltinBoolType));
+
+  // Stop emitting the current block. We'll add some branch instructions to it
+  // later, but we don't want it on the stack any more.
+  auto if_block_id = context.node_block_stack().PeekForAdd();
+  context.node_block_stack().Pop();
+
+  // Create the resumption block, `else` block, and `then` block, and branches
+  // to them.
+  context.node_block_stack().Push();
+  auto else_block_id = context.node_block_stack().PushForAdd();
+  auto then_block_id = context.node_block_stack().PushForAdd();
+  context.AddNodeToBlock(
+      if_block_id,
+      SemanticsNode::BranchIf::Make(if_node, then_block_id, cond_value_id));
+  context.AddNodeToBlock(if_block_id,
+                         SemanticsNode::Branch::Make(if_node, else_block_id));
+  return true;
+}
+
+auto SemanticsHandleIfExpressionThen(SemanticsContext& context,
+                                     ParseTree::Node then_node) -> bool {
+  context.node_stack().Push(then_node, context.node_block_stack().Pop());
+  return true;
+}
+
+auto SemanticsHandleIfExpressionElse(SemanticsContext& context,
+                                     ParseTree::Node else_node) -> bool {
+  auto else_value_id = context.node_stack().Pop<SemanticsNodeId>();
+  auto [then_node, then_end_block_id] =
+      context.node_stack().PopWithParseNode<SemanticsNodeBlockId>(
+          ParseNodeKind::IfExpressionThen);
+  auto then_value_id = context.node_stack().Pop<SemanticsNodeId>();
+  auto if_node =
+      context.node_stack().PopForSoloParseNode(ParseNodeKind::IfExpressionIf);
+
+  // Convert the `else` value to the `then` value's type, and finish the `else`
+  // block.
+  // TODO: Find a common type, and convert both operands to it instead.
+  auto result_type_id = context.semantics_ir().GetNode(then_value_id).type_id();
+  else_value_id =
+      context.ImplicitAsRequired(else_node, else_value_id, result_type_id);
+  auto else_end_block_id = context.node_block_stack().Pop();
+
+  // Create branches to the resumption block.
+  auto resume_block_id = context.node_block_stack().PeekForAdd();
+  context.AddNodeToBlock(then_end_block_id,
+                         SemanticsNode::BranchWithArg::Make(
+                             then_node, resume_block_id, then_value_id));
+  context.AddNodeToBlock(else_end_block_id,
+                         SemanticsNode::BranchWithArg::Make(
+                             else_node, resume_block_id, else_value_id));
+
+  // Obtain the value in the resumption block and push it.
+  context.AddNodeAndPush(
+      if_node, SemanticsNode::BlockArg::Make(if_node, result_type_id));
+  return true;
+}
+
+}  // namespace Carbon

+ 4 - 0
toolchain/semantics/semantics_ir.cpp

@@ -198,6 +198,10 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
       case SemanticsNodeKind::Assign:
       case SemanticsNodeKind::BinaryOperatorAdd:
       case SemanticsNodeKind::BindName:
+      case SemanticsNodeKind::BlockArg:
+      case SemanticsNodeKind::Branch:
+      case SemanticsNodeKind::BranchIf:
+      case SemanticsNodeKind::BranchWithArg:
       case SemanticsNodeKind::Builtin:
       case SemanticsNodeKind::Call:
       case SemanticsNodeKind::CodeBlock:

+ 13 - 0
toolchain/semantics/semantics_node.h

@@ -262,6 +262,19 @@ class SemanticsNode {
                                           SemanticsStringId /*name_id*/,
                                           SemanticsNodeId /*node_id*/>;
 
+  using BlockArg = Factory<SemanticsNodeKind::BlockArg>;
+
+  using Branch = FactoryNoType<SemanticsNodeKind::Branch,
+                               SemanticsNodeBlockId /*target_id*/>;
+
+  using BranchIf = FactoryNoType<SemanticsNodeKind::BranchIf,
+                                 SemanticsNodeBlockId /*target_id*/,
+                                 SemanticsNodeId /*cond_id*/>;
+
+  using BranchWithArg = FactoryNoType<SemanticsNodeKind::BranchWithArg,
+                                      SemanticsNodeBlockId /*target_id*/,
+                                      SemanticsNodeId /*arg*/>;
+
   class Builtin {
    public:
     static auto Make(SemanticsBuiltinKind builtin_kind, SemanticsTypeId type_id)

+ 6 - 0
toolchain/semantics/semantics_node_block_stack.h

@@ -27,6 +27,12 @@ class SemanticsNodeBlockStack {
   // order to support lazy allocation.
   auto Push() -> void;
 
+  // Allocates and pushes a new node block.
+  auto PushForAdd() -> SemanticsNodeBlockId {
+    Push();
+    return PeekForAdd();
+  }
+
   // Peeks at the top node block. This does not trigger lazy allocation, so the
   // returned node block may be invalid.
   auto Peek() -> SemanticsNodeBlockId { return stack_.back(); }

+ 4 - 0
toolchain/semantics/semantics_node_kind.def

@@ -23,6 +23,10 @@ CARBON_SEMANTICS_NODE_KIND(CrossReference)
 CARBON_SEMANTICS_NODE_KIND(Assign)
 CARBON_SEMANTICS_NODE_KIND(BinaryOperatorAdd)
 CARBON_SEMANTICS_NODE_KIND(BindName)
+CARBON_SEMANTICS_NODE_KIND(BlockArg)
+CARBON_SEMANTICS_NODE_KIND(Branch)
+CARBON_SEMANTICS_NODE_KIND(BranchIf)
+CARBON_SEMANTICS_NODE_KIND(BranchWithArg)
 CARBON_SEMANTICS_NODE_KIND(Builtin)
 CARBON_SEMANTICS_NODE_KIND(Call)
 CARBON_SEMANTICS_NODE_KIND(CodeBlock)

+ 3 - 1
toolchain/semantics/testdata/basics/fail_name_lookup.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block0},
+// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block2},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -26,6 +26,8 @@
 // CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+0,
 // CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
 fn Main() {

+ 10 - 4
toolchain/semantics/testdata/function/call/fail_param_count.carbon

@@ -5,10 +5,10 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block0},
-// CHECK:STDOUT:   {name: str2, param_refs: block3, body: block0},
-// CHECK:STDOUT:   {name: str4, param_refs: block5, body: block0},
-// CHECK:STDOUT:   {name: str5, param_refs: block0, body: block6},
+// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block2},
+// CHECK:STDOUT:   {name: str2, param_refs: block4, body: block5},
+// CHECK:STDOUT:   {name: str4, param_refs: block7, body: block8},
+// CHECK:STDOUT:   {name: str5, param_refs: block0, body: block9},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   1,
@@ -66,6 +66,8 @@
 // CHECK:STDOUT:     node+9,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+1,
 // CHECK:STDOUT:     node+2,
 // CHECK:STDOUT:   ],
@@ -73,6 +75,8 @@
 // CHECK:STDOUT:     node+2,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+4,
 // CHECK:STDOUT:     node+5,
 // CHECK:STDOUT:     node+6,
@@ -83,6 +87,8 @@
 // CHECK:STDOUT:     node+7,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+10,
 // CHECK:STDOUT:     node+11,
 // CHECK:STDOUT:     node+12,

+ 4 - 2
toolchain/semantics/testdata/function/call/fail_param_type.carbon

@@ -5,8 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block0},
-// CHECK:STDOUT:   {name: str2, param_refs: block0, body: block4},
+// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block4},
+// CHECK:STDOUT:   {name: str2, param_refs: block0, body: block5},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -46,6 +46,8 @@
 // CHECK:STDOUT:     node+3,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+4,
 // CHECK:STDOUT:     node+5,
 // CHECK:STDOUT:   ],

+ 5 - 3
toolchain/semantics/testdata/function/call/more_param_ir.carbon

@@ -5,8 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str3, param_refs: block2, body: block0},
-// CHECK:STDOUT:   {name: str4, param_refs: block0, body: block4},
+// CHECK:STDOUT:   {name: str3, param_refs: block2, body: block4},
+// CHECK:STDOUT:   {name: str4, param_refs: block0, body: block5},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   1,
@@ -50,7 +50,7 @@
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+16, type: type0},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int5, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+18, type: type0},
-// CHECK:STDOUT:   {kind: Call, arg0: block5, arg1: function0},
+// CHECK:STDOUT:   {kind: Call, arg0: block6, arg1: function0},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
@@ -73,6 +73,8 @@
 // CHECK:STDOUT:     node+7,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+8,
 // CHECK:STDOUT:     node+9,
 // CHECK:STDOUT:     node+10,

+ 5 - 3
toolchain/semantics/testdata/function/call/params_one.carbon

@@ -5,8 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block0},
-// CHECK:STDOUT:   {name: str2, param_refs: block0, body: block4},
+// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block4},
+// CHECK:STDOUT:   {name: str2, param_refs: block0, body: block5},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   1,
@@ -29,7 +29,7 @@
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+4, type: type0},
-// CHECK:STDOUT:   {kind: Call, arg0: block5, arg1: function0},
+// CHECK:STDOUT:   {kind: Call, arg0: block6, arg1: function0},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
@@ -46,6 +46,8 @@
 // CHECK:STDOUT:     node+3,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+4,
 // CHECK:STDOUT:     node+5,
 // CHECK:STDOUT:     node+6,

+ 6 - 4
toolchain/semantics/testdata/function/call/params_one_comma.carbon

@@ -5,8 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block0},
-// CHECK:STDOUT:   {name: str2, param_refs: block0, body: block4},
+// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block4},
+// CHECK:STDOUT:   {name: str2, param_refs: block0, body: block5},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   1,
@@ -30,10 +30,10 @@
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+4, type: type0},
-// CHECK:STDOUT:   {kind: Call, arg0: block5, arg1: function0},
+// CHECK:STDOUT:   {kind: Call, arg0: block6, arg1: function0},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+7, type: type0},
-// CHECK:STDOUT:   {kind: Call, arg0: block6, arg1: function0},
+// CHECK:STDOUT:   {kind: Call, arg0: block7, arg1: function0},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
@@ -50,6 +50,8 @@
 // CHECK:STDOUT:     node+3,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+4,
 // CHECK:STDOUT:     node+5,
 // CHECK:STDOUT:     node+6,

+ 5 - 3
toolchain/semantics/testdata/function/call/params_two.carbon

@@ -5,8 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str2, param_refs: block2, body: block0},
-// CHECK:STDOUT:   {name: str3, param_refs: block0, body: block4},
+// CHECK:STDOUT:   {name: str2, param_refs: block2, body: block4},
+// CHECK:STDOUT:   {name: str3, param_refs: block0, body: block5},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   1,
@@ -35,7 +35,7 @@
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+6, type: type0},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+8, type: type0},
-// CHECK:STDOUT:   {kind: Call, arg0: block5, arg1: function0},
+// CHECK:STDOUT:   {kind: Call, arg0: block6, arg1: function0},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
@@ -55,6 +55,8 @@
 // CHECK:STDOUT:     node+5,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+6,
 // CHECK:STDOUT:     node+7,
 // CHECK:STDOUT:     node+8,

+ 6 - 4
toolchain/semantics/testdata/function/call/params_two_comma.carbon

@@ -5,8 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str2, param_refs: block2, body: block0},
-// CHECK:STDOUT:   {name: str3, param_refs: block0, body: block4},
+// CHECK:STDOUT:   {name: str2, param_refs: block2, body: block4},
+// CHECK:STDOUT:   {name: str3, param_refs: block0, body: block5},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   1,
@@ -37,12 +37,12 @@
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+6, type: type0},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+8, type: type0},
-// CHECK:STDOUT:   {kind: Call, arg0: block5, arg1: function0},
+// CHECK:STDOUT:   {kind: Call, arg0: block6, arg1: function0},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int2, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+11, type: type0},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int3, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+13, type: type0},
-// CHECK:STDOUT:   {kind: Call, arg0: block6, arg1: function0},
+// CHECK:STDOUT:   {kind: Call, arg0: block7, arg1: function0},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
@@ -62,6 +62,8 @@
 // CHECK:STDOUT:     node+5,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+6,
 // CHECK:STDOUT:     node+7,
 // CHECK:STDOUT:     node+8,

+ 4 - 2
toolchain/semantics/testdata/function/call/params_zero.carbon

@@ -5,8 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block0},
-// CHECK:STDOUT:   {name: str1, param_refs: block0, body: block2},
+// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block2},
+// CHECK:STDOUT:   {name: str1, param_refs: block0, body: block3},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -32,6 +32,8 @@
 // CHECK:STDOUT:     node+1,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+2,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]

+ 3 - 1
toolchain/semantics/testdata/function/definition/fail_param_name_conflict.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block0},
+// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block4},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -42,6 +42,8 @@
 // CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+4,
 // CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
 // CHECK:STDERR: fail_param_name_conflict.carbon:[[@LINE+2]]:16: Redefining a in the same scope.

+ 9 - 3
toolchain/semantics/testdata/function/definition/order.carbon

@@ -5,9 +5,9 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block0},
-// CHECK:STDOUT:   {name: str1, param_refs: block0, body: block0},
-// CHECK:STDOUT:   {name: str2, param_refs: block0, body: block0},
+// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block2},
+// CHECK:STDOUT:   {name: str1, param_refs: block0, body: block3},
+// CHECK:STDOUT:   {name: str2, param_refs: block0, body: block4},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -34,6 +34,12 @@
 // CHECK:STDOUT:     node+1,
 // CHECK:STDOUT:     node+2,
 // CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
 fn Foo() {}

+ 3 - 1
toolchain/semantics/testdata/function/definition/params_one.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block0},
+// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block4},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -37,6 +37,8 @@
 // CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+2,
 // CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
 fn Foo(a: i32) {}

+ 3 - 1
toolchain/semantics/testdata/function/definition/params_one_comma.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block0},
+// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block4},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -37,6 +37,8 @@
 // CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+2,
 // CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
 fn Foo(a: i32,) {}

+ 3 - 1
toolchain/semantics/testdata/function/definition/params_two.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str2, param_refs: block2, body: block0},
+// CHECK:STDOUT:   {name: str2, param_refs: block2, body: block4},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -43,6 +43,8 @@
 // CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+4,
 // CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
 fn Foo(a: i32, b: i32) {}

+ 3 - 1
toolchain/semantics/testdata/function/definition/params_two_comma.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str2, param_refs: block2, body: block0},
+// CHECK:STDOUT:   {name: str2, param_refs: block2, body: block4},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -43,6 +43,8 @@
 // CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+4,
 // CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
 fn Foo(a: i32, b: i32,) {}

+ 3 - 1
toolchain/semantics/testdata/function/definition/params_zero.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block0},
+// CHECK:STDOUT:   {name: str0, param_refs: block0, body: block2},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -26,6 +26,8 @@
 // CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+0,
 // CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
 fn Foo() {}

+ 6 - 2
toolchain/semantics/testdata/function/definition/same_param_name.carbon

@@ -5,8 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: cross_reference_irs_size: 1
 // CHECK:STDOUT: functions: [
-// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block0},
-// CHECK:STDOUT:   {name: str2, param_refs: block5, body: block0},
+// CHECK:STDOUT:   {name: str1, param_refs: block2, body: block4},
+// CHECK:STDOUT:   {name: str2, param_refs: block6, body: block7},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
@@ -44,12 +44,16 @@
 // CHECK:STDOUT:     node+5,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+3,
 // CHECK:STDOUT:     node+4,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+4,
 // CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
 fn Foo(a: i32) {}

+ 80 - 0
toolchain/semantics/testdata/if_expression/basic.carbon

@@ -0,0 +1,80 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str3, param_refs: block2, return_type: type1, body: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT:   n,
+// CHECK:STDOUT:   m,
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeBoolType,
+// CHECK:STDOUT:   nodeIntegerType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: VarStorage, type: type0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node+0, type: type0},
+// CHECK:STDOUT:   {kind: VarStorage, type: type1},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node+2, type: type1},
+// CHECK:STDOUT:   {kind: VarStorage, type: type1},
+// CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node+4, type: type1},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block6, arg1: node+0},
+// CHECK:STDOUT:   {kind: Branch, arg0: block5},
+// CHECK:STDOUT:   {kind: BinaryOperatorAdd, arg0: node+2, arg1: node+4, type: type1},
+// CHECK:STDOUT:   {kind: BinaryOperatorAdd, arg0: node+4, arg1: node+2, type: type1},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block7, arg1: node+9},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block7, arg1: node+10},
+// CHECK:STDOUT:   {kind: BlockArg, type: type1},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+13, type: type1},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+13,
+// CHECK:STDOUT:     node+14,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn F(b: bool, n: i32, m: i32) -> i32 {
+  return if b then n + m else m + n;
+}

+ 93 - 0
toolchain/semantics/testdata/if_expression/control_flow.carbon

@@ -0,0 +1,93 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block0, return_type: type0, body: block2},
+// CHECK:STDOUT:   {name: str1, param_refs: block0, return_type: type0, body: block3},
+// CHECK:STDOUT:   {name: str3, param_refs: block5, return_type: type0, body: block6},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT:   1,
+// CHECK:STDOUT:   2,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   A,
+// CHECK:STDOUT:   B,
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeIntegerType,
+// CHECK:STDOUT:   nodeBoolType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+1, type: type0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+4, type: type0},
+// CHECK:STDOUT:   {kind: VarStorage, type: type1},
+// CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node+6, type: type1},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function2},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block8, arg1: node+6},
+// CHECK:STDOUT:   {kind: Branch, arg0: block7},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+11},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+12},
+// CHECK:STDOUT:   {kind: BlockArg, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+15, type: type0},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:     node+14,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:     node+13,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+15,
+// CHECK:STDOUT:     node+16,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn A() -> i32 { return 1; }
+fn B() -> i32 { return 2; }
+
+fn F(b: bool) -> i32 {
+  return if b then A() else B();
+}

+ 120 - 0
toolchain/semantics/testdata/if_expression/nested.carbon

@@ -0,0 +1,120 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str3, param_refs: block2, return_type: type1, body: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT:   1,
+// CHECK:STDOUT:   2,
+// CHECK:STDOUT:   3,
+// CHECK:STDOUT:   4,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   a,
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT:   c,
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeBoolType,
+// CHECK:STDOUT:   nodeIntegerType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: VarStorage, type: type0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node+0, type: type0},
+// CHECK:STDOUT:   {kind: VarStorage, type: type0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node+2, type: type0},
+// CHECK:STDOUT:   {kind: VarStorage, type: type0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node+4, type: type0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block6, arg1: node+0},
+// CHECK:STDOUT:   {kind: Branch, arg0: block5},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block8, arg1: node+2},
+// CHECK:STDOUT:   {kind: Branch, arg0: block7},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: type1},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type1},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+11},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+12},
+// CHECK:STDOUT:   {kind: BlockArg, type: type1},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block11, arg1: node+4},
+// CHECK:STDOUT:   {kind: Branch, arg0: block10},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int2, type: type1},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int3, type: type1},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block12, arg1: node+18},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block12, arg1: node+19},
+// CHECK:STDOUT:   {kind: BlockArg, type: type1},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block13, arg1: node+15},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block13, arg1: node+22},
+// CHECK:STDOUT:   {kind: BlockArg, type: type1},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+25, type: type1},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+16,
+// CHECK:STDOUT:     node+17,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:     node+14,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:     node+13,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+15,
+// CHECK:STDOUT:     node+23,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+19,
+// CHECK:STDOUT:     node+21,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+18,
+// CHECK:STDOUT:     node+20,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+22,
+// CHECK:STDOUT:     node+24,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+25,
+// CHECK:STDOUT:     node+26,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn F(a: bool, b: bool, c: bool) -> i32 {
+  return if a then if b then 1 else 2 else if c then 3 else 4;
+}