Переглянути джерело

Check and lowering support for `for` loops. (#5698)

Add check support for `for` loops following #1885. This also adds a
basic `Optional` type to the prelude, as that's necessary to support the
new `Iterate` interface.

Depends on #5688, #5697. Those PRs aren't stacked here, but this change
will crash until they land.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 10 місяців тому
батько
коміт
866794b82a

+ 1 - 0
core/prelude.carbon

@@ -6,5 +6,6 @@
 
 package Core library "prelude";
 
+export import library "prelude/iterate";
 export import library "prelude/operators";
 export import library "prelude/types";

+ 14 - 0
core/prelude/iterate.carbon

@@ -0,0 +1,14 @@
+// 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
+
+package Core library "prelude/iterate";
+
+export import library "prelude/types/optional";
+
+interface Iterate {
+  let ElementType:! type;
+  let CursorType:! type;
+  fn NewCursor[self: Self]() -> CursorType;
+  fn Next[self: Self](cursor: CursorType*) -> Optional(ElementType);
+}

+ 1 - 0
core/prelude/types.carbon

@@ -7,6 +7,7 @@ package Core library "prelude/types";
 export import library "prelude/types/bool";
 export import library "prelude/types/int_literal";
 export import library "prelude/types/int";
+export import library "prelude/types/optional";
 export import library "prelude/types/uint";
 
 fn Float(size: IntLiteral()) -> type = "float.make_type";

+ 33 - 0
core/prelude/types/optional.carbon

@@ -0,0 +1,33 @@
+// 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
+
+package Core library "prelude/types/optional";
+
+import library "prelude/types/bool";
+
+// For now, an `Optional(T)` is stored as a pair of a `bool` and a `T`, with
+// the `T` left uninitialized if the `bool` is `false`. This isn't a viable
+// approach in the longer term, but is the best we can do for now.
+//
+// TODO: Revisit this once we have choice types implemented in the toolchain.
+//
+// TODO: We don't have an approved design for an `Optional` type yet, but it's
+// used by the design for `Iterate`. The API here is a placeholder.
+class Optional(T:! type) {
+  fn None() -> Self {
+    returned var me: Self;
+    me.has_value = false;
+    return var;
+  }
+
+  fn Some(value: T) -> Self {
+    return {.has_value = true, .value = value};
+  }
+
+  fn HasValue[self: Self]() -> bool { return self.has_value; }
+  fn Get[self: Self]() -> T { return self.value; }
+
+  private var has_value: bool;
+  private var value: T;
+}

+ 2 - 0
toolchain/check/BUILD

@@ -179,6 +179,7 @@ cc_library(
         ":context",
         ":diagnostic_emitter",
         ":dump",
+        ":scope_stack",
         "//common:check",
         "//common:error",
         "//common:find",
@@ -197,6 +198,7 @@ cc_library(
         "//toolchain/lex:tokenized_buffer",
         "//toolchain/parse:node_kind",
         "//toolchain/parse:tree",
+        "//toolchain/sem_ir:absolute_node_id",
         "//toolchain/sem_ir:entry_point",
         "//toolchain/sem_ir:expr_info",
         "//toolchain/sem_ir:file",

+ 6 - 0
toolchain/check/eval_inst.cpp

@@ -580,6 +580,12 @@ auto EvalConstantInst(Context& context, SemIR::ValueOfInitializer inst)
 
 auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
                       SemIR::VarStorage inst) -> ConstantEvalResult {
+  if (!inst.pattern_id.has_value()) {
+    // This variable was not created from a `var` pattern, so isn't a global
+    // variable.
+    return ConstantEvalResult::NotConstant;
+  }
+
   // A variable is constant if it's global.
   auto entity_name_id = SemIR::GetFirstBindingNameFromPatternId(
       context.sem_ir(), inst.pattern_id);

+ 5 - 36
toolchain/check/handle_let_and_var.cpp

@@ -5,7 +5,6 @@
 #include <optional>
 
 #include "toolchain/check/context.h"
-#include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/decl_introducer_state.h"
 #include "toolchain/check/generic.h"
@@ -16,7 +15,6 @@
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/pattern.h"
 #include "toolchain/check/pattern_match.h"
-#include "toolchain/check/return.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/lex/token_kind.h"
@@ -116,29 +114,6 @@ auto HandleParseNode(Context& context, Parse::FieldIntroducerId node_id)
   return true;
 }
 
-// Returns a VarStorage inst for the given `var` pattern. If the pattern
-// is the body of a returned var, this reuses the return slot, and otherwise it
-// adds a new inst.
-static auto GetOrAddStorage(Context& context, SemIR::InstId var_pattern_id)
-    -> SemIR::InstId {
-  if (context.decl_introducer_state_stack().innermost().modifier_set.HasAnyOf(
-          KeywordModifierSet::Returned)) {
-    auto& function = GetCurrentFunctionForReturn(context);
-    auto return_info =
-        SemIR::ReturnTypeInfo::ForFunction(context.sem_ir(), function);
-    if (return_info.has_return_slot()) {
-      return GetCurrentReturnSlot(context);
-    }
-  }
-  auto pattern = context.insts().GetWithLocId(var_pattern_id);
-
-  return AddInstWithCleanup(
-      context, pattern.loc_id,
-      SemIR::VarStorage{.type_id = ExtractScrutineeType(context.sem_ir(),
-                                                        pattern.inst.type_id()),
-                        .pattern_id = var_pattern_id});
-}
-
 auto HandleParseNode(Context& context, Parse::VariablePatternId node_id)
     -> bool {
   auto subpattern_id = context.node_stack().PopPattern();
@@ -183,17 +158,11 @@ static auto EndFullPattern(Context& context) -> void {
   AddInst<SemIR::NameBindingDecl>(context, context.node_stack().PeekNodeId(),
                                   {.pattern_block_id = pattern_block_id});
 
-  // We need to emit the VarStorage insts early, because they may be output
-  // arguments for the initializer. However, we can't emit them when we emit
-  // the corresponding `VarPattern`s because they're part of the pattern match,
-  // not part of the pattern.
-  // TODO: find a way to do this without walking the whole pattern block.
-  for (auto inst_id : context.inst_blocks().Get(pattern_block_id)) {
-    if (context.insts().Is<SemIR::VarPattern>(inst_id)) {
-      context.var_storage_map().Insert(inst_id,
-                                       GetOrAddStorage(context, inst_id));
-    }
-  }
+  // Emit storage for any `var`s in the pattern now.
+  bool returned =
+      context.decl_introducer_state_stack().innermost().modifier_set.HasAnyOf(
+          KeywordModifierSet::Returned);
+  AddPatternVarStorage(context, pattern_block_id, returned);
 }
 
 static auto HandleInitializer(Context& context, Parse::NodeId node_id) -> bool {

+ 151 - 28
toolchain/check/handle_loop_statement.cpp

@@ -2,19 +2,27 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+#include "toolchain/check/call.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/check/full_pattern_stack.h"
 #include "toolchain/check/handle.h"
 #include "toolchain/check/inst.h"
+#include "toolchain/check/member_access.h"
+#include "toolchain/check/operator.h"
+#include "toolchain/check/pattern.h"
+#include "toolchain/check/pattern_match.h"
+#include "toolchain/check/type.h"
+#include "toolchain/sem_ir/absolute_node_id.h"
+#include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
 
-// `while`
-// -------
-
-auto HandleParseNode(Context& context, Parse::WhileConditionStartId node_id)
-    -> bool {
+// Starts emitting the loop header for a `while`-like looping construct. Returns
+// the loop header block ID.
+static auto StartLoopHeader(Context& context, Parse::NodeId node_id)
+    -> SemIR::InstBlockId {
   // Branch to the loop header block. Note that we create a new block here even
   // if the current block is empty; this ensures that the loop always has a
   // preheader block.
@@ -25,15 +33,15 @@ auto HandleParseNode(Context& context, Parse::WhileConditionStartId node_id)
   context.inst_block_stack().Push(loop_header_id);
   context.region_stack().AddToRegion(loop_header_id, node_id);
 
-  context.node_stack().Push(node_id, loop_header_id);
-  return true;
+  return loop_header_id;
 }
 
-auto HandleParseNode(Context& context, Parse::WhileConditionId node_id)
-    -> bool {
-  auto cond_value_id = context.node_stack().PopExpr();
-  auto loop_header_id =
-      context.node_stack().Peek<Parse::NodeKind::WhileConditionStart>();
+// Starts emitting the loop body for a `while`-like looping construct. Converts
+// `cond_value_id` to bool and branches to the loop body if it is `true` and to
+// the loop exit if it is `false`.
+static auto BranchAndStartLoopBody(Context& context, Parse::NodeId node_id,
+                                   SemIR::InstBlockId loop_header_id,
+                                   SemIR::InstId cond_value_id) -> void {
   cond_value_id = ConvertToBoolValue(context, node_id, cond_value_id);
 
   // Branch to either the loop body or the loop exit block.
@@ -45,28 +53,51 @@ auto HandleParseNode(Context& context, Parse::WhileConditionId node_id)
   // Start emitting the loop body.
   context.inst_block_stack().Push(loop_body_id);
   context.region_stack().AddToRegion(loop_body_id, node_id);
+
+  // Allow `break` and `continue` in this scope.
   context.break_continue_stack().push_back(
       {.break_target = loop_exit_id, .continue_target = loop_header_id});
+}
+
+// Finishes emitting the body for a `while`-like loop. Adds a back-edge to the
+// loop header, and starts emitting in the loop exit block.
+static auto FinishLoopBody(Context& context, Parse::NodeId node_id) -> void {
+  auto blocks = context.break_continue_stack().pop_back_val();
 
-  context.node_stack().Push(node_id, loop_exit_id);
+  // Add the loop backedge.
+  AddInst<SemIR::Branch>(context, node_id,
+                         {.target_id = blocks.continue_target});
+  context.inst_block_stack().Pop();
+
+  // Start emitting the loop exit block.
+  context.inst_block_stack().Push(blocks.break_target);
+  context.region_stack().AddToRegion(blocks.break_target, node_id);
+}
+
+// `while`
+// -------
+
+auto HandleParseNode(Context& context, Parse::WhileConditionStartId node_id)
+    -> bool {
+  context.node_stack().Push(node_id, StartLoopHeader(context, node_id));
   return true;
 }
 
-auto HandleParseNode(Context& context, Parse::WhileStatementId node_id)
+auto HandleParseNode(Context& context, Parse::WhileConditionId node_id)
     -> bool {
-  auto loop_exit_id =
-      context.node_stack().Pop<Parse::NodeKind::WhileCondition>();
+  auto cond_value_id = context.node_stack().PopExpr();
   auto loop_header_id =
       context.node_stack().Pop<Parse::NodeKind::WhileConditionStart>();
-  context.break_continue_stack().pop_back();
 
-  // Add the loop backedge.
-  AddInst<SemIR::Branch>(context, node_id, {.target_id = loop_header_id});
-  context.inst_block_stack().Pop();
+  // Branch to either the loop body or the loop exit block, and start emitting
+  // the loop body.
+  BranchAndStartLoopBody(context, node_id, loop_header_id, cond_value_id);
+  return true;
+}
 
-  // Start emitting the loop exit block.
-  context.inst_block_stack().Push(loop_exit_id);
-  context.region_stack().AddToRegion(loop_exit_id, node_id);
+auto HandleParseNode(Context& context, Parse::WhileStatementId node_id)
+    -> bool {
+  FinishLoopBody(context, node_id);
   return true;
 }
 
@@ -75,20 +106,112 @@ auto HandleParseNode(Context& context, Parse::WhileStatementId node_id)
 
 auto HandleParseNode(Context& context, Parse::ForHeaderStartId node_id)
     -> bool {
-  return context.TODO(node_id, "HandleForHeaderStart");
+  // Create a nested scope to hold the cursor variable. This is also the lexical
+  // scope that names in the pattern are added to, although they get rebound on
+  // each loop iteration.
+  context.scope_stack().PushForSameRegion();
+
+  // Begin an implicit let declaration context for the pattern.
+  context.decl_introducer_state_stack().Push<Lex::TokenKind::Let>();
+  context.pattern_block_stack().Push();
+  context.full_pattern_stack().PushFullPattern(
+      FullPatternStack::Kind::NameBindingDecl);
+  BeginSubpattern(context);
+
+  context.node_stack().Push(node_id);
+  return true;
 }
 
 auto HandleParseNode(Context& context, Parse::ForInId node_id) -> bool {
-  context.decl_introducer_state_stack().Pop<Lex::TokenKind::Var>();
-  return context.TODO(node_id, "HandleForIn");
+  auto pattern_block_id = context.pattern_block_stack().Pop();
+  AddInst<SemIR::NameBindingDecl>(context, node_id,
+                                  {.pattern_block_id = pattern_block_id});
+  context.decl_introducer_state_stack().Pop<Lex::TokenKind::Let>();
+  context.full_pattern_stack().StartPatternInitializer();
+  context.node_stack().Push(node_id, pattern_block_id);
+  return true;
+}
+
+// For a value or reference of type `Optional(T)`, call the given accessor.
+static auto CallOptionalAccessor(Context& context, Parse::NodeId node_id,
+                                 SemIR::InstId optional_id,
+                                 llvm::StringLiteral accessor_name)
+    -> SemIR::InstId {
+  auto accessor_name_id =
+      SemIR::NameId::ForIdentifier(context.identifiers().Add(accessor_name));
+  auto accessor_id =
+      PerformMemberAccess(context, node_id, optional_id, accessor_name_id);
+  return PerformCall(context, node_id, accessor_id, {});
 }
 
 auto HandleParseNode(Context& context, Parse::ForHeaderId node_id) -> bool {
-  return context.TODO(node_id, "HandleForHeader");
+  auto range_id = context.node_stack().PopExpr();
+  auto pattern_block_id = context.node_stack().Pop<Parse::NodeKind::ForIn>();
+  auto pattern_id = context.node_stack().PopPattern();
+  auto start_node_id =
+      context.node_stack().PopForSoloNodeId<Parse::NodeKind::ForHeaderStart>();
+
+  // Convert the range expression to a value or reference so that we can use it
+  // multiple times.
+  // TODO: If this produces a temporary, its lifetime should presumably be
+  // extended to cover the loop body.
+  range_id = ConvertToValueOrRefExpr(context, range_id);
+
+  // Create the cursor variable.
+  // TODO: Produce a custom diagnostic if the range operand can't be used as a
+  // range.
+  auto cursor_id = BuildUnaryOperator(
+      context, node_id, {.interface_name = "Iterate", .op_name = "NewCursor"},
+      range_id);
+  auto cursor_type_id = context.insts().Get(cursor_id).type_id();
+  auto cursor_var_id = AddInstWithCleanup<SemIR::VarStorage>(
+      context, node_id,
+      {.type_id = cursor_type_id, .pattern_id = SemIR::AbsoluteInstId::None});
+  auto init_id = Initialize(context, node_id, cursor_var_id, cursor_id);
+  AddInst<SemIR::Assign>(context, node_id,
+                         {.lhs_id = cursor_var_id, .rhs_id = init_id});
+
+  // Start emitting the loop header block.
+  auto loop_header_id = StartLoopHeader(context, start_node_id);
+
+  // Call `<range>.(Iterate.Next)(&cursor)`.
+  auto cursor_type_inst_id = context.types().GetInstId(cursor_type_id);
+  auto cursor_addr_id = AddInst<SemIR::AddrOf>(
+      context, node_id,
+      {.type_id = GetPointerType(context, cursor_type_inst_id),
+       .lvalue_id = cursor_var_id});
+  auto element_id = BuildBinaryOperator(
+      context, node_id, {.interface_name = "Iterate", .op_name = "Next"},
+      range_id, cursor_addr_id);
+  // We need to convert away from an initializing expression in order to call
+  // `HasValue` and then separately pattern-match against the element.
+  // TODO: Instead, form a `.Some(pattern_id)` pattern and pattern-match against
+  // that.
+  element_id = ConvertToValueOrRefExpr(context, element_id);
+
+  // Branch to the loop body if the optional element has a value.
+  auto cond_value_id =
+      CallOptionalAccessor(context, node_id, element_id, "HasValue");
+  BranchAndStartLoopBody(context, node_id, loop_header_id, cond_value_id);
+
+  // The loop pattern's initializer is now complete, and any bindings in it
+  // should be in scope.
+  context.full_pattern_stack().EndPatternInitializer();
+  context.full_pattern_stack().PopFullPattern();
+
+  // Create storage for var patterns now.
+  AddPatternVarStorage(context, pattern_block_id, /*is_returned_var=*/false);
+
+  // Initialize the pattern from `<element>.Get()`.
+  auto element_value_id =
+      CallOptionalAccessor(context, node_id, element_id, "Get");
+  LocalPatternMatch(context, pattern_id, element_value_id);
+  return true;
 }
 
 auto HandleParseNode(Context& context, Parse::ForStatementId node_id) -> bool {
-  return context.TODO(node_id, "HandleForStatement");
+  FinishLoopBody(context, node_id);
+  return true;
 }
 
 // `break`

+ 4 - 4
toolchain/check/node_stack.h

@@ -419,10 +419,10 @@ class NodeStack {
       case Parse::NodeKind::WhereOperand:
         return Id::KindFor<SemIR::InstId>();
       case Parse::NodeKind::ExplicitParamList:
+      case Parse::NodeKind::ForIn:
       case Parse::NodeKind::IfCondition:
       case Parse::NodeKind::IfExprIf:
       case Parse::NodeKind::ImplicitParamList:
-      case Parse::NodeKind::WhileCondition:
       case Parse::NodeKind::WhileConditionStart:
         return Id::KindFor<SemIR::InstBlockId>();
       case Parse::NodeKind::FunctionDefinitionStart:
@@ -449,6 +449,7 @@ class NodeStack {
       case Parse::NodeKind::ExplicitParamListStart:
       case Parse::NodeKind::FieldInitializer:
       case Parse::NodeKind::FieldIntroducer:
+      case Parse::NodeKind::ForHeaderStart:
       case Parse::NodeKind::FunctionIntroducer:
       case Parse::NodeKind::IfStatementElse:
       case Parse::NodeKind::ImplicitParamListStart:
@@ -483,10 +484,8 @@ class NodeStack {
       case Parse::NodeKind::ExportIntroducer:
       case Parse::NodeKind::FileEnd:
       case Parse::NodeKind::FileStart:
-      case Parse::NodeKind::Forall:
       case Parse::NodeKind::ForHeader:
-      case Parse::NodeKind::ForHeaderStart:
-      case Parse::NodeKind::ForIn:
+      case Parse::NodeKind::Forall:
       case Parse::NodeKind::IdentifierNameQualifierWithParams:
       case Parse::NodeKind::IdentifierNameQualifierWithoutParams:
       case Parse::NodeKind::IdentifierPackageName:
@@ -528,6 +527,7 @@ class NodeStack {
       case Parse::NodeKind::StructFieldDesignator:
       case Parse::NodeKind::StructTypeLiteralComma:
       case Parse::NodeKind::TupleLiteralComma:
+      case Parse::NodeKind::WhileCondition:
         return Id::Kind::Invalid;
       default:
         // In this case, the kind must be determinable from the category, or we

+ 39 - 0
toolchain/check/pattern.cpp

@@ -4,7 +4,9 @@
 
 #include "toolchain/check/pattern.h"
 
+#include "toolchain/check/control_flow.h"
 #include "toolchain/check/inst.h"
+#include "toolchain/check/return.h"
 #include "toolchain/check/type.h"
 
 namespace Carbon::Check {
@@ -94,4 +96,41 @@ auto AddBindingPattern(Context& context, SemIR::LocId name_loc,
   return {.pattern_id = binding_pattern_id, .bind_id = bind_id};
 }
 
+// Returns a VarStorage inst for the given `var` pattern. If the pattern
+// is the body of a returned var, this reuses the return slot, and otherwise it
+// adds a new inst.
+static auto GetOrAddVarStorage(Context& context, SemIR::InstId var_pattern_id,
+                               bool is_returned_var) -> SemIR::InstId {
+  if (is_returned_var) {
+    auto& function = GetCurrentFunctionForReturn(context);
+    auto return_info =
+        SemIR::ReturnTypeInfo::ForFunction(context.sem_ir(), function);
+    if (return_info.has_return_slot()) {
+      return GetCurrentReturnSlot(context);
+    }
+  }
+  auto pattern = context.insts().GetWithLocId(var_pattern_id);
+
+  return AddInstWithCleanup(
+      context, pattern.loc_id,
+      SemIR::VarStorage{.type_id = ExtractScrutineeType(context.sem_ir(),
+                                                        pattern.inst.type_id()),
+                        .pattern_id = var_pattern_id});
+}
+
+auto AddPatternVarStorage(Context& context, SemIR::InstBlockId pattern_block_id,
+                          bool is_returned_var) -> void {
+  // We need to emit the VarStorage insts early, because they may be output
+  // arguments for the initializer. However, we can't emit them when we emit
+  // the corresponding `VarPattern`s because they're part of the pattern match,
+  // not part of the pattern.
+  // TODO: Find a way to do this without walking the whole pattern block.
+  for (auto inst_id : context.inst_blocks().Get(pattern_block_id)) {
+    if (context.insts().Is<SemIR::VarPattern>(inst_id)) {
+      context.var_storage_map().Insert(
+          inst_id, GetOrAddVarStorage(context, inst_id, is_returned_var));
+    }
+  }
+}
+
 }  // namespace Carbon::Check

+ 6 - 0
toolchain/check/pattern.h

@@ -42,6 +42,12 @@ auto AddBindingPattern(Context& context, SemIR::LocId name_loc,
                        SemIR::ExprRegionId type_region_id, bool is_generic,
                        bool is_template) -> BindingPatternInfo;
 
+// Creates storage for `var` patterns nested within the given pattern at the
+// current location in the output SemIR. For a `returned var`, this
+// reuses the function's return slot when present.
+auto AddPatternVarStorage(Context& context, SemIR::InstBlockId pattern_block_id,
+                          bool is_returned_var) -> void;
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_PATTERN_H_

+ 168 - 0
toolchain/check/testdata/for/basic.carbon

@@ -0,0 +1,168 @@
+// 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-FILE: toolchain/testing/testdata/min_prelude/for.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/for/basic.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/for/basic.carbon
+
+// --- fail_not_range.carbon
+
+library "[[@TEST_NAME]]";
+
+fn Run() {
+  // TODO: These diagnostics could be better. If nothing else, we should only diagnose once.
+  // CHECK:STDERR: fail_not_range.carbon:[[@LINE+8]]:7: error: cannot access member of interface `Core.Iterate` in type `{}` that does not implement that interface [MissingImplInMemberAccess]
+  // CHECK:STDERR:   for (c: {} in {}) {
+  // CHECK:STDERR:       ^~~~~~~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_not_range.carbon:[[@LINE+4]]:7: error: cannot access member of interface `Core.Iterate` in type `{}` that does not implement that interface [MissingImplInMemberAccess]
+  // CHECK:STDERR:   for (c: {} in {}) {
+  // CHECK:STDERR:       ^~~~~~~~~~~~~
+  // CHECK:STDERR:
+  for (c: {} in {}) {
+  }
+}
+
+// --- trivial.carbon
+
+library "[[@TEST_NAME]]";
+
+class TrivialRange {
+  impl as Core.Iterate where .CursorType = () and .ElementType = () {
+    fn NewCursor[self: Self]() {}
+    fn Next[self: Self](cursor: ()*) -> Core.Optional(()) {
+      return Core.Optional(()).None();
+    }
+  }
+}
+
+fn Body();
+fn AfterLoop();
+
+fn Run() {
+  //@dump-sem-ir-begin
+  for (_: () in {} as TrivialRange) {
+    Body();
+  }
+  AfterLoop();
+  //@dump-sem-ir-end
+}
+
+// CHECK:STDOUT: --- trivial.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %TrivialRange: type = class_type @TrivialRange [concrete]
+// CHECK:STDOUT:   %Iterate.type: type = facet_type <@Iterate> [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %NewCursor.type.427: type = fn_type @NewCursor.1 [concrete]
+// CHECK:STDOUT:   %Next.type.941: type = fn_type @Next.1 [concrete]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %HasValue.type.a15: type = fn_type @HasValue, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %HasValue.73f: %HasValue.type.a15 = struct_value () [symbolic]
+// CHECK:STDOUT:   %Get.type.e03: type = fn_type @Get, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %Get.971: %Get.type.e03 = struct_value () [symbolic]
+// CHECK:STDOUT:   %Iterate.impl_witness: <witness> = impl_witness @TrivialRange.%Iterate.impl_witness_table [concrete]
+// CHECK:STDOUT:   %NewCursor.type.bd2331.1: type = fn_type @NewCursor.2 [concrete]
+// CHECK:STDOUT:   %NewCursor.c20a1c.1: %NewCursor.type.bd2331.1 = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.843: type = ptr_type %empty_tuple.type [concrete]
+// CHECK:STDOUT:   %Optional.f9a: type = class_type @Optional, @Optional(%empty_tuple.type) [concrete]
+// CHECK:STDOUT:   %Next.type.9f0: type = fn_type @Next.2 [concrete]
+// CHECK:STDOUT:   %Next.029: %Next.type.9f0 = struct_value () [concrete]
+// CHECK:STDOUT:   %Iterate.facet.d50: %Iterate.type = facet_value %TrivialRange, (%Iterate.impl_witness) [concrete]
+// CHECK:STDOUT:   %pattern_type.cb1: type = pattern_type %empty_tuple.type [concrete]
+// CHECK:STDOUT:   %NewCursor.type.bd2331.2: type = fn_type @NewCursor.3 [concrete]
+// CHECK:STDOUT:   %NewCursor.c20a1c.2: %NewCursor.type.bd2331.2 = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %HasValue.type.b7a: type = fn_type @HasValue, @Optional(%empty_tuple.type) [concrete]
+// CHECK:STDOUT:   %HasValue.ada: %HasValue.type.b7a = struct_value () [concrete]
+// CHECK:STDOUT:   %Get.type.130: type = fn_type @Get, @Optional(%empty_tuple.type) [concrete]
+// CHECK:STDOUT:   %Get.6e8: %Get.type.130 = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
+// CHECK:STDOUT:   %Body.type: type = fn_type @Body [concrete]
+// CHECK:STDOUT:   %Body: %Body.type = struct_value () [concrete]
+// CHECK:STDOUT:   %AfterLoop.type: type = fn_type @AfterLoop [concrete]
+// CHECK:STDOUT:   %AfterLoop: %AfterLoop.type = struct_value () [concrete]
+// CHECK:STDOUT:   %TrivialRange.val: %TrivialRange = struct_value () [concrete]
+// CHECK:STDOUT:   %.510: type = fn_type_with_self_type %NewCursor.type.427, %Iterate.facet.d50 [concrete]
+// CHECK:STDOUT:   %.708: type = fn_type_with_self_type %Next.type.941, %Iterate.facet.d50 [concrete]
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.ada, @HasValue(%empty_tuple.type) [concrete]
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.6e8, @Get(%empty_tuple.type) [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core.import_ref.cd6: @Optional.%HasValue.type (%HasValue.type.a15) = import_ref Core//prelude/parts/iterate, inst98 [indirect], loaded [symbolic = @Optional.%HasValue (constants.%HasValue.73f)]
+// CHECK:STDOUT:   %Core.import_ref.4fd: @Optional.%Get.type (%Get.type.e03) = import_ref Core//prelude/parts/iterate, inst99 [indirect], loaded [symbolic = @Optional.%Get (constants.%Get.971)]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Run() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %_.patt: %pattern_type.cb1 = binding_pattern _ [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc18_18.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %TrivialRange.ref: type = name_ref TrivialRange, file.%TrivialRange.decl [concrete = constants.%TrivialRange]
+// CHECK:STDOUT:   %.loc18_18.2: ref %TrivialRange = temporary_storage
+// CHECK:STDOUT:   %.loc18_18.3: init %TrivialRange = class_init (), %.loc18_18.2 [concrete = constants.%TrivialRange.val]
+// CHECK:STDOUT:   %.loc18_18.4: ref %TrivialRange = temporary %.loc18_18.2, %.loc18_18.3
+// CHECK:STDOUT:   %.loc18_20.1: ref %TrivialRange = converted %.loc18_18.1, %.loc18_18.4
+// CHECK:STDOUT:   %impl.elem2: %.510 = impl_witness_access constants.%Iterate.impl_witness, element2 [concrete = constants.%NewCursor.c20a1c.2]
+// CHECK:STDOUT:   %bound_method.loc18_35.1: <bound method> = bound_method %.loc18_20.1, %impl.elem2
+// CHECK:STDOUT:   %.loc18_20.2: %TrivialRange = bind_value %.loc18_20.1
+// CHECK:STDOUT:   %NewCursor.ref: %NewCursor.type.bd2331.1 = name_ref NewCursor, @impl.%NewCursor.decl.loc6_32.1 [concrete = constants.%NewCursor.c20a1c.1]
+// CHECK:STDOUT:   %NewCursor.bound: <bound method> = bound_method %.loc18_20.2, %NewCursor.ref
+// CHECK:STDOUT:   %NewCursor.call: init %empty_tuple.type = call %NewCursor.bound(%.loc18_20.2)
+// CHECK:STDOUT:   %var: ref %empty_tuple.type = var invalid
+// CHECK:STDOUT:   assign %var, %NewCursor.call
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.next:
+// CHECK:STDOUT:   %addr: %ptr.843 = addr_of %var
+// CHECK:STDOUT:   %impl.elem3: %.708 = impl_witness_access constants.%Iterate.impl_witness, element3 [concrete = constants.%Next.029]
+// CHECK:STDOUT:   %bound_method.loc18_35.2: <bound method> = bound_method %.loc18_20.1, %impl.elem3
+// CHECK:STDOUT:   %.loc18_35.1: ref %Optional.f9a = temporary_storage
+// CHECK:STDOUT:   %.loc18_20.3: %TrivialRange = bind_value %.loc18_20.1
+// CHECK:STDOUT:   %Next.call: init %Optional.f9a = call %bound_method.loc18_35.2(%.loc18_20.3, %addr) to %.loc18_35.1
+// CHECK:STDOUT:   %.loc18_35.2: ref %Optional.f9a = temporary %.loc18_35.1, %Next.call
+// CHECK:STDOUT:   %.loc18_35.3: %HasValue.type.b7a = specific_constant imports.%Core.import_ref.cd6, @Optional(constants.%empty_tuple.type) [concrete = constants.%HasValue.ada]
+// CHECK:STDOUT:   %HasValue.ref: %HasValue.type.b7a = name_ref HasValue, %.loc18_35.3 [concrete = constants.%HasValue.ada]
+// CHECK:STDOUT:   %HasValue.bound: <bound method> = bound_method %.loc18_35.2, %HasValue.ref
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.ref, @HasValue(constants.%empty_tuple.type) [concrete = constants.%HasValue.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc18_35.3: <bound method> = bound_method %.loc18_35.2, %HasValue.specific_fn
+// CHECK:STDOUT:   %.loc18_35.4: %Optional.f9a = bind_value %.loc18_35.2
+// CHECK:STDOUT:   %HasValue.call: init bool = call %bound_method.loc18_35.3(%.loc18_35.4)
+// CHECK:STDOUT:   %.loc18_35.5: bool = value_of_initializer %HasValue.call
+// CHECK:STDOUT:   %.loc18_35.6: bool = converted %HasValue.call, %.loc18_35.5
+// CHECK:STDOUT:   if %.loc18_35.6 br !for.body else br !for.done
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.body:
+// CHECK:STDOUT:   %.loc18_35.7: %Get.type.130 = specific_constant imports.%Core.import_ref.4fd, @Optional(constants.%empty_tuple.type) [concrete = constants.%Get.6e8]
+// CHECK:STDOUT:   %Get.ref: %Get.type.130 = name_ref Get, %.loc18_35.7 [concrete = constants.%Get.6e8]
+// CHECK:STDOUT:   %Get.bound: <bound method> = bound_method %.loc18_35.2, %Get.ref
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.ref, @Get(constants.%empty_tuple.type) [concrete = constants.%Get.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc18_35.4: <bound method> = bound_method %.loc18_35.2, %Get.specific_fn
+// CHECK:STDOUT:   %.loc18_35.8: %Optional.f9a = bind_value %.loc18_35.2
+// CHECK:STDOUT:   %Get.call: init %empty_tuple.type = call %bound_method.loc18_35.4(%.loc18_35.8)
+// CHECK:STDOUT:   %.loc18_12.1: type = splice_block %.loc18_12.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:     %.loc18_12.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc18_12.3: type = converted %.loc18_12.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc18_35.9: ref %empty_tuple.type = temporary_storage
+// CHECK:STDOUT:   %.loc18_35.10: ref %empty_tuple.type = temporary %.loc18_35.9, %Get.call
+// CHECK:STDOUT:   %tuple: %empty_tuple.type = tuple_value () [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc18_35.11: %empty_tuple.type = converted %Get.call, %tuple [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %_: %empty_tuple.type = bind_name _, %.loc18_35.11
+// CHECK:STDOUT:   %Body.ref: %Body.type = name_ref Body, file.%Body.decl [concrete = constants.%Body]
+// CHECK:STDOUT:   %Body.call: init %empty_tuple.type = call %Body.ref()
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.done:
+// CHECK:STDOUT:   %AfterLoop.ref: %AfterLoop.type = name_ref AfterLoop, file.%AfterLoop.decl [concrete = constants.%AfterLoop]
+// CHECK:STDOUT:   %AfterLoop.call: init %empty_tuple.type = call %AfterLoop.ref()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 676 - 0
toolchain/check/testdata/for/pattern.carbon

@@ -0,0 +1,676 @@
+// 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-FILE: toolchain/testing/testdata/min_prelude/for.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/for/pattern.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/for/pattern.carbon
+
+// --- empty_range.carbon
+
+library "[[@TEST_NAME]]";
+
+class EmptyRange(T:! type) {
+  fn Make() -> Self { return {}; }
+
+  impl as Core.Iterate where .CursorType = {} and .ElementType = T {
+    fn NewCursor[self: Self]() -> {} {
+      return {};
+    }
+    fn Next[self: Self](cursor: {}*) -> Core.Optional(T) {
+      return Core.Optional(T).None();
+    }
+  }
+}
+
+// --- value.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "empty_range";
+
+class C {}
+
+fn Body(c: C);
+
+fn Run() {
+  //@dump-sem-ir-begin
+  for (c: C in EmptyRange(C).Make()) {
+    Body(c);
+  }
+  //@dump-sem-ir-end
+}
+
+// --- var.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "empty_range";
+
+class C {}
+
+fn Body(c: C*);
+
+fn Run() {
+  //@dump-sem-ir-begin
+  for (var c: C in EmptyRange(C).Make()) {
+    Body(&c);
+  }
+  //@dump-sem-ir-end
+}
+
+// --- tuple.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "empty_range";
+
+fn Body(a: bool, b: bool);
+
+fn Run() {
+  //@dump-sem-ir-begin
+  for ((a: bool, b: bool) in EmptyRange((bool, bool)).Make()) {
+    Body(a, b);
+  }
+  //@dump-sem-ir-end
+}
+
+// --- tuple_class.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "empty_range";
+
+class C {}
+
+fn Body(a: C, b: C);
+
+fn Run() {
+  //@dump-sem-ir-begin
+  for ((a: C, b: C) in EmptyRange((C, C)).Make()) {
+    Body(a, b);
+  }
+  //@dump-sem-ir-end
+}
+
+// --- fail_bad_pattern.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "empty_range";
+
+class X {}
+class Y {}
+
+fn Run() {
+  // CHECK:STDERR: fail_bad_pattern.carbon:[[@LINE+7]]:7: error: cannot implicitly convert expression of type `Y` to `X` [ConversionFailure]
+  // CHECK:STDERR:   for (x: X in EmptyRange(Y).Make()) {
+  // CHECK:STDERR:       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_bad_pattern.carbon:[[@LINE+4]]:7: note: type `Y` does not implement interface `Core.ImplicitAs(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   for (x: X in EmptyRange(Y).Make()) {
+  // CHECK:STDERR:       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  for (x: X in EmptyRange(Y).Make()) {
+  }
+}
+
+// CHECK:STDOUT: --- value.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %pattern_type.c48: type = pattern_type %C [concrete]
+// CHECK:STDOUT:   %Body.type: type = fn_type @Body [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %Body: %Body.type = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.c28: type = ptr_type %empty_struct_type [concrete]
+// CHECK:STDOUT:   %EmptyRange.type: type = generic_class_type @EmptyRange [concrete]
+// CHECK:STDOUT:   %EmptyRange.generic: %EmptyRange.type = struct_value () [concrete]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %Make.type.bd2: type = fn_type @Make, @EmptyRange(%T) [symbolic]
+// CHECK:STDOUT:   %Make.ed1: %Make.type.bd2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %EmptyRange.cc8: type = class_type @EmptyRange, @EmptyRange(%C) [concrete]
+// CHECK:STDOUT:   %Make.type.557: type = fn_type @Make, @EmptyRange(%C) [concrete]
+// CHECK:STDOUT:   %Make.74d: %Make.type.557 = struct_value () [concrete]
+// CHECK:STDOUT:   %Make.specific_fn: <specific function> = specific_function %Make.74d, @Make(%C) [concrete]
+// CHECK:STDOUT:   %Iterate.type: type = facet_type <@Iterate> [concrete]
+// CHECK:STDOUT:   %NewCursor.type.427: type = fn_type @NewCursor.1 [concrete]
+// CHECK:STDOUT:   %NewCursor.type.f5f: type = fn_type @NewCursor.2, @impl(%T) [symbolic]
+// CHECK:STDOUT:   %NewCursor.ec1: %NewCursor.type.f5f = struct_value () [symbolic]
+// CHECK:STDOUT:   %Next.type.264: type = fn_type @Next.1, @impl(%T) [symbolic]
+// CHECK:STDOUT:   %Next.08e: %Next.type.264 = struct_value () [symbolic]
+// CHECK:STDOUT:   %HasValue.type.f81: type = fn_type @HasValue, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %HasValue.6fd: %HasValue.type.f81 = struct_value () [symbolic]
+// CHECK:STDOUT:   %Get.type.b8f: type = fn_type @Get, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %Get.9c8: %Get.type.b8f = struct_value () [symbolic]
+// CHECK:STDOUT:   %Iterate.impl_witness.52a: <witness> = impl_witness imports.%Iterate.impl_witness_table, @impl(%C) [concrete]
+// CHECK:STDOUT:   %NewCursor.type.951: type = fn_type @NewCursor.2, @impl(%C) [concrete]
+// CHECK:STDOUT:   %NewCursor.535: %NewCursor.type.951 = struct_value () [concrete]
+// CHECK:STDOUT:   %Next.type.63f: type = fn_type @Next.1, @impl(%C) [concrete]
+// CHECK:STDOUT:   %Next.899: %Next.type.63f = struct_value () [concrete]
+// CHECK:STDOUT:   %Iterate.facet: %Iterate.type = facet_value %EmptyRange.cc8, (%Iterate.impl_witness.52a) [concrete]
+// CHECK:STDOUT:   %.2f6: type = fn_type_with_self_type %NewCursor.type.427, %Iterate.facet [concrete]
+// CHECK:STDOUT:   %NewCursor.specific_fn: <specific function> = specific_function %NewCursor.535, @NewCursor.2(%C) [concrete]
+// CHECK:STDOUT:   %Next.type.941: type = fn_type @Next.2 [concrete]
+// CHECK:STDOUT:   %.5d5: type = fn_type_with_self_type %Next.type.941, %Iterate.facet [concrete]
+// CHECK:STDOUT:   %Optional.cf0: type = class_type @Optional, @Optional(%C) [concrete]
+// CHECK:STDOUT:   %Next.specific_fn: <specific function> = specific_function %Next.899, @Next.1(%C) [concrete]
+// CHECK:STDOUT:   %HasValue.type.71d: type = fn_type @HasValue, @Optional(%C) [concrete]
+// CHECK:STDOUT:   %HasValue.513: %HasValue.type.71d = struct_value () [concrete]
+// CHECK:STDOUT:   %Get.type.115: type = fn_type @Get, @Optional(%C) [concrete]
+// CHECK:STDOUT:   %Get.9c1: %Get.type.115 = struct_value () [concrete]
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.513, @HasValue(%C) [concrete]
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.9c1, @Get(%C) [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.EmptyRange: %EmptyRange.type = import_ref Main//empty_range, EmptyRange, loaded [concrete = constants.%EmptyRange.generic]
+// CHECK:STDOUT:   %Main.import_ref.6ea: @EmptyRange.%Make.type (%Make.type.bd2) = import_ref Main//empty_range, loc5_21, loaded [symbolic = @EmptyRange.%Make (constants.%Make.ed1)]
+// CHECK:STDOUT:   %Main.import_ref.6ce = import_ref Main//empty_range, loc7_68, unloaded
+// CHECK:STDOUT:   %Main.import_ref.999 = import_ref Main//empty_range, loc7_68, unloaded
+// CHECK:STDOUT:   %Main.import_ref.57b: @impl.%NewCursor.type (%NewCursor.type.f5f) = import_ref Main//empty_range, loc8_38, loaded [symbolic = @impl.%NewCursor (constants.%NewCursor.ec1)]
+// CHECK:STDOUT:   %Main.import_ref.170: @impl.%Next.type (%Next.type.264) = import_ref Main//empty_range, loc11_58, loaded [symbolic = @impl.%Next (constants.%Next.08e)]
+// CHECK:STDOUT:   %Iterate.impl_witness_table = impl_witness_table (%Main.import_ref.6ce, %Main.import_ref.999, %Main.import_ref.57b, %Main.import_ref.170), @impl [concrete]
+// CHECK:STDOUT:   %Main.import_ref.7f9: @Optional.%HasValue.type (%HasValue.type.f81) = import_ref Main//empty_range, inst136 [indirect], loaded [symbolic = @Optional.%HasValue (constants.%HasValue.6fd)]
+// CHECK:STDOUT:   %Main.import_ref.d10: @Optional.%Get.type (%Get.type.b8f) = import_ref Main//empty_range, inst137 [indirect], loaded [symbolic = @Optional.%Get (constants.%Get.9c8)]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Run() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %c.patt: %pattern_type.c48 = binding_pattern c [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %EmptyRange.ref: %EmptyRange.type = name_ref EmptyRange, imports.%Main.EmptyRange [concrete = constants.%EmptyRange.generic]
+// CHECK:STDOUT:   %C.ref.loc12_27: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %EmptyRange: type = class_type @EmptyRange, @EmptyRange(constants.%C) [concrete = constants.%EmptyRange.cc8]
+// CHECK:STDOUT:   %.loc12_29: %Make.type.557 = specific_constant imports.%Main.import_ref.6ea, @EmptyRange(constants.%C) [concrete = constants.%Make.74d]
+// CHECK:STDOUT:   %Make.ref: %Make.type.557 = name_ref Make, %.loc12_29 [concrete = constants.%Make.74d]
+// CHECK:STDOUT:   %Make.specific_fn: <specific function> = specific_function %Make.ref, @Make(constants.%C) [concrete = constants.%Make.specific_fn]
+// CHECK:STDOUT:   %.loc12_35.1: ref %EmptyRange.cc8 = temporary_storage
+// CHECK:STDOUT:   %Make.call: init %EmptyRange.cc8 = call %Make.specific_fn() to %.loc12_35.1
+// CHECK:STDOUT:   %.loc12_35.2: ref %EmptyRange.cc8 = temporary %.loc12_35.1, %Make.call
+// CHECK:STDOUT:   %impl.elem2: %.2f6 = impl_witness_access constants.%Iterate.impl_witness.52a, element2 [concrete = constants.%NewCursor.535]
+// CHECK:STDOUT:   %bound_method.loc12_36.1: <bound method> = bound_method %.loc12_35.2, %impl.elem2
+// CHECK:STDOUT:   %specific_fn.loc12_36.1: <specific function> = specific_function %impl.elem2, @NewCursor.2(constants.%C) [concrete = constants.%NewCursor.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_36.2: <bound method> = bound_method %.loc12_35.2, %specific_fn.loc12_36.1
+// CHECK:STDOUT:   %.loc12_35.3: %EmptyRange.cc8 = bind_value %.loc12_35.2
+// CHECK:STDOUT:   %NewCursor.call: init %empty_struct_type = call %bound_method.loc12_36.2(%.loc12_35.3)
+// CHECK:STDOUT:   %var: ref %empty_struct_type = var invalid
+// CHECK:STDOUT:   assign %var, %NewCursor.call
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.next:
+// CHECK:STDOUT:   %addr: %ptr.c28 = addr_of %var
+// CHECK:STDOUT:   %impl.elem3: %.5d5 = impl_witness_access constants.%Iterate.impl_witness.52a, element3 [concrete = constants.%Next.899]
+// CHECK:STDOUT:   %bound_method.loc12_36.3: <bound method> = bound_method %.loc12_35.2, %impl.elem3
+// CHECK:STDOUT:   %specific_fn.loc12_36.2: <specific function> = specific_function %impl.elem3, @Next.1(constants.%C) [concrete = constants.%Next.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_36.4: <bound method> = bound_method %.loc12_35.2, %specific_fn.loc12_36.2
+// CHECK:STDOUT:   %.loc12_36.1: ref %Optional.cf0 = temporary_storage
+// CHECK:STDOUT:   %.loc12_35.4: %EmptyRange.cc8 = bind_value %.loc12_35.2
+// CHECK:STDOUT:   %Next.call: init %Optional.cf0 = call %bound_method.loc12_36.4(%.loc12_35.4, %addr) to %.loc12_36.1
+// CHECK:STDOUT:   %.loc12_36.2: ref %Optional.cf0 = temporary %.loc12_36.1, %Next.call
+// CHECK:STDOUT:   %.loc12_36.3: %HasValue.type.71d = specific_constant imports.%Main.import_ref.7f9, @Optional(constants.%C) [concrete = constants.%HasValue.513]
+// CHECK:STDOUT:   %HasValue.ref: %HasValue.type.71d = name_ref HasValue, %.loc12_36.3 [concrete = constants.%HasValue.513]
+// CHECK:STDOUT:   %HasValue.bound: <bound method> = bound_method %.loc12_36.2, %HasValue.ref
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.ref, @HasValue(constants.%C) [concrete = constants.%HasValue.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_36.5: <bound method> = bound_method %.loc12_36.2, %HasValue.specific_fn
+// CHECK:STDOUT:   %.loc12_36.4: %Optional.cf0 = bind_value %.loc12_36.2
+// CHECK:STDOUT:   %HasValue.call: init bool = call %bound_method.loc12_36.5(%.loc12_36.4)
+// CHECK:STDOUT:   %.loc12_36.5: bool = value_of_initializer %HasValue.call
+// CHECK:STDOUT:   %.loc12_36.6: bool = converted %HasValue.call, %.loc12_36.5
+// CHECK:STDOUT:   if %.loc12_36.6 br !for.body else br !for.done
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.body:
+// CHECK:STDOUT:   %.loc12_36.7: %Get.type.115 = specific_constant imports.%Main.import_ref.d10, @Optional(constants.%C) [concrete = constants.%Get.9c1]
+// CHECK:STDOUT:   %Get.ref: %Get.type.115 = name_ref Get, %.loc12_36.7 [concrete = constants.%Get.9c1]
+// CHECK:STDOUT:   %Get.bound: <bound method> = bound_method %.loc12_36.2, %Get.ref
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.ref, @Get(constants.%C) [concrete = constants.%Get.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_36.6: <bound method> = bound_method %.loc12_36.2, %Get.specific_fn
+// CHECK:STDOUT:   %.loc12_36.8: ref %C = temporary_storage
+// CHECK:STDOUT:   %.loc12_36.9: %Optional.cf0 = bind_value %.loc12_36.2
+// CHECK:STDOUT:   %Get.call: init %C = call %bound_method.loc12_36.6(%.loc12_36.9) to %.loc12_36.8
+// CHECK:STDOUT:   %C.ref.loc12_11: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %.loc12_36.10: ref %C = temporary %.loc12_36.8, %Get.call
+// CHECK:STDOUT:   %.loc12_36.11: %C = bind_value %.loc12_36.10
+// CHECK:STDOUT:   %c: %C = bind_name c, %.loc12_36.11
+// CHECK:STDOUT:   %Body.ref: %Body.type = name_ref Body, file.%Body.decl [concrete = constants.%Body]
+// CHECK:STDOUT:   %c.ref: %C = name_ref c, %c
+// CHECK:STDOUT:   %Body.call: init %empty_tuple.type = call %Body.ref(%c.ref)
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.done:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- var.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %ptr.019: type = ptr_type %C [concrete]
+// CHECK:STDOUT:   %Body.type: type = fn_type @Body [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %Body: %Body.type = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.c28: type = ptr_type %empty_struct_type [concrete]
+// CHECK:STDOUT:   %pattern_type.c48: type = pattern_type %C [concrete]
+// CHECK:STDOUT:   %EmptyRange.type: type = generic_class_type @EmptyRange [concrete]
+// CHECK:STDOUT:   %EmptyRange.generic: %EmptyRange.type = struct_value () [concrete]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %Make.type.bd2: type = fn_type @Make, @EmptyRange(%T) [symbolic]
+// CHECK:STDOUT:   %Make.ed1: %Make.type.bd2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %EmptyRange.cc8: type = class_type @EmptyRange, @EmptyRange(%C) [concrete]
+// CHECK:STDOUT:   %Make.type.557: type = fn_type @Make, @EmptyRange(%C) [concrete]
+// CHECK:STDOUT:   %Make.74d: %Make.type.557 = struct_value () [concrete]
+// CHECK:STDOUT:   %Make.specific_fn: <specific function> = specific_function %Make.74d, @Make(%C) [concrete]
+// CHECK:STDOUT:   %Iterate.type: type = facet_type <@Iterate> [concrete]
+// CHECK:STDOUT:   %NewCursor.type.427: type = fn_type @NewCursor.1 [concrete]
+// CHECK:STDOUT:   %NewCursor.type.f5f: type = fn_type @NewCursor.2, @impl(%T) [symbolic]
+// CHECK:STDOUT:   %NewCursor.ec1: %NewCursor.type.f5f = struct_value () [symbolic]
+// CHECK:STDOUT:   %Next.type.264: type = fn_type @Next.1, @impl(%T) [symbolic]
+// CHECK:STDOUT:   %Next.08e: %Next.type.264 = struct_value () [symbolic]
+// CHECK:STDOUT:   %HasValue.type.f81: type = fn_type @HasValue, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %HasValue.6fd: %HasValue.type.f81 = struct_value () [symbolic]
+// CHECK:STDOUT:   %Get.type.b8f: type = fn_type @Get, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %Get.9c8: %Get.type.b8f = struct_value () [symbolic]
+// CHECK:STDOUT:   %Iterate.impl_witness.52a: <witness> = impl_witness imports.%Iterate.impl_witness_table, @impl(%C) [concrete]
+// CHECK:STDOUT:   %NewCursor.type.951: type = fn_type @NewCursor.2, @impl(%C) [concrete]
+// CHECK:STDOUT:   %NewCursor.535: %NewCursor.type.951 = struct_value () [concrete]
+// CHECK:STDOUT:   %Next.type.63f: type = fn_type @Next.1, @impl(%C) [concrete]
+// CHECK:STDOUT:   %Next.899: %Next.type.63f = struct_value () [concrete]
+// CHECK:STDOUT:   %Iterate.facet: %Iterate.type = facet_value %EmptyRange.cc8, (%Iterate.impl_witness.52a) [concrete]
+// CHECK:STDOUT:   %.2f6: type = fn_type_with_self_type %NewCursor.type.427, %Iterate.facet [concrete]
+// CHECK:STDOUT:   %NewCursor.specific_fn: <specific function> = specific_function %NewCursor.535, @NewCursor.2(%C) [concrete]
+// CHECK:STDOUT:   %Next.type.941: type = fn_type @Next.2 [concrete]
+// CHECK:STDOUT:   %.5d5: type = fn_type_with_self_type %Next.type.941, %Iterate.facet [concrete]
+// CHECK:STDOUT:   %Optional.cf0: type = class_type @Optional, @Optional(%C) [concrete]
+// CHECK:STDOUT:   %Next.specific_fn: <specific function> = specific_function %Next.899, @Next.1(%C) [concrete]
+// CHECK:STDOUT:   %HasValue.type.71d: type = fn_type @HasValue, @Optional(%C) [concrete]
+// CHECK:STDOUT:   %HasValue.513: %HasValue.type.71d = struct_value () [concrete]
+// CHECK:STDOUT:   %Get.type.115: type = fn_type @Get, @Optional(%C) [concrete]
+// CHECK:STDOUT:   %Get.9c1: %Get.type.115 = struct_value () [concrete]
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.513, @HasValue(%C) [concrete]
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.9c1, @Get(%C) [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.EmptyRange: %EmptyRange.type = import_ref Main//empty_range, EmptyRange, loaded [concrete = constants.%EmptyRange.generic]
+// CHECK:STDOUT:   %Main.import_ref.6ea: @EmptyRange.%Make.type (%Make.type.bd2) = import_ref Main//empty_range, loc5_21, loaded [symbolic = @EmptyRange.%Make (constants.%Make.ed1)]
+// CHECK:STDOUT:   %Main.import_ref.6ce = import_ref Main//empty_range, loc7_68, unloaded
+// CHECK:STDOUT:   %Main.import_ref.999 = import_ref Main//empty_range, loc7_68, unloaded
+// CHECK:STDOUT:   %Main.import_ref.57b: @impl.%NewCursor.type (%NewCursor.type.f5f) = import_ref Main//empty_range, loc8_38, loaded [symbolic = @impl.%NewCursor (constants.%NewCursor.ec1)]
+// CHECK:STDOUT:   %Main.import_ref.170: @impl.%Next.type (%Next.type.264) = import_ref Main//empty_range, loc11_58, loaded [symbolic = @impl.%Next (constants.%Next.08e)]
+// CHECK:STDOUT:   %Iterate.impl_witness_table = impl_witness_table (%Main.import_ref.6ce, %Main.import_ref.999, %Main.import_ref.57b, %Main.import_ref.170), @impl [concrete]
+// CHECK:STDOUT:   %Main.import_ref.7f9: @Optional.%HasValue.type (%HasValue.type.f81) = import_ref Main//empty_range, inst136 [indirect], loaded [symbolic = @Optional.%HasValue (constants.%HasValue.6fd)]
+// CHECK:STDOUT:   %Main.import_ref.d10: @Optional.%Get.type (%Get.type.b8f) = import_ref Main//empty_range, inst137 [indirect], loaded [symbolic = @Optional.%Get (constants.%Get.9c8)]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Run() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %c.patt: %pattern_type.c48 = binding_pattern c [concrete]
+// CHECK:STDOUT:     %c.var_patt: %pattern_type.c48 = var_pattern %c.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %EmptyRange.ref: %EmptyRange.type = name_ref EmptyRange, imports.%Main.EmptyRange [concrete = constants.%EmptyRange.generic]
+// CHECK:STDOUT:   %C.ref.loc12_31: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %EmptyRange: type = class_type @EmptyRange, @EmptyRange(constants.%C) [concrete = constants.%EmptyRange.cc8]
+// CHECK:STDOUT:   %.loc12_33: %Make.type.557 = specific_constant imports.%Main.import_ref.6ea, @EmptyRange(constants.%C) [concrete = constants.%Make.74d]
+// CHECK:STDOUT:   %Make.ref: %Make.type.557 = name_ref Make, %.loc12_33 [concrete = constants.%Make.74d]
+// CHECK:STDOUT:   %Make.specific_fn: <specific function> = specific_function %Make.ref, @Make(constants.%C) [concrete = constants.%Make.specific_fn]
+// CHECK:STDOUT:   %.loc12_39.1: ref %EmptyRange.cc8 = temporary_storage
+// CHECK:STDOUT:   %Make.call: init %EmptyRange.cc8 = call %Make.specific_fn() to %.loc12_39.1
+// CHECK:STDOUT:   %.loc12_39.2: ref %EmptyRange.cc8 = temporary %.loc12_39.1, %Make.call
+// CHECK:STDOUT:   %impl.elem2: %.2f6 = impl_witness_access constants.%Iterate.impl_witness.52a, element2 [concrete = constants.%NewCursor.535]
+// CHECK:STDOUT:   %bound_method.loc12_40.1: <bound method> = bound_method %.loc12_39.2, %impl.elem2
+// CHECK:STDOUT:   %specific_fn.loc12_40.1: <specific function> = specific_function %impl.elem2, @NewCursor.2(constants.%C) [concrete = constants.%NewCursor.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_40.2: <bound method> = bound_method %.loc12_39.2, %specific_fn.loc12_40.1
+// CHECK:STDOUT:   %.loc12_39.3: %EmptyRange.cc8 = bind_value %.loc12_39.2
+// CHECK:STDOUT:   %NewCursor.call: init %empty_struct_type = call %bound_method.loc12_40.2(%.loc12_39.3)
+// CHECK:STDOUT:   %var: ref %empty_struct_type = var invalid
+// CHECK:STDOUT:   assign %var, %NewCursor.call
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.next:
+// CHECK:STDOUT:   %addr.loc12: %ptr.c28 = addr_of %var
+// CHECK:STDOUT:   %impl.elem3: %.5d5 = impl_witness_access constants.%Iterate.impl_witness.52a, element3 [concrete = constants.%Next.899]
+// CHECK:STDOUT:   %bound_method.loc12_40.3: <bound method> = bound_method %.loc12_39.2, %impl.elem3
+// CHECK:STDOUT:   %specific_fn.loc12_40.2: <specific function> = specific_function %impl.elem3, @Next.1(constants.%C) [concrete = constants.%Next.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_40.4: <bound method> = bound_method %.loc12_39.2, %specific_fn.loc12_40.2
+// CHECK:STDOUT:   %.loc12_40.1: ref %Optional.cf0 = temporary_storage
+// CHECK:STDOUT:   %.loc12_39.4: %EmptyRange.cc8 = bind_value %.loc12_39.2
+// CHECK:STDOUT:   %Next.call: init %Optional.cf0 = call %bound_method.loc12_40.4(%.loc12_39.4, %addr.loc12) to %.loc12_40.1
+// CHECK:STDOUT:   %.loc12_40.2: ref %Optional.cf0 = temporary %.loc12_40.1, %Next.call
+// CHECK:STDOUT:   %.loc12_40.3: %HasValue.type.71d = specific_constant imports.%Main.import_ref.7f9, @Optional(constants.%C) [concrete = constants.%HasValue.513]
+// CHECK:STDOUT:   %HasValue.ref: %HasValue.type.71d = name_ref HasValue, %.loc12_40.3 [concrete = constants.%HasValue.513]
+// CHECK:STDOUT:   %HasValue.bound: <bound method> = bound_method %.loc12_40.2, %HasValue.ref
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.ref, @HasValue(constants.%C) [concrete = constants.%HasValue.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_40.5: <bound method> = bound_method %.loc12_40.2, %HasValue.specific_fn
+// CHECK:STDOUT:   %.loc12_40.4: %Optional.cf0 = bind_value %.loc12_40.2
+// CHECK:STDOUT:   %HasValue.call: init bool = call %bound_method.loc12_40.5(%.loc12_40.4)
+// CHECK:STDOUT:   %.loc12_40.5: bool = value_of_initializer %HasValue.call
+// CHECK:STDOUT:   %.loc12_40.6: bool = converted %HasValue.call, %.loc12_40.5
+// CHECK:STDOUT:   if %.loc12_40.6 br !for.body else br !for.done
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.body:
+// CHECK:STDOUT:   %c.var: ref %C = var %c.var_patt
+// CHECK:STDOUT:   %.loc12_40.7: %Get.type.115 = specific_constant imports.%Main.import_ref.d10, @Optional(constants.%C) [concrete = constants.%Get.9c1]
+// CHECK:STDOUT:   %Get.ref: %Get.type.115 = name_ref Get, %.loc12_40.7 [concrete = constants.%Get.9c1]
+// CHECK:STDOUT:   %Get.bound: <bound method> = bound_method %.loc12_40.2, %Get.ref
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.ref, @Get(constants.%C) [concrete = constants.%Get.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_40.6: <bound method> = bound_method %.loc12_40.2, %Get.specific_fn
+// CHECK:STDOUT:   %.loc12_8: ref %C = splice_block %c.var {}
+// CHECK:STDOUT:   %.loc12_40.8: %Optional.cf0 = bind_value %.loc12_40.2
+// CHECK:STDOUT:   %Get.call: init %C = call %bound_method.loc12_40.6(%.loc12_40.8) to %.loc12_8
+// CHECK:STDOUT:   assign %c.var, %Get.call
+// CHECK:STDOUT:   %C.ref.loc12_15: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %c: ref %C = bind_name c, %c.var
+// CHECK:STDOUT:   %Body.ref: %Body.type = name_ref Body, file.%Body.decl [concrete = constants.%Body]
+// CHECK:STDOUT:   %c.ref: ref %C = name_ref c, %c
+// CHECK:STDOUT:   %addr.loc13: %ptr.019 = addr_of %c.ref
+// CHECK:STDOUT:   %Body.call: init %empty_tuple.type = call %Body.ref(%addr.loc13)
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.done:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- tuple.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bool.type: type = fn_type @Bool [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %Bool: %Bool.type = struct_value () [concrete]
+// CHECK:STDOUT:   %pattern_type.831: type = pattern_type bool [concrete]
+// CHECK:STDOUT:   %Body.type: type = fn_type @Body [concrete]
+// CHECK:STDOUT:   %Body: %Body.type = struct_value () [concrete]
+// CHECK:STDOUT:   %tuple.type.784: type = tuple_type (bool, bool) [concrete]
+// CHECK:STDOUT:   %pattern_type.860: type = pattern_type %tuple.type.784 [concrete]
+// CHECK:STDOUT:   %EmptyRange.type: type = generic_class_type @EmptyRange [concrete]
+// CHECK:STDOUT:   %EmptyRange.generic: %EmptyRange.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %Make.type.bd2: type = fn_type @Make, @EmptyRange(%T) [symbolic]
+// CHECK:STDOUT:   %Make.ed1: %Make.type.bd2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %tuple.type.24b: type = tuple_type (type, type) [concrete]
+// CHECK:STDOUT:   %EmptyRange.2f3: type = class_type @EmptyRange, @EmptyRange(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %Make.type.4fb: type = fn_type @Make, @EmptyRange(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %Make.743: %Make.type.4fb = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.c28: type = ptr_type %empty_struct_type [concrete]
+// CHECK:STDOUT:   %Make.specific_fn: <specific function> = specific_function %Make.743, @Make(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %Iterate.type: type = facet_type <@Iterate> [concrete]
+// CHECK:STDOUT:   %NewCursor.type.427: type = fn_type @NewCursor.1 [concrete]
+// CHECK:STDOUT:   %NewCursor.type.f5f: type = fn_type @NewCursor.2, @impl(%T) [symbolic]
+// CHECK:STDOUT:   %NewCursor.ec1: %NewCursor.type.f5f = struct_value () [symbolic]
+// CHECK:STDOUT:   %Next.type.264: type = fn_type @Next.1, @impl(%T) [symbolic]
+// CHECK:STDOUT:   %Next.08e: %Next.type.264 = struct_value () [symbolic]
+// CHECK:STDOUT:   %HasValue.type.f81: type = fn_type @HasValue, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %HasValue.6fd: %HasValue.type.f81 = struct_value () [symbolic]
+// CHECK:STDOUT:   %Get.type.b8f: type = fn_type @Get, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %Get.9c8: %Get.type.b8f = struct_value () [symbolic]
+// CHECK:STDOUT:   %Iterate.impl_witness.6db: <witness> = impl_witness imports.%Iterate.impl_witness_table, @impl(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %NewCursor.type.d32: type = fn_type @NewCursor.2, @impl(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %NewCursor.763: %NewCursor.type.d32 = struct_value () [concrete]
+// CHECK:STDOUT:   %Next.type.482: type = fn_type @Next.1, @impl(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %Next.e60: %Next.type.482 = struct_value () [concrete]
+// CHECK:STDOUT:   %Iterate.facet: %Iterate.type = facet_value %EmptyRange.2f3, (%Iterate.impl_witness.6db) [concrete]
+// CHECK:STDOUT:   %.2bf: type = fn_type_with_self_type %NewCursor.type.427, %Iterate.facet [concrete]
+// CHECK:STDOUT:   %NewCursor.specific_fn: <specific function> = specific_function %NewCursor.763, @NewCursor.2(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %Next.type.941: type = fn_type @Next.2 [concrete]
+// CHECK:STDOUT:   %.6aa: type = fn_type_with_self_type %Next.type.941, %Iterate.facet [concrete]
+// CHECK:STDOUT:   %Optional.79e: type = class_type @Optional, @Optional(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %Next.specific_fn: <specific function> = specific_function %Next.e60, @Next.1(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %HasValue.type.414: type = fn_type @HasValue, @Optional(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %HasValue.4f9: %HasValue.type.414 = struct_value () [concrete]
+// CHECK:STDOUT:   %Get.type.ebd: type = fn_type @Get, @Optional(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %Get.d99: %Get.type.ebd = struct_value () [concrete]
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.4f9, @HasValue(%tuple.type.784) [concrete]
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.d99, @Get(%tuple.type.784) [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.EmptyRange: %EmptyRange.type = import_ref Main//empty_range, EmptyRange, loaded [concrete = constants.%EmptyRange.generic]
+// CHECK:STDOUT:   %Main.import_ref.6ea: @EmptyRange.%Make.type (%Make.type.bd2) = import_ref Main//empty_range, loc5_21, loaded [symbolic = @EmptyRange.%Make (constants.%Make.ed1)]
+// CHECK:STDOUT:   %Main.import_ref.6ce = import_ref Main//empty_range, loc7_68, unloaded
+// CHECK:STDOUT:   %Main.import_ref.999 = import_ref Main//empty_range, loc7_68, unloaded
+// CHECK:STDOUT:   %Main.import_ref.57b: @impl.%NewCursor.type (%NewCursor.type.f5f) = import_ref Main//empty_range, loc8_38, loaded [symbolic = @impl.%NewCursor (constants.%NewCursor.ec1)]
+// CHECK:STDOUT:   %Main.import_ref.170: @impl.%Next.type (%Next.type.264) = import_ref Main//empty_range, loc11_58, loaded [symbolic = @impl.%Next (constants.%Next.08e)]
+// CHECK:STDOUT:   %Iterate.impl_witness_table = impl_witness_table (%Main.import_ref.6ce, %Main.import_ref.999, %Main.import_ref.57b, %Main.import_ref.170), @impl [concrete]
+// CHECK:STDOUT:   %Main.import_ref.7f9: @Optional.%HasValue.type (%HasValue.type.f81) = import_ref Main//empty_range, inst136 [indirect], loaded [symbolic = @Optional.%HasValue (constants.%HasValue.6fd)]
+// CHECK:STDOUT:   %Main.import_ref.d10: @Optional.%Get.type (%Get.type.b8f) = import_ref Main//empty_range, inst137 [indirect], loaded [symbolic = @Optional.%Get (constants.%Get.9c8)]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Run() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.831 = binding_pattern a [concrete]
+// CHECK:STDOUT:     %b.patt: %pattern_type.831 = binding_pattern b [concrete]
+// CHECK:STDOUT:     %.loc10_25: %pattern_type.860 = tuple_pattern (%a.patt, %b.patt) [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %EmptyRange.ref: %EmptyRange.type = name_ref EmptyRange, imports.%Main.EmptyRange [concrete = constants.%EmptyRange.generic]
+// CHECK:STDOUT:   %bool.make_type.loc10_42: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:   %bool.make_type.loc10_48: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:   %.loc10_52: %tuple.type.24b = tuple_literal (%bool.make_type.loc10_42, %bool.make_type.loc10_48)
+// CHECK:STDOUT:   %.loc10_53.1: type = value_of_initializer %bool.make_type.loc10_42 [concrete = bool]
+// CHECK:STDOUT:   %.loc10_53.2: type = converted %bool.make_type.loc10_42, %.loc10_53.1 [concrete = bool]
+// CHECK:STDOUT:   %.loc10_53.3: type = value_of_initializer %bool.make_type.loc10_48 [concrete = bool]
+// CHECK:STDOUT:   %.loc10_53.4: type = converted %bool.make_type.loc10_48, %.loc10_53.3 [concrete = bool]
+// CHECK:STDOUT:   %.loc10_53.5: type = converted %.loc10_52, constants.%tuple.type.784 [concrete = constants.%tuple.type.784]
+// CHECK:STDOUT:   %EmptyRange: type = class_type @EmptyRange, @EmptyRange(constants.%tuple.type.784) [concrete = constants.%EmptyRange.2f3]
+// CHECK:STDOUT:   %.loc10_54: %Make.type.4fb = specific_constant imports.%Main.import_ref.6ea, @EmptyRange(constants.%tuple.type.784) [concrete = constants.%Make.743]
+// CHECK:STDOUT:   %Make.ref: %Make.type.4fb = name_ref Make, %.loc10_54 [concrete = constants.%Make.743]
+// CHECK:STDOUT:   %Make.specific_fn: <specific function> = specific_function %Make.ref, @Make(constants.%tuple.type.784) [concrete = constants.%Make.specific_fn]
+// CHECK:STDOUT:   %.loc10_60.1: ref %EmptyRange.2f3 = temporary_storage
+// CHECK:STDOUT:   %Make.call: init %EmptyRange.2f3 = call %Make.specific_fn() to %.loc10_60.1
+// CHECK:STDOUT:   %.loc10_60.2: ref %EmptyRange.2f3 = temporary %.loc10_60.1, %Make.call
+// CHECK:STDOUT:   %impl.elem2: %.2bf = impl_witness_access constants.%Iterate.impl_witness.6db, element2 [concrete = constants.%NewCursor.763]
+// CHECK:STDOUT:   %bound_method.loc10_61.1: <bound method> = bound_method %.loc10_60.2, %impl.elem2
+// CHECK:STDOUT:   %specific_fn.loc10_61.1: <specific function> = specific_function %impl.elem2, @NewCursor.2(constants.%tuple.type.784) [concrete = constants.%NewCursor.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc10_61.2: <bound method> = bound_method %.loc10_60.2, %specific_fn.loc10_61.1
+// CHECK:STDOUT:   %.loc10_60.3: %EmptyRange.2f3 = bind_value %.loc10_60.2
+// CHECK:STDOUT:   %NewCursor.call: init %empty_struct_type = call %bound_method.loc10_61.2(%.loc10_60.3)
+// CHECK:STDOUT:   %var: ref %empty_struct_type = var invalid
+// CHECK:STDOUT:   assign %var, %NewCursor.call
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.next:
+// CHECK:STDOUT:   %addr: %ptr.c28 = addr_of %var
+// CHECK:STDOUT:   %impl.elem3: %.6aa = impl_witness_access constants.%Iterate.impl_witness.6db, element3 [concrete = constants.%Next.e60]
+// CHECK:STDOUT:   %bound_method.loc10_61.3: <bound method> = bound_method %.loc10_60.2, %impl.elem3
+// CHECK:STDOUT:   %specific_fn.loc10_61.2: <specific function> = specific_function %impl.elem3, @Next.1(constants.%tuple.type.784) [concrete = constants.%Next.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc10_61.4: <bound method> = bound_method %.loc10_60.2, %specific_fn.loc10_61.2
+// CHECK:STDOUT:   %.loc10_61.1: ref %Optional.79e = temporary_storage
+// CHECK:STDOUT:   %.loc10_60.4: %EmptyRange.2f3 = bind_value %.loc10_60.2
+// CHECK:STDOUT:   %Next.call: init %Optional.79e = call %bound_method.loc10_61.4(%.loc10_60.4, %addr) to %.loc10_61.1
+// CHECK:STDOUT:   %.loc10_61.2: ref %Optional.79e = temporary %.loc10_61.1, %Next.call
+// CHECK:STDOUT:   %.loc10_61.3: %HasValue.type.414 = specific_constant imports.%Main.import_ref.7f9, @Optional(constants.%tuple.type.784) [concrete = constants.%HasValue.4f9]
+// CHECK:STDOUT:   %HasValue.ref: %HasValue.type.414 = name_ref HasValue, %.loc10_61.3 [concrete = constants.%HasValue.4f9]
+// CHECK:STDOUT:   %HasValue.bound: <bound method> = bound_method %.loc10_61.2, %HasValue.ref
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.ref, @HasValue(constants.%tuple.type.784) [concrete = constants.%HasValue.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc10_61.5: <bound method> = bound_method %.loc10_61.2, %HasValue.specific_fn
+// CHECK:STDOUT:   %.loc10_61.4: %Optional.79e = bind_value %.loc10_61.2
+// CHECK:STDOUT:   %HasValue.call: init bool = call %bound_method.loc10_61.5(%.loc10_61.4)
+// CHECK:STDOUT:   %.loc10_61.5: bool = value_of_initializer %HasValue.call
+// CHECK:STDOUT:   %.loc10_61.6: bool = converted %HasValue.call, %.loc10_61.5
+// CHECK:STDOUT:   if %.loc10_61.6 br !for.body else br !for.done
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.body:
+// CHECK:STDOUT:   %.loc10_61.7: %Get.type.ebd = specific_constant imports.%Main.import_ref.d10, @Optional(constants.%tuple.type.784) [concrete = constants.%Get.d99]
+// CHECK:STDOUT:   %Get.ref: %Get.type.ebd = name_ref Get, %.loc10_61.7 [concrete = constants.%Get.d99]
+// CHECK:STDOUT:   %Get.bound: <bound method> = bound_method %.loc10_61.2, %Get.ref
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.ref, @Get(constants.%tuple.type.784) [concrete = constants.%Get.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc10_61.6: <bound method> = bound_method %.loc10_61.2, %Get.specific_fn
+// CHECK:STDOUT:   %.loc10_61.8: ref %tuple.type.784 = temporary_storage
+// CHECK:STDOUT:   %.loc10_61.9: %Optional.79e = bind_value %.loc10_61.2
+// CHECK:STDOUT:   %Get.call: init %tuple.type.784 = call %bound_method.loc10_61.6(%.loc10_61.9) to %.loc10_61.8
+// CHECK:STDOUT:   %.loc10_61.10: ref %tuple.type.784 = temporary %.loc10_61.8, %Get.call
+// CHECK:STDOUT:   %tuple.elem0: ref bool = tuple_access %.loc10_61.10, element0
+// CHECK:STDOUT:   %tuple.elem1: ref bool = tuple_access %.loc10_61.10, element1
+// CHECK:STDOUT:   %.loc10_12.1: type = splice_block %.loc10_12.3 [concrete = bool] {
+// CHECK:STDOUT:     %bool.make_type.loc10_12: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:     %.loc10_12.2: type = value_of_initializer %bool.make_type.loc10_12 [concrete = bool]
+// CHECK:STDOUT:     %.loc10_12.3: type = converted %bool.make_type.loc10_12, %.loc10_12.2 [concrete = bool]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc10_61.11: bool = bind_value %tuple.elem0
+// CHECK:STDOUT:   %a: bool = bind_name a, %.loc10_61.11
+// CHECK:STDOUT:   %.loc10_21.1: type = splice_block %.loc10_21.3 [concrete = bool] {
+// CHECK:STDOUT:     %bool.make_type.loc10_21: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:     %.loc10_21.2: type = value_of_initializer %bool.make_type.loc10_21 [concrete = bool]
+// CHECK:STDOUT:     %.loc10_21.3: type = converted %bool.make_type.loc10_21, %.loc10_21.2 [concrete = bool]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc10_61.12: bool = bind_value %tuple.elem1
+// CHECK:STDOUT:   %b: bool = bind_name b, %.loc10_61.12
+// CHECK:STDOUT:   %Body.ref: %Body.type = name_ref Body, file.%Body.decl [concrete = constants.%Body]
+// CHECK:STDOUT:   %a.ref: bool = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: bool = name_ref b, %b
+// CHECK:STDOUT:   %Body.call: init %empty_tuple.type = call %Body.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.done:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- tuple_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %pattern_type.c48: type = pattern_type %C [concrete]
+// CHECK:STDOUT:   %Body.type: type = fn_type @Body [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %Body: %Body.type = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.c28: type = ptr_type %empty_struct_type [concrete]
+// CHECK:STDOUT:   %tuple.type.56b: type = tuple_type (%C, %C) [concrete]
+// CHECK:STDOUT:   %pattern_type.99e: type = pattern_type %tuple.type.56b [concrete]
+// CHECK:STDOUT:   %EmptyRange.type: type = generic_class_type @EmptyRange [concrete]
+// CHECK:STDOUT:   %EmptyRange.generic: %EmptyRange.type = struct_value () [concrete]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %Make.type.bd2: type = fn_type @Make, @EmptyRange(%T) [symbolic]
+// CHECK:STDOUT:   %Make.ed1: %Make.type.bd2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %tuple.type.24b: type = tuple_type (type, type) [concrete]
+// CHECK:STDOUT:   %EmptyRange.90a: type = class_type @EmptyRange, @EmptyRange(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %Make.type.829: type = fn_type @Make, @EmptyRange(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %Make.d4f: %Make.type.829 = struct_value () [concrete]
+// CHECK:STDOUT:   %Make.specific_fn: <specific function> = specific_function %Make.d4f, @Make(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %Iterate.type: type = facet_type <@Iterate> [concrete]
+// CHECK:STDOUT:   %NewCursor.type.427: type = fn_type @NewCursor.1 [concrete]
+// CHECK:STDOUT:   %NewCursor.type.f5f: type = fn_type @NewCursor.2, @impl(%T) [symbolic]
+// CHECK:STDOUT:   %NewCursor.ec1: %NewCursor.type.f5f = struct_value () [symbolic]
+// CHECK:STDOUT:   %Next.type.264: type = fn_type @Next.1, @impl(%T) [symbolic]
+// CHECK:STDOUT:   %Next.08e: %Next.type.264 = struct_value () [symbolic]
+// CHECK:STDOUT:   %HasValue.type.f81: type = fn_type @HasValue, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %HasValue.6fd: %HasValue.type.f81 = struct_value () [symbolic]
+// CHECK:STDOUT:   %Get.type.b8f: type = fn_type @Get, @Optional(%T) [symbolic]
+// CHECK:STDOUT:   %Get.9c8: %Get.type.b8f = struct_value () [symbolic]
+// CHECK:STDOUT:   %Iterate.impl_witness.627: <witness> = impl_witness imports.%Iterate.impl_witness_table, @impl(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %NewCursor.type.59c: type = fn_type @NewCursor.2, @impl(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %NewCursor.263: %NewCursor.type.59c = struct_value () [concrete]
+// CHECK:STDOUT:   %Next.type.70f: type = fn_type @Next.1, @impl(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %Next.852: %Next.type.70f = struct_value () [concrete]
+// CHECK:STDOUT:   %Iterate.facet: %Iterate.type = facet_value %EmptyRange.90a, (%Iterate.impl_witness.627) [concrete]
+// CHECK:STDOUT:   %.8bd: type = fn_type_with_self_type %NewCursor.type.427, %Iterate.facet [concrete]
+// CHECK:STDOUT:   %NewCursor.specific_fn: <specific function> = specific_function %NewCursor.263, @NewCursor.2(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %Next.type.941: type = fn_type @Next.2 [concrete]
+// CHECK:STDOUT:   %.8d0: type = fn_type_with_self_type %Next.type.941, %Iterate.facet [concrete]
+// CHECK:STDOUT:   %Optional.657: type = class_type @Optional, @Optional(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %Next.specific_fn: <specific function> = specific_function %Next.852, @Next.1(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %HasValue.type.2f1: type = fn_type @HasValue, @Optional(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %HasValue.bef: %HasValue.type.2f1 = struct_value () [concrete]
+// CHECK:STDOUT:   %Get.type.4a4: type = fn_type @Get, @Optional(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %Get.8a7: %Get.type.4a4 = struct_value () [concrete]
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.bef, @HasValue(%tuple.type.56b) [concrete]
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.8a7, @Get(%tuple.type.56b) [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.EmptyRange: %EmptyRange.type = import_ref Main//empty_range, EmptyRange, loaded [concrete = constants.%EmptyRange.generic]
+// CHECK:STDOUT:   %Main.import_ref.6ea: @EmptyRange.%Make.type (%Make.type.bd2) = import_ref Main//empty_range, loc5_21, loaded [symbolic = @EmptyRange.%Make (constants.%Make.ed1)]
+// CHECK:STDOUT:   %Main.import_ref.6ce = import_ref Main//empty_range, loc7_68, unloaded
+// CHECK:STDOUT:   %Main.import_ref.999 = import_ref Main//empty_range, loc7_68, unloaded
+// CHECK:STDOUT:   %Main.import_ref.57b: @impl.%NewCursor.type (%NewCursor.type.f5f) = import_ref Main//empty_range, loc8_38, loaded [symbolic = @impl.%NewCursor (constants.%NewCursor.ec1)]
+// CHECK:STDOUT:   %Main.import_ref.170: @impl.%Next.type (%Next.type.264) = import_ref Main//empty_range, loc11_58, loaded [symbolic = @impl.%Next (constants.%Next.08e)]
+// CHECK:STDOUT:   %Iterate.impl_witness_table = impl_witness_table (%Main.import_ref.6ce, %Main.import_ref.999, %Main.import_ref.57b, %Main.import_ref.170), @impl [concrete]
+// CHECK:STDOUT:   %Main.import_ref.7f9: @Optional.%HasValue.type (%HasValue.type.f81) = import_ref Main//empty_range, inst136 [indirect], loaded [symbolic = @Optional.%HasValue (constants.%HasValue.6fd)]
+// CHECK:STDOUT:   %Main.import_ref.d10: @Optional.%Get.type (%Get.type.b8f) = import_ref Main//empty_range, inst137 [indirect], loaded [symbolic = @Optional.%Get (constants.%Get.9c8)]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Run() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.c48 = binding_pattern a [concrete]
+// CHECK:STDOUT:     %b.patt: %pattern_type.c48 = binding_pattern b [concrete]
+// CHECK:STDOUT:     %.loc12_19: %pattern_type.99e = tuple_pattern (%a.patt, %b.patt) [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %EmptyRange.ref: %EmptyRange.type = name_ref EmptyRange, imports.%Main.EmptyRange [concrete = constants.%EmptyRange.generic]
+// CHECK:STDOUT:   %C.ref.loc12_36: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %C.ref.loc12_39: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %.loc12_40: %tuple.type.24b = tuple_literal (%C.ref.loc12_36, %C.ref.loc12_39)
+// CHECK:STDOUT:   %.loc12_41: type = converted %.loc12_40, constants.%tuple.type.56b [concrete = constants.%tuple.type.56b]
+// CHECK:STDOUT:   %EmptyRange: type = class_type @EmptyRange, @EmptyRange(constants.%tuple.type.56b) [concrete = constants.%EmptyRange.90a]
+// CHECK:STDOUT:   %.loc12_42: %Make.type.829 = specific_constant imports.%Main.import_ref.6ea, @EmptyRange(constants.%tuple.type.56b) [concrete = constants.%Make.d4f]
+// CHECK:STDOUT:   %Make.ref: %Make.type.829 = name_ref Make, %.loc12_42 [concrete = constants.%Make.d4f]
+// CHECK:STDOUT:   %Make.specific_fn: <specific function> = specific_function %Make.ref, @Make(constants.%tuple.type.56b) [concrete = constants.%Make.specific_fn]
+// CHECK:STDOUT:   %.loc12_48.1: ref %EmptyRange.90a = temporary_storage
+// CHECK:STDOUT:   %Make.call: init %EmptyRange.90a = call %Make.specific_fn() to %.loc12_48.1
+// CHECK:STDOUT:   %.loc12_48.2: ref %EmptyRange.90a = temporary %.loc12_48.1, %Make.call
+// CHECK:STDOUT:   %impl.elem2: %.8bd = impl_witness_access constants.%Iterate.impl_witness.627, element2 [concrete = constants.%NewCursor.263]
+// CHECK:STDOUT:   %bound_method.loc12_49.1: <bound method> = bound_method %.loc12_48.2, %impl.elem2
+// CHECK:STDOUT:   %specific_fn.loc12_49.1: <specific function> = specific_function %impl.elem2, @NewCursor.2(constants.%tuple.type.56b) [concrete = constants.%NewCursor.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_49.2: <bound method> = bound_method %.loc12_48.2, %specific_fn.loc12_49.1
+// CHECK:STDOUT:   %.loc12_48.3: %EmptyRange.90a = bind_value %.loc12_48.2
+// CHECK:STDOUT:   %NewCursor.call: init %empty_struct_type = call %bound_method.loc12_49.2(%.loc12_48.3)
+// CHECK:STDOUT:   %var: ref %empty_struct_type = var invalid
+// CHECK:STDOUT:   assign %var, %NewCursor.call
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.next:
+// CHECK:STDOUT:   %addr: %ptr.c28 = addr_of %var
+// CHECK:STDOUT:   %impl.elem3: %.8d0 = impl_witness_access constants.%Iterate.impl_witness.627, element3 [concrete = constants.%Next.852]
+// CHECK:STDOUT:   %bound_method.loc12_49.3: <bound method> = bound_method %.loc12_48.2, %impl.elem3
+// CHECK:STDOUT:   %specific_fn.loc12_49.2: <specific function> = specific_function %impl.elem3, @Next.1(constants.%tuple.type.56b) [concrete = constants.%Next.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_49.4: <bound method> = bound_method %.loc12_48.2, %specific_fn.loc12_49.2
+// CHECK:STDOUT:   %.loc12_49.1: ref %Optional.657 = temporary_storage
+// CHECK:STDOUT:   %.loc12_48.4: %EmptyRange.90a = bind_value %.loc12_48.2
+// CHECK:STDOUT:   %Next.call: init %Optional.657 = call %bound_method.loc12_49.4(%.loc12_48.4, %addr) to %.loc12_49.1
+// CHECK:STDOUT:   %.loc12_49.2: ref %Optional.657 = temporary %.loc12_49.1, %Next.call
+// CHECK:STDOUT:   %.loc12_49.3: %HasValue.type.2f1 = specific_constant imports.%Main.import_ref.7f9, @Optional(constants.%tuple.type.56b) [concrete = constants.%HasValue.bef]
+// CHECK:STDOUT:   %HasValue.ref: %HasValue.type.2f1 = name_ref HasValue, %.loc12_49.3 [concrete = constants.%HasValue.bef]
+// CHECK:STDOUT:   %HasValue.bound: <bound method> = bound_method %.loc12_49.2, %HasValue.ref
+// CHECK:STDOUT:   %HasValue.specific_fn: <specific function> = specific_function %HasValue.ref, @HasValue(constants.%tuple.type.56b) [concrete = constants.%HasValue.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_49.5: <bound method> = bound_method %.loc12_49.2, %HasValue.specific_fn
+// CHECK:STDOUT:   %.loc12_49.4: %Optional.657 = bind_value %.loc12_49.2
+// CHECK:STDOUT:   %HasValue.call: init bool = call %bound_method.loc12_49.5(%.loc12_49.4)
+// CHECK:STDOUT:   %.loc12_49.5: bool = value_of_initializer %HasValue.call
+// CHECK:STDOUT:   %.loc12_49.6: bool = converted %HasValue.call, %.loc12_49.5
+// CHECK:STDOUT:   if %.loc12_49.6 br !for.body else br !for.done
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.body:
+// CHECK:STDOUT:   %.loc12_49.7: %Get.type.4a4 = specific_constant imports.%Main.import_ref.d10, @Optional(constants.%tuple.type.56b) [concrete = constants.%Get.8a7]
+// CHECK:STDOUT:   %Get.ref: %Get.type.4a4 = name_ref Get, %.loc12_49.7 [concrete = constants.%Get.8a7]
+// CHECK:STDOUT:   %Get.bound: <bound method> = bound_method %.loc12_49.2, %Get.ref
+// CHECK:STDOUT:   %Get.specific_fn: <specific function> = specific_function %Get.ref, @Get(constants.%tuple.type.56b) [concrete = constants.%Get.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc12_49.6: <bound method> = bound_method %.loc12_49.2, %Get.specific_fn
+// CHECK:STDOUT:   %.loc12_49.8: ref %tuple.type.56b = temporary_storage
+// CHECK:STDOUT:   %.loc12_49.9: %Optional.657 = bind_value %.loc12_49.2
+// CHECK:STDOUT:   %Get.call: init %tuple.type.56b = call %bound_method.loc12_49.6(%.loc12_49.9) to %.loc12_49.8
+// CHECK:STDOUT:   %.loc12_49.10: ref %tuple.type.56b = temporary %.loc12_49.8, %Get.call
+// CHECK:STDOUT:   %tuple.elem0: ref %C = tuple_access %.loc12_49.10, element0
+// CHECK:STDOUT:   %tuple.elem1: ref %C = tuple_access %.loc12_49.10, element1
+// CHECK:STDOUT:   %C.ref.loc12_12: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %.loc12_49.11: %C = bind_value %tuple.elem0
+// CHECK:STDOUT:   %a: %C = bind_name a, %.loc12_49.11
+// CHECK:STDOUT:   %C.ref.loc12_18: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %.loc12_49.12: %C = bind_value %tuple.elem1
+// CHECK:STDOUT:   %b: %C = bind_name b, %.loc12_49.12
+// CHECK:STDOUT:   %Body.ref: %Body.type = name_ref Body, file.%Body.decl [concrete = constants.%Body]
+// CHECK:STDOUT:   %a.ref: %C = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: %C = name_ref b, %b
+// CHECK:STDOUT:   %Body.call: init %empty_tuple.type = call %Body.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   br !for.next
+// CHECK:STDOUT:
+// CHECK:STDOUT: !for.done:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 3 - 1
toolchain/lower/handle_aggregates.cpp

@@ -41,7 +41,9 @@ static auto GetAggregateElement(FunctionContext& context,
     case SemIR::ExprCategory::NotExpr:
     case SemIR::ExprCategory::Initializing:
     case SemIR::ExprCategory::Mixed:
-      CARBON_FATAL("Unexpected expression category for aggregate access");
+      CARBON_FATAL(
+          "Unexpected expression category for aggregate access into {0}",
+          context.sem_ir().insts().Get(aggr_inst_id));
 
     case SemIR::ExprCategory::Value: {
       auto aggr_type = context.GetTypeIdOfInst(aggr_inst_id);

+ 144 - 0
toolchain/lower/testdata/for/bindings.carbon

@@ -0,0 +1,144 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/for/bindings.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/for/bindings.carbon
+
+class EmptyRange(T:! type) {
+  impl as Core.Iterate where .CursorType = {} and .ElementType = T {
+    fn NewCursor[self: Self]() -> {} {
+      return {};
+    }
+    fn Next[self: Self](cursor: {}*) -> Core.Optional(T) {
+      return Core.Optional(T).None();
+    }
+  }
+}
+
+fn F(m: i32, n: i32*);
+
+fn For() {
+  var r: EmptyRange((i32, i32)) = {};
+
+  for ((m: i32, var n: i32) in r) {
+    F(m, &n);
+  }
+}
+
+// CHECK:STDOUT: ; ModuleID = 'bindings.carbon'
+// CHECK:STDOUT: source_filename = "bindings.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @EmptyRange.val.loc25_3 = internal constant {} zeroinitializer
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CF.Main(i32, ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CFor.Main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %r.var = alloca {}, align 8, !dbg !7
+// CHECK:STDOUT:   %var = alloca {}, align 8, !dbg !8
+// CHECK:STDOUT:   %.loc27_33.1.temp = alloca { i1, { i32, i32 } }, align 8, !dbg !8
+// CHECK:STDOUT:   %n.var = alloca i32, align 4, !dbg !9
+// CHECK:STDOUT:   %.loc27_33.8.temp = alloca { i32, i32 }, align 8, !dbg !8
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %r.var), !dbg !7
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %r.var, ptr align 1 @EmptyRange.val.loc25_3, i64 0, i1 false), !dbg !7
+// CHECK:STDOUT:   call void @"_CNewCursor.EmptyRange.Main:Iterate.Core.29d34654e802e24e"(ptr %r.var), !dbg !8
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %var), !dbg !8
+// CHECK:STDOUT:   br label %for.next, !dbg !8
+// CHECK:STDOUT:
+// CHECK:STDOUT: for.next:                                         ; preds = %for.body, %entry
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 12, ptr %.loc27_33.1.temp), !dbg !8
+// CHECK:STDOUT:   call void @"_CNext.EmptyRange.Main:Iterate.Core.29d34654e802e24e"(ptr %.loc27_33.1.temp, ptr %r.var, ptr %var), !dbg !8
+// CHECK:STDOUT:   %HasValue.call = call i1 @_CHasValue.Optional.Core.29d34654e802e24e(ptr %.loc27_33.1.temp), !dbg !8
+// CHECK:STDOUT:   br i1 %HasValue.call, label %for.body, label %for.done, !dbg !8
+// CHECK:STDOUT:
+// CHECK:STDOUT: for.body:                                         ; preds = %for.next
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 4, ptr %n.var), !dbg !9
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %.loc27_33.8.temp), !dbg !8
+// CHECK:STDOUT:   call void @_CGet.Optional.Core.29d34654e802e24e(ptr %.loc27_33.8.temp, ptr %.loc27_33.1.temp), !dbg !8
+// CHECK:STDOUT:   %tuple.elem0.tuple.elem = getelementptr inbounds nuw { i32, i32 }, ptr %.loc27_33.8.temp, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   %tuple.elem1.tuple.elem = getelementptr inbounds nuw { i32, i32 }, ptr %.loc27_33.8.temp, i32 0, i32 1, !dbg !8
+// CHECK:STDOUT:   %.loc27_33.11 = load i32, ptr %tuple.elem0.tuple.elem, align 4, !dbg !8
+// CHECK:STDOUT:   %.loc27_33.12 = load i32, ptr %tuple.elem1.tuple.elem, align 4, !dbg !8
+// CHECK:STDOUT:   store i32 %.loc27_33.12, ptr %n.var, align 4, !dbg !9
+// CHECK:STDOUT:   call void @_CF.Main(i32 %.loc27_33.11, ptr %n.var), !dbg !10
+// CHECK:STDOUT:   br label %for.next, !dbg !11
+// CHECK:STDOUT:
+// CHECK:STDOUT: for.done:                                         ; preds = %for.next
+// CHECK:STDOUT:   ret void, !dbg !12
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @"_CNewCursor.EmptyRange.Main:Iterate.Core.29d34654e802e24e"(ptr %self) !dbg !13 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @"_CNext.EmptyRange.Main:Iterate.Core.29d34654e802e24e"(ptr sret({ i1, { i32, i32 } }) %return, ptr %self, ptr %cursor) !dbg !15 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CNone.Optional.Core.29d34654e802e24e(ptr %return), !dbg !16
+// CHECK:STDOUT:   ret void, !dbg !17
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr i1 @_CHasValue.Optional.Core.29d34654e802e24e(ptr %self) !dbg !18 {
+// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, { i32, i32 } }, ptr %self, i32 0, i32 0, !dbg !20
+// CHECK:STDOUT:   %1 = load i1, ptr %has_value, align 1, !dbg !20
+// CHECK:STDOUT:   ret i1 %1, !dbg !21
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @_CGet.Optional.Core.29d34654e802e24e(ptr sret({ i32, i32 }) %return, ptr %self) !dbg !22 {
+// CHECK:STDOUT:   %value = getelementptr inbounds nuw { i1, { i32, i32 } }, ptr %self, i32 0, i32 1, !dbg !23
+// CHECK:STDOUT:   ret void, !dbg !24
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @_CNone.Optional.Core.29d34654e802e24e(ptr sret({ i1, { i32, i32 } }) %return) !dbg !25 {
+// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, { i32, i32 } }, ptr %return, i32 0, i32 0, !dbg !26
+// CHECK:STDOUT:   store i1 false, ptr %has_value, align 1, !dbg !26
+// CHECK:STDOUT:   ret void, !dbg !27
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 4, 3, 2, 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "bindings.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "For", linkageName: "_CFor.Main", scope: null, file: !3, line: 24, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 25, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 27, column: 7, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 27, column: 17, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 28, column: 5, scope: !4)
+// CHECK:STDOUT: !11 = !DILocation(line: 27, column: 3, scope: !4)
+// CHECK:STDOUT: !12 = !DILocation(line: 24, column: 1, scope: !4)
+// CHECK:STDOUT: !13 = distinct !DISubprogram(name: "NewCursor", linkageName: "_CNewCursor.EmptyRange.Main:Iterate.Core.29d34654e802e24e", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !14 = !DILocation(line: 14, column: 7, scope: !13)
+// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "Next", linkageName: "_CNext.EmptyRange.Main:Iterate.Core.29d34654e802e24e", scope: null, file: !3, line: 16, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !16 = !DILocation(line: 17, column: 14, scope: !15)
+// CHECK:STDOUT: !17 = !DILocation(line: 17, column: 7, scope: !15)
+// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "HasValue", linkageName: "_CHasValue.Optional.Core.29d34654e802e24e", scope: null, file: !19, line: 28, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !19 = !DIFile(filename: "{{.*}}/prelude/types/optional.carbon", directory: "")
+// CHECK:STDOUT: !20 = !DILocation(line: 28, column: 46, scope: !18)
+// CHECK:STDOUT: !21 = !DILocation(line: 28, column: 39, scope: !18)
+// CHECK:STDOUT: !22 = distinct !DISubprogram(name: "Get", linkageName: "_CGet.Optional.Core.29d34654e802e24e", scope: null, file: !19, line: 29, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !23 = !DILocation(line: 29, column: 38, scope: !22)
+// CHECK:STDOUT: !24 = !DILocation(line: 29, column: 31, scope: !22)
+// CHECK:STDOUT: !25 = distinct !DISubprogram(name: "None", linkageName: "_CNone.Optional.Core.29d34654e802e24e", scope: null, file: !19, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !26 = !DILocation(line: 20, column: 5, scope: !25)
+// CHECK:STDOUT: !27 = !DILocation(line: 21, column: 5, scope: !25)

+ 162 - 0
toolchain/lower/testdata/for/break_continue.carbon

@@ -0,0 +1,162 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/for/break_continue.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/for/break_continue.carbon
+
+class EmptyRange(T:! type) {
+  impl as Core.Iterate where .CursorType = {} and .ElementType = T {
+    fn NewCursor[self: Self]() -> {} {
+      return {};
+    }
+    fn Next[self: Self](cursor: {}*) -> Core.Optional(T) {
+      return Core.Optional(T).None();
+    }
+  }
+}
+
+fn F() -> bool;
+fn G() -> bool;
+fn H();
+
+fn For() {
+  var r: EmptyRange(i32) = {};
+  for (n: i32 in r) {
+    if (F()) { break; }
+    if (G()) { continue; }
+    H();
+  }
+}
+
+// CHECK:STDOUT: ; ModuleID = 'break_continue.carbon'
+// CHECK:STDOUT: source_filename = "break_continue.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @EmptyRange.val.loc27_3 = internal constant {} zeroinitializer
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare i1 @_CF.Main()
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare i1 @_CG.Main()
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CH.Main()
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CFor.Main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %r.var = alloca {}, align 8, !dbg !7
+// CHECK:STDOUT:   %var = alloca {}, align 8, !dbg !8
+// CHECK:STDOUT:   %.loc28_19.1.temp = alloca { i1, i32 }, align 8, !dbg !8
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %r.var), !dbg !7
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %r.var, ptr align 1 @EmptyRange.val.loc27_3, i64 0, i1 false), !dbg !7
+// CHECK:STDOUT:   call void @"_CNewCursor.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4"(ptr %r.var), !dbg !8
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %var), !dbg !8
+// CHECK:STDOUT:   br label %for.next, !dbg !8
+// CHECK:STDOUT:
+// CHECK:STDOUT: for.next:                                         ; preds = %if.else.loc30, %if.then.loc30, %entry
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %.loc28_19.1.temp), !dbg !8
+// CHECK:STDOUT:   call void @"_CNext.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4"(ptr %.loc28_19.1.temp, ptr %r.var, ptr %var), !dbg !8
+// CHECK:STDOUT:   %HasValue.call = call i1 @_CHasValue.Optional.Core.b88d1103f417c6d4(ptr %.loc28_19.1.temp), !dbg !8
+// CHECK:STDOUT:   br i1 %HasValue.call, label %for.body, label %for.done, !dbg !8
+// CHECK:STDOUT:
+// CHECK:STDOUT: for.body:                                         ; preds = %for.next
+// CHECK:STDOUT:   %Get.call = call i32 @_CGet.Optional.Core.b88d1103f417c6d4(ptr %.loc28_19.1.temp), !dbg !8
+// CHECK:STDOUT:   %F.call = call i1 @_CF.Main(), !dbg !9
+// CHECK:STDOUT:   br i1 %F.call, label %if.then.loc29, label %if.else.loc29, !dbg !10
+// CHECK:STDOUT:
+// CHECK:STDOUT: if.then.loc29:                                    ; preds = %for.body
+// CHECK:STDOUT:   br label %for.done, !dbg !11
+// CHECK:STDOUT:
+// CHECK:STDOUT: if.else.loc29:                                    ; preds = %for.body
+// CHECK:STDOUT:   %G.call = call i1 @_CG.Main(), !dbg !12
+// CHECK:STDOUT:   br i1 %G.call, label %if.then.loc30, label %if.else.loc30, !dbg !13
+// CHECK:STDOUT:
+// CHECK:STDOUT: if.then.loc30:                                    ; preds = %if.else.loc29
+// CHECK:STDOUT:   br label %for.next, !dbg !14
+// CHECK:STDOUT:
+// CHECK:STDOUT: if.else.loc30:                                    ; preds = %if.else.loc29
+// CHECK:STDOUT:   call void @_CH.Main(), !dbg !15
+// CHECK:STDOUT:   br label %for.next, !dbg !16
+// CHECK:STDOUT:
+// CHECK:STDOUT: for.done:                                         ; preds = %if.then.loc29, %for.next
+// CHECK:STDOUT:   ret void, !dbg !17
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @"_CNewCursor.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4"(ptr %self) !dbg !18 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !19
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @"_CNext.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4"(ptr sret({ i1, i32 }) %return, ptr %self, ptr %cursor) !dbg !20 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CNone.Optional.Core.b88d1103f417c6d4(ptr %return), !dbg !21
+// CHECK:STDOUT:   ret void, !dbg !22
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr i1 @_CHasValue.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !23 {
+// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 0, !dbg !25
+// CHECK:STDOUT:   %1 = load i1, ptr %has_value, align 1, !dbg !25
+// CHECK:STDOUT:   ret i1 %1, !dbg !26
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr i32 @_CGet.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !27 {
+// CHECK:STDOUT:   %value = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 1, !dbg !28
+// CHECK:STDOUT:   %1 = load i32, ptr %value, align 4, !dbg !28
+// CHECK:STDOUT:   ret i32 %1, !dbg !29
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @_CNone.Optional.Core.b88d1103f417c6d4(ptr sret({ i1, i32 }) %return) !dbg !30 {
+// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 0, !dbg !31
+// CHECK:STDOUT:   store i1 false, ptr %has_value, align 1, !dbg !31
+// CHECK:STDOUT:   ret void, !dbg !32
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 2, 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "break_continue.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "For", linkageName: "_CFor.Main", scope: null, file: !3, line: 26, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 27, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 28, column: 7, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 29, column: 9, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 29, column: 8, scope: !4)
+// CHECK:STDOUT: !11 = !DILocation(line: 29, column: 16, scope: !4)
+// CHECK:STDOUT: !12 = !DILocation(line: 30, column: 9, scope: !4)
+// CHECK:STDOUT: !13 = !DILocation(line: 30, column: 8, scope: !4)
+// CHECK:STDOUT: !14 = !DILocation(line: 30, column: 16, scope: !4)
+// CHECK:STDOUT: !15 = !DILocation(line: 31, column: 5, scope: !4)
+// CHECK:STDOUT: !16 = !DILocation(line: 28, column: 3, scope: !4)
+// CHECK:STDOUT: !17 = !DILocation(line: 26, column: 1, scope: !4)
+// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "NewCursor", linkageName: "_CNewCursor.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !19 = !DILocation(line: 14, column: 7, scope: !18)
+// CHECK:STDOUT: !20 = distinct !DISubprogram(name: "Next", linkageName: "_CNext.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4", scope: null, file: !3, line: 16, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !21 = !DILocation(line: 17, column: 14, scope: !20)
+// CHECK:STDOUT: !22 = !DILocation(line: 17, column: 7, scope: !20)
+// CHECK:STDOUT: !23 = distinct !DISubprogram(name: "HasValue", linkageName: "_CHasValue.Optional.Core.b88d1103f417c6d4", scope: null, file: !24, line: 28, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !24 = !DIFile(filename: "{{.*}}/prelude/types/optional.carbon", directory: "")
+// CHECK:STDOUT: !25 = !DILocation(line: 28, column: 46, scope: !23)
+// CHECK:STDOUT: !26 = !DILocation(line: 28, column: 39, scope: !23)
+// CHECK:STDOUT: !27 = distinct !DISubprogram(name: "Get", linkageName: "_CGet.Optional.Core.b88d1103f417c6d4", scope: null, file: !24, line: 29, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !28 = !DILocation(line: 29, column: 38, scope: !27)
+// CHECK:STDOUT: !29 = !DILocation(line: 29, column: 31, scope: !27)
+// CHECK:STDOUT: !30 = distinct !DISubprogram(name: "None", linkageName: "_CNone.Optional.Core.b88d1103f417c6d4", scope: null, file: !24, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !31 = !DILocation(line: 20, column: 5, scope: !30)
+// CHECK:STDOUT: !32 = !DILocation(line: 21, column: 5, scope: !30)

+ 147 - 0
toolchain/lower/testdata/for/for.carbon

@@ -0,0 +1,147 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/for/for.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/for/for.carbon
+
+class EmptyRange(T:! type) {
+  impl as Core.Iterate where .CursorType = {} and .ElementType = T {
+    fn NewCursor[self: Self]() -> {} {
+      return {};
+    }
+    fn Next[self: Self](cursor: {}*) -> Core.Optional(T) {
+      return Core.Optional(T).None();
+    }
+  }
+}
+
+fn F();
+fn G();
+fn H();
+
+fn For() {
+  var r: EmptyRange(i32) = {};
+
+  F();
+  for (n: i32 in r) {
+    G();
+  }
+  H();
+}
+
+// CHECK:STDOUT: ; ModuleID = 'for.carbon'
+// CHECK:STDOUT: source_filename = "for.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @EmptyRange.val.loc27_3 = internal constant {} zeroinitializer
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CF.Main()
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CG.Main()
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CH.Main()
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CFor.Main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %r.var = alloca {}, align 8, !dbg !7
+// CHECK:STDOUT:   %var = alloca {}, align 8, !dbg !8
+// CHECK:STDOUT:   %.loc30_19.1.temp = alloca { i1, i32 }, align 8, !dbg !8
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %r.var), !dbg !7
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %r.var, ptr align 1 @EmptyRange.val.loc27_3, i64 0, i1 false), !dbg !7
+// CHECK:STDOUT:   call void @_CF.Main(), !dbg !9
+// CHECK:STDOUT:   call void @"_CNewCursor.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4"(ptr %r.var), !dbg !8
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %var), !dbg !8
+// CHECK:STDOUT:   br label %for.next, !dbg !8
+// CHECK:STDOUT:
+// CHECK:STDOUT: for.next:                                         ; preds = %for.body, %entry
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %.loc30_19.1.temp), !dbg !8
+// CHECK:STDOUT:   call void @"_CNext.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4"(ptr %.loc30_19.1.temp, ptr %r.var, ptr %var), !dbg !8
+// CHECK:STDOUT:   %HasValue.call = call i1 @_CHasValue.Optional.Core.b88d1103f417c6d4(ptr %.loc30_19.1.temp), !dbg !8
+// CHECK:STDOUT:   br i1 %HasValue.call, label %for.body, label %for.done, !dbg !8
+// CHECK:STDOUT:
+// CHECK:STDOUT: for.body:                                         ; preds = %for.next
+// CHECK:STDOUT:   %Get.call = call i32 @_CGet.Optional.Core.b88d1103f417c6d4(ptr %.loc30_19.1.temp), !dbg !8
+// CHECK:STDOUT:   call void @_CG.Main(), !dbg !10
+// CHECK:STDOUT:   br label %for.next, !dbg !11
+// CHECK:STDOUT:
+// CHECK:STDOUT: for.done:                                         ; preds = %for.next
+// CHECK:STDOUT:   call void @_CH.Main(), !dbg !12
+// CHECK:STDOUT:   ret void, !dbg !13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @"_CNewCursor.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4"(ptr %self) !dbg !14 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !15
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @"_CNext.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4"(ptr sret({ i1, i32 }) %return, ptr %self, ptr %cursor) !dbg !16 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CNone.Optional.Core.b88d1103f417c6d4(ptr %return), !dbg !17
+// CHECK:STDOUT:   ret void, !dbg !18
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr i1 @_CHasValue.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !19 {
+// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 0, !dbg !21
+// CHECK:STDOUT:   %1 = load i1, ptr %has_value, align 1, !dbg !21
+// CHECK:STDOUT:   ret i1 %1, !dbg !22
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr i32 @_CGet.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !23 {
+// CHECK:STDOUT:   %value = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 1, !dbg !24
+// CHECK:STDOUT:   %1 = load i32, ptr %value, align 4, !dbg !24
+// CHECK:STDOUT:   ret i32 %1, !dbg !25
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @_CNone.Optional.Core.b88d1103f417c6d4(ptr sret({ i1, i32 }) %return) !dbg !26 {
+// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 0, !dbg !27
+// CHECK:STDOUT:   store i1 false, ptr %has_value, align 1, !dbg !27
+// CHECK:STDOUT:   ret void, !dbg !28
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 2, 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "for.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "For", linkageName: "_CFor.Main", scope: null, file: !3, line: 26, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 27, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 30, column: 7, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 29, column: 3, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 31, column: 5, scope: !4)
+// CHECK:STDOUT: !11 = !DILocation(line: 30, column: 3, scope: !4)
+// CHECK:STDOUT: !12 = !DILocation(line: 33, column: 3, scope: !4)
+// CHECK:STDOUT: !13 = !DILocation(line: 26, column: 1, scope: !4)
+// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "NewCursor", linkageName: "_CNewCursor.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !15 = !DILocation(line: 14, column: 7, scope: !14)
+// CHECK:STDOUT: !16 = distinct !DISubprogram(name: "Next", linkageName: "_CNext.EmptyRange.Main:Iterate.Core.b88d1103f417c6d4", scope: null, file: !3, line: 16, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !17 = !DILocation(line: 17, column: 14, scope: !16)
+// CHECK:STDOUT: !18 = !DILocation(line: 17, column: 7, scope: !16)
+// CHECK:STDOUT: !19 = distinct !DISubprogram(name: "HasValue", linkageName: "_CHasValue.Optional.Core.b88d1103f417c6d4", scope: null, file: !20, line: 28, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !20 = !DIFile(filename: "{{.*}}/prelude/types/optional.carbon", directory: "")
+// CHECK:STDOUT: !21 = !DILocation(line: 28, column: 46, scope: !19)
+// CHECK:STDOUT: !22 = !DILocation(line: 28, column: 39, scope: !19)
+// CHECK:STDOUT: !23 = distinct !DISubprogram(name: "Get", linkageName: "_CGet.Optional.Core.b88d1103f417c6d4", scope: null, file: !20, line: 29, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !24 = !DILocation(line: 29, column: 38, scope: !23)
+// CHECK:STDOUT: !25 = !DILocation(line: 29, column: 31, scope: !23)
+// CHECK:STDOUT: !26 = distinct !DISubprogram(name: "None", linkageName: "_CNone.Optional.Core.b88d1103f417c6d4", scope: null, file: !20, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !27 = !DILocation(line: 20, column: 5, scope: !26)
+// CHECK:STDOUT: !28 = !DILocation(line: 21, column: 5, scope: !26)

+ 1 - 1
toolchain/parse/typed_nodes.h

@@ -683,7 +683,7 @@ struct ForIn {
   Lex::InTokenIndex token;
 };
 
-// The `for (... in ...)` portion of a `for` statement.
+// The `(... in ...)` portion of a `for` statement.
 struct ForHeader {
   static constexpr auto Kind =
       NodeKind::ForHeader.Define({.bracketed_by = ForHeaderStart::Kind});

+ 6 - 0
toolchain/sem_ir/inst_namer.cpp

@@ -409,6 +409,12 @@ struct BranchNames {
   // Returns names for a branching parse node, or nullopt if not a branch.
   static auto For(Parse::NodeKind node_kind) -> std::optional<BranchNames> {
     switch (node_kind) {
+      case Parse::NodeKind::ForHeaderStart:
+        return {{.prefix = "for", .branch = "next"}};
+
+      case Parse::NodeKind::ForHeader:
+        return {{.prefix = "for", .branch_if = "body", .branch = "done"}};
+
       case Parse::NodeKind::IfExprIf:
         return {{.prefix = "if.expr",
                  .branch_if = "then",

+ 2 - 1
toolchain/sem_ir/typed_insts.h

@@ -1745,7 +1745,8 @@ struct VarStorage {
 
   TypeId type_id;
 
-  // If this storage was created for a `var` pattern, the pattern.
+  // If this storage was created for a `var` pattern, the pattern. Otherwise,
+  // such as the implicit storage in `for`, this is `None`.
   AbsoluteInstId pattern_id;
 };
 

+ 4 - 3
toolchain/testing/file_test.cpp

@@ -250,9 +250,10 @@ auto ToolchainFileTest::DoExtraCheckReplacements(std::string& check_line) const
     // The column happens to be right for FileStart, but the line is wrong.
     static RE2 file_token_re(R"((FileEnd.*column: |FileStart.*line: )( *\d+))");
     RE2::Replace(&check_line, file_token_re, R"(\1{{ *\\d+}})");
-  } else if (component_ == "check") {
-    // The path to the core package appears in some check diagnostics, and will
-    // differ between testing environments, so don't test it.
+  } else if (component_ == "check" || component_ == "lower") {
+    // The path to the core package appears in some check diagnostics and in
+    // debug information produced by lowering, and will differ between testing
+    // environments, so don't test it.
     // TODO: Consider adding a content keyword to name the core package, and
     // replace with that instead. Alternatively, consider adding the core
     // package to the VFS with a fixed name.

+ 19 - 0
toolchain/testing/testdata/min_prelude/for.carbon

@@ -0,0 +1,19 @@
+// 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
+//
+// EXTRA-ARGS: --custom-core --exclude-dump-file-prefix=min_prelude/
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/parts/as.carbon
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/parts/bool.carbon
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/parts/iterate.carbon
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/parts/optional.carbon
+
+// --- min_prelude/bool.carbon
+
+// A minimal prelude for testing `for` loops.
+package Core library "prelude";
+
+export import library "prelude/parts/as";
+export import library "prelude/parts/bool";
+export import library "prelude/parts/iterate";
+export import library "prelude/parts/optional";

+ 16 - 0
toolchain/testing/testdata/min_prelude/parts/iterate.carbon

@@ -0,0 +1,16 @@
+// 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
+
+// --- min_prelude/parts/iterate.carbon
+
+package Core library "prelude/parts/iterate";
+
+import library "prelude/parts/optional";
+
+interface Iterate {
+  let ElementType:! type;
+  let CursorType:! type;
+  fn NewCursor[self: Self]() -> CursorType;
+  fn Next[self: Self](cursor: CursorType*) -> Optional(ElementType);
+}

+ 27 - 0
toolchain/testing/testdata/min_prelude/parts/optional.carbon

@@ -0,0 +1,27 @@
+// 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
+
+// --- min_prelude/parts/optional.carbon
+
+package Core library "prelude/parts/optional";
+
+import library "prelude/parts/bool";
+
+class Optional(T:! type) {
+  fn None() -> Self {
+    returned var me: Self;
+    me.has_value = false;
+    return var;
+  }
+
+  fn Some(value: T) -> Self {
+    return {.has_value = true, .value = value};
+  }
+
+  fn HasValue[self: Self]() -> bool { return self.has_value; }
+  fn Get[self: Self]() -> T { return self.value; }
+
+  var has_value: bool;
+  var value: T;
+}