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

Semantics IR building for `if` statements. (#2920)

This follows the same structure as `if` expressions, except that no
result value is needed.

Semantics IR building for code blocks is also added. Rather than popping
all the node stack entries we push for statements within a code block,
change statements and declarations to not push themselves onto the
stack. We're not notionally performing work recursively within prior
statements, and we don't need their value for anything, so it seems
cleaner to not push them. This also allows statements and declarations
to determine what syntactic context they're in by peeking at the top of
the stack, though that's not used in this patch.
Richard Smith 2 лет назад
Родитель
Сommit
1ea123cd94

+ 0 - 6
toolchain/lowering/lowering_handle.cpp

@@ -128,12 +128,6 @@ auto LoweringHandleCall(LoweringFunctionContext& context,
   context.SetLocal(node_id, value);
 }
 
-auto LoweringHandleCodeBlock(LoweringFunctionContext& /*context*/,
-                             SemanticsNodeId /*node_id*/, SemanticsNode node)
-    -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
-}
-
 auto LoweringHandleFunctionDeclaration(LoweringFunctionContext& /*context*/,
                                        SemanticsNodeId /*node_id*/,
                                        SemanticsNode node) -> void {

+ 46 - 0
toolchain/lowering/testdata/if/else.carbon

@@ -0,0 +1,46 @@
+// 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: ; ModuleID = 'else.carbon'
+// CHECK:STDOUT: source_filename = "else.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %EmptyTupleType = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @F() {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @G() {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @H() {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @If(i1 %b) {
+// CHECK:STDOUT:   br i1 %b, label %1, label %2
+// CHECK:STDOUT:
+// CHECK:STDOUT: 1:                                                ; preds = %0
+// CHECK:STDOUT:   %F = call %EmptyTupleType @F()
+// CHECK:STDOUT:   br label %3
+// CHECK:STDOUT:
+// CHECK:STDOUT: 2:                                                ; preds = %0
+// CHECK:STDOUT:   %G = call %EmptyTupleType @G()
+// CHECK:STDOUT:   br label %3
+// CHECK:STDOUT:
+// CHECK:STDOUT: 3:                                                ; preds = %2, %1
+// CHECK:STDOUT:   %H = call %EmptyTupleType @H()
+// CHECK:STDOUT: }
+
+fn F() {}
+fn G() {}
+fn H() {}
+
+fn If(b: bool) {
+  if (b) {
+    F();
+  } else {
+    G();
+  }
+  H();
+}

+ 36 - 0
toolchain/lowering/testdata/if/no_else.carbon

@@ -0,0 +1,36 @@
+// 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: ; ModuleID = 'no_else.carbon'
+// CHECK:STDOUT: source_filename = "no_else.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %EmptyTupleType = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @F() {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @G() {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @If(i1 %b) {
+// CHECK:STDOUT:   br i1 %b, label %1, label %2
+// CHECK:STDOUT:
+// CHECK:STDOUT: 1:                                                ; preds = %0
+// CHECK:STDOUT:   %F = call %EmptyTupleType @F()
+// CHECK:STDOUT:   br label %2
+// CHECK:STDOUT:
+// CHECK:STDOUT: 2:                                                ; preds = %1, %0
+// CHECK:STDOUT:   %G = call %EmptyTupleType @G()
+// CHECK:STDOUT: }
+
+fn F() {}
+fn G() {}
+
+fn If(b: bool) {
+  if (b) {
+    F();
+  }
+  G();
+}

+ 8 - 43
toolchain/semantics/semantics_handle.cpp

@@ -22,16 +22,6 @@ auto SemanticsHandleBreakStatementStart(SemanticsContext& context,
   return context.TODO(parse_node, "HandleBreakStatementStart");
 }
 
-auto SemanticsHandleCodeBlock(SemanticsContext& context,
-                              ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleCodeBlock");
-}
-
-auto SemanticsHandleCodeBlockStart(SemanticsContext& context,
-                                   ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleCodeBlockStart");
-}
-
 auto SemanticsHandleContinueStatement(SemanticsContext& context,
                                       ParseTree::Node parse_node) -> bool {
   return context.TODO(parse_node, "HandleContinueStatement");
@@ -119,21 +109,19 @@ auto SemanticsHandleDesignatorExpression(SemanticsContext& context,
   return true;
 }
 
-auto SemanticsHandleEmptyDeclaration(SemanticsContext& context,
-                                     ParseTree::Node parse_node) -> bool {
-  // Empty declarations have no actions associated, but we still balance the
-  // tree.
-  context.node_stack().Push(parse_node);
+auto SemanticsHandleEmptyDeclaration(SemanticsContext& /*context*/,
+                                     ParseTree::Node /*parse_node*/) -> bool {
+  // Empty declarations have no actions associated.
   return true;
 }
 
 auto SemanticsHandleExpressionStatement(SemanticsContext& context,
-                                        ParseTree::Node parse_node) -> bool {
+                                        ParseTree::Node /*parse_node*/)
+    -> bool {
   // Pop the expression without investigating its contents.
   // TODO: This will probably eventually need to do some "do not discard"
   // analysis.
   context.node_stack().PopAndDiscardId();
-  context.node_stack().Push(parse_node);
   return true;
 }
 
@@ -168,26 +156,6 @@ auto SemanticsHandleGenericPatternBinding(SemanticsContext& context,
   return context.TODO(parse_node, "GenericPatternBinding");
 }
 
-auto SemanticsHandleIfCondition(SemanticsContext& context,
-                                ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleIfCondition");
-}
-
-auto SemanticsHandleIfConditionStart(SemanticsContext& context,
-                                     ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleIfConditionStart");
-}
-
-auto SemanticsHandleIfStatement(SemanticsContext& context,
-                                ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleIfStatement");
-}
-
-auto SemanticsHandleIfStatementElse(SemanticsContext& context,
-                                    ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleIfStatementElse");
-}
-
 auto SemanticsHandleInfixOperator(SemanticsContext& context,
                                   ParseTree::Node parse_node) -> bool {
   auto rhs_id = context.node_stack().Pop<SemanticsNodeId>();
@@ -484,7 +452,7 @@ auto SemanticsHandleReturnStatement(SemanticsContext& context,
           .Emit();
     }
 
-    context.AddNodeAndPush(parse_node, SemanticsNode::Return::Make(parse_node));
+    context.AddNode(SemanticsNode::Return::Make(parse_node));
   } else {
     auto arg = context.node_stack().Pop<SemanticsNodeId>();
     context.node_stack().PopAndDiscardSoloParseNode(
@@ -505,10 +473,8 @@ auto SemanticsHandleReturnStatement(SemanticsContext& context,
           context.ImplicitAsRequired(parse_node, arg, callable.return_type_id);
     }
 
-    context.AddNodeAndPush(
-        parse_node,
-        SemanticsNode::ReturnExpression::Make(
-            parse_node, context.semantics_ir().GetNode(arg).type_id(), arg));
+    context.AddNode(SemanticsNode::ReturnExpression::Make(
+        parse_node, context.semantics_ir().GetNode(arg).type_id(), arg));
   }
   return true;
 }
@@ -634,7 +600,6 @@ auto SemanticsHandleVariableDeclaration(SemanticsContext& context,
 
   context.node_stack().PopAndDiscardSoloParseNode(
       ParseNodeKind::VariableIntroducer);
-  context.node_stack().Push(parse_node);
 
   return true;
 }

+ 24 - 0
toolchain/semantics/semantics_handle_codeblock.cpp

@@ -0,0 +1,24 @@
+// 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"
+#include "toolchain/semantics/semantics_node.h"
+
+namespace Carbon {
+
+auto SemanticsHandleCodeBlockStart(SemanticsContext& context,
+                                   ParseTree::Node parse_node) -> bool {
+  context.node_stack().Push(parse_node);
+  context.PushScope();
+  return true;
+}
+
+auto SemanticsHandleCodeBlock(SemanticsContext& context,
+                              ParseTree::Node /*parse_node*/) -> bool {
+  context.PopScope();
+  context.node_stack().PopForSoloParseNode(ParseNodeKind::CodeBlockStart);
+  return true;
+}
+
+}  // namespace Carbon

+ 0 - 5
toolchain/semantics/semantics_handle_function.cpp

@@ -13,11 +13,6 @@ auto SemanticsHandleFunctionDeclaration(SemanticsContext& context,
 
 auto SemanticsHandleFunctionDefinition(SemanticsContext& context,
                                        ParseTree::Node /*parse_node*/) -> bool {
-  // Merges code block children up under the FunctionDefinitionStart.
-  while (context.parse_tree().node_kind(context.node_stack().PeekParseNode()) !=
-         ParseNodeKind::FunctionDefinitionStart) {
-    context.node_stack().PopAndIgnore();
-  }
   context.node_stack().PopAndDiscardId(ParseNodeKind::FunctionDefinitionStart);
   context.return_scope_stack().pop_back();
   context.PopScope();

+ 88 - 0
toolchain/semantics/semantics_handle_if.cpp

@@ -0,0 +1,88 @@
+// 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"
+#include "toolchain/semantics/semantics_node.h"
+
+namespace Carbon {
+
+auto SemanticsHandleIfConditionStart(SemanticsContext& /*context*/,
+                                     ParseTree::Node /*parse_node*/) -> bool {
+  return true;
+}
+
+auto SemanticsHandleIfCondition(SemanticsContext& context,
+                                ParseTree::Node parse_node) -> bool {
+  // Convert the condition to `bool`.
+  auto cond_value_id = context.node_stack().Pop<SemanticsNodeId>();
+  cond_value_id = context.ImplicitAsBool(parse_node, cond_value_id);
+
+  // Create the else block and the then block, and branch to the right one. If
+  // there is no `else`, the then block will terminate with a branch to the
+  // else block, which will be reused as the resumption block.
+  auto if_block_id = context.node_block_stack().PopForAdd();
+  auto else_block_id = context.node_block_stack().PushForAdd();
+  auto then_block_id = context.node_block_stack().PushForAdd();
+
+  // Branch to the appropriate block.
+  context.AddNodeToBlock(
+      if_block_id,
+      SemanticsNode::BranchIf::Make(parse_node, then_block_id, cond_value_id));
+  context.AddNodeToBlock(
+      if_block_id, SemanticsNode::Branch::Make(parse_node, else_block_id));
+
+  context.node_stack().Push(parse_node);
+  return true;
+}
+
+auto SemanticsHandleIfStatementElse(SemanticsContext& context,
+                                    ParseTree::Node parse_node) -> bool {
+  context.node_stack().PopAndDiscardSoloParseNode(ParseNodeKind::IfCondition);
+
+  // Switch to emitting the else block.
+  auto then_block_id = context.node_block_stack().PopForAdd();
+  context.node_stack().Push(parse_node, then_block_id);
+  return true;
+}
+
+auto SemanticsHandleIfStatement(SemanticsContext& context,
+                                ParseTree::Node parse_node) -> bool {
+  // Either the then or else block, depending on whether there's an `else` node
+  // on the top of the node stack.
+  auto sub_block_id = context.node_block_stack().PopForAdd();
+
+  switch (auto kind = context.parse_tree().node_kind(
+              context.node_stack().PeekParseNode())) {
+    case ParseNodeKind::IfCondition: {
+      // Branch from then block to else block.
+      context.node_stack().PopAndDiscardSoloParseNode(
+          ParseNodeKind::IfCondition);
+      context.AddNodeToBlock(
+          sub_block_id,
+          SemanticsNode::Branch::Make(parse_node,
+                                      context.node_block_stack().PeekForAdd()));
+      break;
+    }
+
+    case ParseNodeKind::IfStatementElse: {
+      // Branch from the then and else blocks to a new resumption block.
+      auto then_block_id = context.node_stack().Pop<SemanticsNodeBlockId>(
+          ParseNodeKind::IfStatementElse);
+      auto resume_block_id = context.node_block_stack().PushForAdd();
+      context.AddNodeToBlock(then_block_id, SemanticsNode::Branch::Make(
+                                                parse_node, resume_block_id));
+      context.AddNodeToBlock(sub_block_id, SemanticsNode::Branch::Make(
+                                               parse_node, resume_block_id));
+      break;
+    }
+
+    default: {
+      CARBON_FATAL() << "Unexpected parse node at start of `if`: " << kind;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace Carbon

+ 0 - 1
toolchain/semantics/semantics_ir.cpp

@@ -205,7 +205,6 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
       case SemanticsNodeKind::BranchWithArg:
       case SemanticsNodeKind::Builtin:
       case SemanticsNodeKind::Call:
-      case SemanticsNodeKind::CodeBlock:
       case SemanticsNodeKind::CrossReference:
       case SemanticsNodeKind::FunctionDeclaration:
       case SemanticsNodeKind::IntegerLiteral:

+ 0 - 3
toolchain/semantics/semantics_node.h

@@ -322,9 +322,6 @@ class SemanticsNode {
       Factory<SemanticsNodeKind::Call, SemanticsNodeBlockId /*refs_id*/,
               SemanticsFunctionId /*function_id*/>;
 
-  using CodeBlock = FactoryNoType<SemanticsNodeKind::CodeBlock,
-                                  SemanticsNodeBlockId /*node_block_id*/>;
-
   class CrossReference
       : public FactoryBase<SemanticsNodeKind::CrossReference,
                            SemanticsCrossReferenceIRId /*ir_id*/,

+ 0 - 1
toolchain/semantics/semantics_node_kind.def

@@ -30,7 +30,6 @@ 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)
 CARBON_SEMANTICS_NODE_KIND(FunctionDeclaration)
 CARBON_SEMANTICS_NODE_KIND(IntegerLiteral)
 CARBON_SEMANTICS_NODE_KIND(RealLiteral)

+ 93 - 0
toolchain/semantics/testdata/if/else.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, body: block2},
+// CHECK:STDOUT:   {name: str1, param_refs: block0, body: block3},
+// CHECK:STDOUT:   {name: str2, param_refs: block0, body: block4},
+// CHECK:STDOUT:   {name: str4, param_refs: block6, body: block7},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT:   G,
+// CHECK:STDOUT:   H,
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT:   If,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeEmptyTupleType,
+// CHECK:STDOUT:   nodeBoolType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function2},
+// CHECK:STDOUT:   {kind: VarStorage, type: type1},
+// CHECK:STDOUT:   {kind: BindName, arg0: str3, arg1: node+3, type: type1},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function3},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block9, arg1: node+3},
+// CHECK:STDOUT:   {kind: Branch, arg0: block8},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1},
+// CHECK:STDOUT:   {kind: Branch, arg0: block10},
+// CHECK:STDOUT:   {kind: Branch, arg0: block10},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function2},
+// 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+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// 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:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn F() {}
+fn G() {}
+fn H() {}
+
+fn If(b: bool) {
+  if (b) {
+    F();
+  } else {
+    G();
+  }
+  H();
+}

+ 75 - 0
toolchain/semantics/testdata/if/fail_scope.carbon

@@ -0,0 +1,75 @@
+// 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: str1, param_refs: block2, return_type: type1, body: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT:   2,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT:   VarScope,
+// CHECK:STDOUT:   n,
+// 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: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block6, arg1: node+0},
+// CHECK:STDOUT:   {kind: Branch, arg0: block5},
+// CHECK:STDOUT:   {kind: VarStorage, type: type1},
+// CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node+5, type: type1},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: type1},
+// CHECK:STDOUT:   {kind: Assign, arg0: node+5, arg1: node+7, type: type1},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+5, type: type1},
+// CHECK:STDOUT:   {kind: Branch, arg0: block5},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: nodeInvalidType, type: typeInvalidType},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn VarScope(b: bool) -> i32 {
+  if (b) {
+    var n: i32 = 2;
+    return n;
+  }
+  // CHECK:STDERR: fail_scope.carbon:[[@LINE+1]]:10: Name n not found
+  return n;
+}

+ 78 - 0
toolchain/semantics/testdata/if/no_else.carbon

@@ -0,0 +1,78 @@
+// 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, body: block2},
+// CHECK:STDOUT:   {name: str1, param_refs: block0, body: block3},
+// CHECK:STDOUT:   {name: str3, param_refs: block5, body: block6},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT:   G,
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT:   If,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeEmptyTupleType,
+// CHECK:STDOUT:   nodeBoolType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: VarStorage, type: type1},
+// CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node+2, type: type1},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function2},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block8, arg1: node+2},
+// CHECK:STDOUT:   {kind: Branch, arg0: block7},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0},
+// CHECK:STDOUT:   {kind: Branch, arg0: block7},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn F() {}
+fn G() {}
+
+fn If(b: bool) {
+  if (b) {
+    F();
+  }
+  G();
+}