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

Stop creating fake integer literals as indexes for tuple initialization. (#3248)

This makes struct and tuple initialization basically identical; unify
them.
Richard Smith 2 лет назад
Родитель
Сommit
31d55da403
31 измененных файлов с 209 добавлено и 250 удалено
  1. 18 39
      toolchain/check/context.cpp
  2. 8 11
      toolchain/check/testdata/array/assign_var.carbon
  3. 13 21
      toolchain/check/testdata/basics/raw_and_textual_ir.carbon
  4. 7 13
      toolchain/check/testdata/basics/raw_ir.carbon
  5. 6 8
      toolchain/check/testdata/basics/textual_ir.carbon
  6. 6 8
      toolchain/check/testdata/index/fail_non_deterministic_type.carbon
  7. 3 4
      toolchain/check/testdata/index/fail_tuple_large_index.carbon
  8. 6 8
      toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon
  9. 6 8
      toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon
  10. 3 4
      toolchain/check/testdata/index/tuple_element_access.carbon
  11. 6 8
      toolchain/check/testdata/operators/assignment.carbon
  12. 12 16
      toolchain/check/testdata/operators/fail_assigment_to_non_assignable.carbon
  13. 6 8
      toolchain/check/testdata/pointer/address_of_lvalue.carbon
  14. 6 8
      toolchain/check/testdata/return/tuple.carbon
  15. 5 6
      toolchain/check/testdata/struct/tuple_as_element.carbon
  16. 10 14
      toolchain/check/testdata/tuples/nested_tuple.carbon
  17. 3 4
      toolchain/check/testdata/tuples/one_element.carbon
  18. 6 8
      toolchain/check/testdata/tuples/two_elements.carbon
  19. 11 0
      toolchain/lower/handle.cpp
  20. 4 4
      toolchain/lower/testdata/array/assign_return_value.carbon
  21. 16 16
      toolchain/lower/testdata/array/base.carbon
  22. 6 6
      toolchain/lower/testdata/function/call/tuple_param_with_return_slot.carbon
  23. 4 4
      toolchain/lower/testdata/index/array_element_access.carbon
  24. 10 10
      toolchain/lower/testdata/index/tuple_element_access.carbon
  25. 4 4
      toolchain/lower/testdata/index/tuple_return_value_access.carbon
  26. 4 4
      toolchain/lower/testdata/operators/assignment.carbon
  27. 1 1
      toolchain/lower/testdata/tuple/one_entry.carbon
  28. 4 4
      toolchain/lower/testdata/tuple/two_entries.carbon
  29. 9 0
      toolchain/sem_ir/file.cpp
  30. 4 1
      toolchain/sem_ir/node.h
  31. 2 0
      toolchain/sem_ir/node_kind.def

+ 18 - 39
toolchain/check/context.cpp

@@ -325,40 +325,13 @@ auto Context::Initialize(Parse::Node parse_node, SemIR::NodeId target_id,
       // that we can still initialize directly from one tuple element if
       // another one needs to be converted.
       switch (expr.kind()) {
-        case SemIR::NodeKind::TupleLiteral: {
-          auto elements = semantics_ir().GetNodeBlock(expr.GetAsTupleLiteral());
-          CopyOnWriteBlock new_block(semantics_ir(), expr.GetAsTupleLiteral());
-          bool is_in_place =
-              SemIR::GetInitializingRepresentation(semantics_ir(), type_id)
-                  .kind == SemIR::InitializingRepresentation::InPlace;
-          for (auto [i, elem_id] : llvm::enumerate(elements)) {
-            // TODO: Avoid creating the integer literal.
-            auto int_id = AddNode(SemIR::Node::IntegerLiteral::Make(
-                parse_node, CanonicalizeType(SemIR::NodeId::BuiltinIntegerType),
-                semantics_ir().AddIntegerLiteral(llvm::APInt(32, i))));
-            // TODO: We know the type already matches because we already invoked
-            // `ImplicitAsRequired`, but this will need to change once we stop
-            // doing that.
-            auto inner_target_type = semantics_ir().GetNode(elem_id).type_id();
-            // TODO: This should be placed into the return slot, and only
-            // created if needed.
-            auto inner_target_id = AddNode(SemIR::Node::TupleIndex::Make(
-                parse_node, inner_target_type, target_id, int_id));
-
-            auto new_id =
-                is_in_place ? InitializeAndFinalize(parse_node, inner_target_id,
-                                                    elem_id)
-                            : Initialize(parse_node, inner_target_id, elem_id);
-            new_block.Set(i, new_id);
-          }
-          return AddNode(SemIR::Node::TupleInit::Make(parse_node, type_id,
-                                                      expr_id, new_block.id()));
-        }
-
+        case SemIR::NodeKind::TupleLiteral:
         case SemIR::NodeKind::StructLiteral: {
-          auto elements =
-              semantics_ir().GetNodeBlock(expr.GetAsStructLiteral());
-          CopyOnWriteBlock new_block(semantics_ir(), expr.GetAsStructLiteral());
+          bool is_tuple = expr.kind() == SemIR::NodeKind::TupleLiteral;
+          auto elements_id =
+              is_tuple ? expr.GetAsTupleLiteral() : expr.GetAsStructLiteral();
+          auto elements = semantics_ir().GetNodeBlock(elements_id);
+          CopyOnWriteBlock new_block(semantics_ir(), elements_id);
           bool is_in_place =
               SemIR::GetInitializingRepresentation(semantics_ir(), type_id)
                   .kind == SemIR::InitializingRepresentation::InPlace;
@@ -369,18 +342,24 @@ auto Context::Initialize(Parse::Node parse_node, SemIR::NodeId target_id,
             auto inner_target_type = semantics_ir().GetNode(elem_id).type_id();
             // TODO: This should be placed into the return slot, and only
             // created if needed.
-            auto inner_target_id = AddNode(SemIR::Node::StructAccess::Make(
-                parse_node, inner_target_type, target_id,
-                SemIR::MemberIndex(i)));
-
+            auto inner_target_id =
+                AddNode(is_tuple ? SemIR::Node::TupleAccess::Make(
+                                       parse_node, inner_target_type, target_id,
+                                       SemIR::MemberIndex(i))
+                                 : SemIR::Node::StructAccess::Make(
+                                       parse_node, inner_target_type, target_id,
+                                       SemIR::MemberIndex(i)));
             auto new_id =
                 is_in_place ? InitializeAndFinalize(parse_node, inner_target_id,
                                                     elem_id)
                             : Initialize(parse_node, inner_target_id, elem_id);
             new_block.Set(i, new_id);
           }
-          return AddNode(SemIR::Node::StructInit::Make(
-              parse_node, type_id, expr_id, new_block.id()));
+          return AddNode(
+              is_tuple ? SemIR::Node::TupleInit::Make(parse_node, type_id,
+                                                      expr_id, new_block.id())
+                       : SemIR::Node::StructInit::Make(
+                             parse_node, type_id, expr_id, new_block.id()));
         }
 
         default:

+ 8 - 11
toolchain/check/testdata/array/assign_var.carbon

@@ -16,17 +16,14 @@ var b: [i32; 3] = a;
 // CHECK:STDOUT:   %.loc7_30: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc7_33: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc7_34: (i32, i32, i32) = tuple_literal (%.loc7_27, %.loc7_30, %.loc7_33)
-// CHECK:STDOUT:   %.loc7_35.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_35.2: ref i32 = tuple_index %a, %.loc7_35.1
-// CHECK:STDOUT:   %.loc7_35.3: init i32 = initialize_from %.loc7_27 to %.loc7_35.2
-// CHECK:STDOUT:   %.loc7_35.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc7_35.5: ref i32 = tuple_index %a, %.loc7_35.4
-// CHECK:STDOUT:   %.loc7_35.6: init i32 = initialize_from %.loc7_30 to %.loc7_35.5
-// CHECK:STDOUT:   %.loc7_35.7: i32 = int_literal 2
-// CHECK:STDOUT:   %.loc7_35.8: ref i32 = tuple_index %a, %.loc7_35.7
-// CHECK:STDOUT:   %.loc7_35.9: init i32 = initialize_from %.loc7_33 to %.loc7_35.8
-// CHECK:STDOUT:   %.loc7_35.10: init (i32, i32, i32) = tuple_init %.loc7_34, (%.loc7_35.3, %.loc7_35.6, %.loc7_35.9)
-// CHECK:STDOUT:   assign %a, %.loc7_35.10
+// CHECK:STDOUT:   %.loc7_35.1: ref i32 = tuple_access %a, member0
+// CHECK:STDOUT:   %.loc7_35.2: init i32 = initialize_from %.loc7_27 to %.loc7_35.1
+// CHECK:STDOUT:   %.loc7_35.3: ref i32 = tuple_access %a, member1
+// CHECK:STDOUT:   %.loc7_35.4: init i32 = initialize_from %.loc7_30 to %.loc7_35.3
+// CHECK:STDOUT:   %.loc7_35.5: ref i32 = tuple_access %a, member2
+// CHECK:STDOUT:   %.loc7_35.6: init i32 = initialize_from %.loc7_33 to %.loc7_35.5
+// CHECK:STDOUT:   %.loc7_35.7: init (i32, i32, i32) = tuple_init %.loc7_34, (%.loc7_35.2, %.loc7_35.4, %.loc7_35.6)
+// CHECK:STDOUT:   assign %a, %.loc7_35.7
 // CHECK:STDOUT:   %.loc8_14: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc8_15: type = array_type %.loc8_14, i32
 // CHECK:STDOUT:   %b: ref [i32; 3] = var "b"

+ 13 - 21
toolchain/check/testdata/basics/raw_and_textual_ir.carbon

@@ -20,8 +20,6 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     integer_literals: [
 // CHECK:STDOUT:       2,
-// CHECK:STDOUT:       0,
-// CHECK:STDOUT:       1,
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     real_literals: [
 // CHECK:STDOUT:       {mantissa: 34, exponent: -1, is_decimal: 1},
@@ -58,14 +56,12 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:       {kind: BinaryOperatorAdd, arg0: node+0, arg1: node+6, type: type0},
 // CHECK:STDOUT:       {kind: RealLiteral, arg0: real0, type: type2},
 // CHECK:STDOUT:       {kind: TupleLiteral, arg0: block5, type: type3},
-// CHECK:STDOUT:       {kind: IntegerLiteral, arg0: int1, type: type0},
-// CHECK:STDOUT:       {kind: TupleIndex, arg0: node+4, arg1: node+10, type: type0},
-// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+7, arg1: node+11, type: type0},
-// CHECK:STDOUT:       {kind: IntegerLiteral, arg0: int2, type: type0},
-// CHECK:STDOUT:       {kind: TupleIndex, arg0: node+4, arg1: node+13, type: type2},
-// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+8, arg1: node+14, type: type2},
+// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+4, arg1: member0, type: type0},
+// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+7, arg1: node+10, type: type0},
+// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+4, arg1: member1, type: type2},
+// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+8, arg1: node+12, type: type2},
 // CHECK:STDOUT:       {kind: TupleInit, arg0: node+9, arg1: block6, type: type3},
-// CHECK:STDOUT:       {kind: ReturnExpression, arg0: node+16},
+// CHECK:STDOUT:       {kind: ReturnExpression, arg0: node+14},
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     node_blocks: [
 // CHECK:STDOUT:       [
@@ -95,16 +91,14 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:         node+13,
 // CHECK:STDOUT:         node+14,
 // CHECK:STDOUT:         node+15,
-// CHECK:STDOUT:         node+16,
-// CHECK:STDOUT:         node+17,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
 // CHECK:STDOUT:         node+7,
 // CHECK:STDOUT:         node+8,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
-// CHECK:STDOUT:         node+12,
-// CHECK:STDOUT:         node+15,
+// CHECK:STDOUT:         node+11,
+// CHECK:STDOUT:         node+13,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
 // CHECK:STDOUT:         node+5,
@@ -121,12 +115,10 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:   %.loc12_13: i32 = add %n, %.loc12_15
 // CHECK:STDOUT:   %.loc12_18: f64 = real_literal 34e-1
 // CHECK:STDOUT:   %.loc12_21: (i32, f64) = tuple_literal (%.loc12_13, %.loc12_18)
-// CHECK:STDOUT:   %.loc12_22.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc12_22.2: ref i32 = tuple_index %return, %.loc12_22.1
-// CHECK:STDOUT:   %.loc12_22.3: init i32 = initialize_from %.loc12_13 to %.loc12_22.2
-// CHECK:STDOUT:   %.loc12_22.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc12_22.5: ref f64 = tuple_index %return, %.loc12_22.4
-// CHECK:STDOUT:   %.loc12_22.6: init f64 = initialize_from %.loc12_18 to %.loc12_22.5
-// CHECK:STDOUT:   %.loc12_22.7: init (i32, f64) = tuple_init %.loc12_21, (%.loc12_22.3, %.loc12_22.6)
-// CHECK:STDOUT:   return %.loc12_22.7
+// CHECK:STDOUT:   %.loc12_22.1: ref i32 = tuple_access %return, member0
+// CHECK:STDOUT:   %.loc12_22.2: init i32 = initialize_from %.loc12_13 to %.loc12_22.1
+// CHECK:STDOUT:   %.loc12_22.3: ref f64 = tuple_access %return, member1
+// CHECK:STDOUT:   %.loc12_22.4: init f64 = initialize_from %.loc12_18 to %.loc12_22.3
+// CHECK:STDOUT:   %.loc12_22.5: init (i32, f64) = tuple_init %.loc12_21, (%.loc12_22.2, %.loc12_22.4)
+// CHECK:STDOUT:   return %.loc12_22.5
 // CHECK:STDOUT: }

+ 7 - 13
toolchain/check/testdata/basics/raw_ir.carbon

@@ -20,8 +20,6 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     integer_literals: [
 // CHECK:STDOUT:       2,
-// CHECK:STDOUT:       0,
-// CHECK:STDOUT:       1,
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     real_literals: [
 // CHECK:STDOUT:       {mantissa: 34, exponent: -1, is_decimal: 1},
@@ -58,14 +56,12 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:       {kind: BinaryOperatorAdd, arg0: node+0, arg1: node+6, type: type0},
 // CHECK:STDOUT:       {kind: RealLiteral, arg0: real0, type: type2},
 // CHECK:STDOUT:       {kind: TupleLiteral, arg0: block5, type: type3},
-// CHECK:STDOUT:       {kind: IntegerLiteral, arg0: int1, type: type0},
-// CHECK:STDOUT:       {kind: TupleIndex, arg0: node+4, arg1: node+10, type: type0},
-// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+7, arg1: node+11, type: type0},
-// CHECK:STDOUT:       {kind: IntegerLiteral, arg0: int2, type: type0},
-// CHECK:STDOUT:       {kind: TupleIndex, arg0: node+4, arg1: node+13, type: type2},
-// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+8, arg1: node+14, type: type2},
+// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+4, arg1: member0, type: type0},
+// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+7, arg1: node+10, type: type0},
+// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+4, arg1: member1, type: type2},
+// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+8, arg1: node+12, type: type2},
 // CHECK:STDOUT:       {kind: TupleInit, arg0: node+9, arg1: block6, type: type3},
-// CHECK:STDOUT:       {kind: ReturnExpression, arg0: node+16},
+// CHECK:STDOUT:       {kind: ReturnExpression, arg0: node+14},
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     node_blocks: [
 // CHECK:STDOUT:       [
@@ -95,16 +91,14 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:         node+13,
 // CHECK:STDOUT:         node+14,
 // CHECK:STDOUT:         node+15,
-// CHECK:STDOUT:         node+16,
-// CHECK:STDOUT:         node+17,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
 // CHECK:STDOUT:         node+7,
 // CHECK:STDOUT:         node+8,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
-// CHECK:STDOUT:         node+12,
-// CHECK:STDOUT:         node+15,
+// CHECK:STDOUT:         node+11,
+// CHECK:STDOUT:         node+13,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
 // CHECK:STDOUT:         node+5,

+ 6 - 8
toolchain/check/testdata/basics/textual_ir.carbon

@@ -22,12 +22,10 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:   %.loc12_13: i32 = add %n, %.loc12_15
 // CHECK:STDOUT:   %.loc12_18: f64 = real_literal 34e-1
 // CHECK:STDOUT:   %.loc12_21: (i32, f64) = tuple_literal (%.loc12_13, %.loc12_18)
-// CHECK:STDOUT:   %.loc12_22.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc12_22.2: ref i32 = tuple_index %return, %.loc12_22.1
-// CHECK:STDOUT:   %.loc12_22.3: init i32 = initialize_from %.loc12_13 to %.loc12_22.2
-// CHECK:STDOUT:   %.loc12_22.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc12_22.5: ref f64 = tuple_index %return, %.loc12_22.4
-// CHECK:STDOUT:   %.loc12_22.6: init f64 = initialize_from %.loc12_18 to %.loc12_22.5
-// CHECK:STDOUT:   %.loc12_22.7: init (i32, f64) = tuple_init %.loc12_21, (%.loc12_22.3, %.loc12_22.6)
-// CHECK:STDOUT:   return %.loc12_22.7
+// CHECK:STDOUT:   %.loc12_22.1: ref i32 = tuple_access %return, member0
+// CHECK:STDOUT:   %.loc12_22.2: init i32 = initialize_from %.loc12_13 to %.loc12_22.1
+// CHECK:STDOUT:   %.loc12_22.3: ref f64 = tuple_access %return, member1
+// CHECK:STDOUT:   %.loc12_22.4: init f64 = initialize_from %.loc12_18 to %.loc12_22.3
+// CHECK:STDOUT:   %.loc12_22.5: init (i32, f64) = tuple_init %.loc12_21, (%.loc12_22.2, %.loc12_22.4)
+// CHECK:STDOUT:   return %.loc12_22.5
 // CHECK:STDOUT: }

+ 6 - 8
toolchain/check/testdata/index/fail_non_deterministic_type.carbon

@@ -19,14 +19,12 @@ var c: i32 = a[b];
 // CHECK:STDOUT:   %.loc7_22: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc7_25: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc7_26: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_25)
-// CHECK:STDOUT:   %.loc7_27.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_27.2: ref i32 = tuple_index %a, %.loc7_27.1
-// CHECK:STDOUT:   %.loc7_27.3: init i32 = initialize_from %.loc7_22 to %.loc7_27.2
-// CHECK:STDOUT:   %.loc7_27.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc7_27.5: ref i32 = tuple_index %a, %.loc7_27.4
-// CHECK:STDOUT:   %.loc7_27.6: init i32 = initialize_from %.loc7_25 to %.loc7_27.5
-// CHECK:STDOUT:   %.loc7_27.7: init (i32, i32) = tuple_init %.loc7_26, (%.loc7_27.3, %.loc7_27.6)
-// CHECK:STDOUT:   assign %a, %.loc7_27.7
+// CHECK:STDOUT:   %.loc7_27.1: ref i32 = tuple_access %a, member0
+// CHECK:STDOUT:   %.loc7_27.2: init i32 = initialize_from %.loc7_22 to %.loc7_27.1
+// CHECK:STDOUT:   %.loc7_27.3: ref i32 = tuple_access %a, member1
+// CHECK:STDOUT:   %.loc7_27.4: init i32 = initialize_from %.loc7_25 to %.loc7_27.3
+// CHECK:STDOUT:   %.loc7_27.5: init (i32, i32) = tuple_init %.loc7_26, (%.loc7_27.2, %.loc7_27.4)
+// CHECK:STDOUT:   assign %a, %.loc7_27.5
 // CHECK:STDOUT:   %b: ref i32 = var "b"
 // CHECK:STDOUT:   %.loc8: i32 = int_literal 0
 // CHECK:STDOUT:   assign %b, %.loc8

+ 3 - 4
toolchain/check/testdata/index/fail_tuple_large_index.carbon

@@ -18,10 +18,9 @@ var c: i32 = b[0xFFFFFFFFFFFFFFFFF];
 // CHECK:STDOUT:   %a: ref (i32,) = var "a"
 // CHECK:STDOUT:   %.loc7_18: i32 = int_literal 12
 // CHECK:STDOUT:   %.loc7_21: (i32,) = tuple_literal (%.loc7_18)
-// CHECK:STDOUT:   %.loc7_22.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_22.2: ref i32 = tuple_index %a, %.loc7_22.1
-// CHECK:STDOUT:   %.loc7_22.3: init (i32,) = tuple_init %.loc7_21, (%.loc7_18)
-// CHECK:STDOUT:   assign %a, %.loc7_22.3
+// CHECK:STDOUT:   %.loc7_22.1: ref i32 = tuple_access %a, member0
+// CHECK:STDOUT:   %.loc7_22.2: init (i32,) = tuple_init %.loc7_21, (%.loc7_18)
+// CHECK:STDOUT:   assign %a, %.loc7_22.2
 // CHECK:STDOUT:   %.loc8: (type,) = tuple_literal (i32)
 // CHECK:STDOUT:   %b: ref (i32,) = var "b"
 // CHECK:STDOUT:   %.loc7_5: (i32,) = bind_value %a

+ 6 - 8
toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon

@@ -18,14 +18,12 @@ var b: i32 = a[2.6];
 // CHECK:STDOUT:   %.loc7_22: i32 = int_literal 12
 // CHECK:STDOUT:   %.loc7_26: i32 = int_literal 6
 // CHECK:STDOUT:   %.loc7_27: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_26)
-// CHECK:STDOUT:   %.loc7_28.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_28.2: ref i32 = tuple_index %a, %.loc7_28.1
-// CHECK:STDOUT:   %.loc7_28.3: init i32 = initialize_from %.loc7_22 to %.loc7_28.2
-// CHECK:STDOUT:   %.loc7_28.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc7_28.5: ref i32 = tuple_index %a, %.loc7_28.4
-// CHECK:STDOUT:   %.loc7_28.6: init i32 = initialize_from %.loc7_26 to %.loc7_28.5
-// CHECK:STDOUT:   %.loc7_28.7: init (i32, i32) = tuple_init %.loc7_27, (%.loc7_28.3, %.loc7_28.6)
-// CHECK:STDOUT:   assign %a, %.loc7_28.7
+// CHECK:STDOUT:   %.loc7_28.1: ref i32 = tuple_access %a, member0
+// CHECK:STDOUT:   %.loc7_28.2: init i32 = initialize_from %.loc7_22 to %.loc7_28.1
+// CHECK:STDOUT:   %.loc7_28.3: ref i32 = tuple_access %a, member1
+// CHECK:STDOUT:   %.loc7_28.4: init i32 = initialize_from %.loc7_26 to %.loc7_28.3
+// CHECK:STDOUT:   %.loc7_28.5: init (i32, i32) = tuple_init %.loc7_27, (%.loc7_28.2, %.loc7_28.4)
+// CHECK:STDOUT:   assign %a, %.loc7_28.5
 // CHECK:STDOUT:   %b: ref i32 = var "b"
 // CHECK:STDOUT:   %.loc11_16: f64 = real_literal 26e-1
 // CHECK:STDOUT:   %.loc11_19: ref <error> = tuple_index %a, <error>

+ 6 - 8
toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon

@@ -18,14 +18,12 @@ var b: i32 = a[2];
 // CHECK:STDOUT:   %.loc7_22: i32 = int_literal 12
 // CHECK:STDOUT:   %.loc7_26: i32 = int_literal 6
 // CHECK:STDOUT:   %.loc7_27: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_26)
-// CHECK:STDOUT:   %.loc7_28.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_28.2: ref i32 = tuple_index %a, %.loc7_28.1
-// CHECK:STDOUT:   %.loc7_28.3: init i32 = initialize_from %.loc7_22 to %.loc7_28.2
-// CHECK:STDOUT:   %.loc7_28.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc7_28.5: ref i32 = tuple_index %a, %.loc7_28.4
-// CHECK:STDOUT:   %.loc7_28.6: init i32 = initialize_from %.loc7_26 to %.loc7_28.5
-// CHECK:STDOUT:   %.loc7_28.7: init (i32, i32) = tuple_init %.loc7_27, (%.loc7_28.3, %.loc7_28.6)
-// CHECK:STDOUT:   assign %a, %.loc7_28.7
+// CHECK:STDOUT:   %.loc7_28.1: ref i32 = tuple_access %a, member0
+// CHECK:STDOUT:   %.loc7_28.2: init i32 = initialize_from %.loc7_22 to %.loc7_28.1
+// CHECK:STDOUT:   %.loc7_28.3: ref i32 = tuple_access %a, member1
+// CHECK:STDOUT:   %.loc7_28.4: init i32 = initialize_from %.loc7_26 to %.loc7_28.3
+// CHECK:STDOUT:   %.loc7_28.5: init (i32, i32) = tuple_init %.loc7_27, (%.loc7_28.2, %.loc7_28.4)
+// CHECK:STDOUT:   assign %a, %.loc7_28.5
 // CHECK:STDOUT:   %b: ref i32 = var "b"
 // CHECK:STDOUT:   %.loc11_16: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc11_17: ref <error> = tuple_index %a, <error>

+ 3 - 4
toolchain/check/testdata/index/tuple_element_access.carbon

@@ -15,10 +15,9 @@ var c: i32 = b[0];
 // CHECK:STDOUT:   %a: ref (i32,) = var "a"
 // CHECK:STDOUT:   %.loc7_18: i32 = int_literal 12
 // CHECK:STDOUT:   %.loc7_21: (i32,) = tuple_literal (%.loc7_18)
-// CHECK:STDOUT:   %.loc7_22.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_22.2: ref i32 = tuple_index %a, %.loc7_22.1
-// CHECK:STDOUT:   %.loc7_22.3: init (i32,) = tuple_init %.loc7_21, (%.loc7_18)
-// CHECK:STDOUT:   assign %a, %.loc7_22.3
+// CHECK:STDOUT:   %.loc7_22.1: ref i32 = tuple_access %a, member0
+// CHECK:STDOUT:   %.loc7_22.2: init (i32,) = tuple_init %.loc7_21, (%.loc7_18)
+// CHECK:STDOUT:   assign %a, %.loc7_22.2
 // CHECK:STDOUT:   %.loc8: (type,) = tuple_literal (i32)
 // CHECK:STDOUT:   %b: ref (i32,) = var "b"
 // CHECK:STDOUT:   %.loc7_5: (i32,) = bind_value %a

+ 6 - 8
toolchain/check/testdata/operators/assignment.carbon

@@ -40,14 +40,12 @@ fn Main() {
 // CHECK:STDOUT:   %.loc11_24: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc11_27: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc11_28: (i32, i32) = tuple_literal (%.loc11_24, %.loc11_27)
-// CHECK:STDOUT:   %.loc11_29.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc11_29.2: ref i32 = tuple_index %b, %.loc11_29.1
-// CHECK:STDOUT:   %.loc11_29.3: init i32 = initialize_from %.loc11_24 to %.loc11_29.2
-// CHECK:STDOUT:   %.loc11_29.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc11_29.5: ref i32 = tuple_index %b, %.loc11_29.4
-// CHECK:STDOUT:   %.loc11_29.6: init i32 = initialize_from %.loc11_27 to %.loc11_29.5
-// CHECK:STDOUT:   %.loc11_29.7: init (i32, i32) = tuple_init %.loc11_28, (%.loc11_29.3, %.loc11_29.6)
-// CHECK:STDOUT:   assign %b, %.loc11_29.7
+// CHECK:STDOUT:   %.loc11_29.1: ref i32 = tuple_access %b, member0
+// CHECK:STDOUT:   %.loc11_29.2: init i32 = initialize_from %.loc11_24 to %.loc11_29.1
+// CHECK:STDOUT:   %.loc11_29.3: ref i32 = tuple_access %b, member1
+// CHECK:STDOUT:   %.loc11_29.4: init i32 = initialize_from %.loc11_27 to %.loc11_29.3
+// CHECK:STDOUT:   %.loc11_29.5: init (i32, i32) = tuple_init %.loc11_28, (%.loc11_29.2, %.loc11_29.4)
+// CHECK:STDOUT:   assign %b, %.loc11_29.5
 // CHECK:STDOUT:   %.loc12_5: i32 = int_literal 0
 // CHECK:STDOUT:   %.loc12_6: ref i32 = tuple_index %b, %.loc12_5
 // CHECK:STDOUT:   %.loc12_10: i32 = int_literal 3

+ 12 - 16
toolchain/check/testdata/operators/fail_assigment_to_non_assignable.carbon

@@ -67,14 +67,12 @@ fn Main() {
 // CHECK:STDOUT:   %.loc21_13: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc21_16: i32 = int_literal 4
 // CHECK:STDOUT:   %.loc21_17: (i32, i32) = tuple_literal (%.loc21_13, %.loc21_16)
-// CHECK:STDOUT:   %.loc21_10.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc21_10.2: i32 = tuple_index %.loc21_8.2, %.loc21_10.1
-// CHECK:STDOUT:   %.loc21_10.3: init i32 = initialize_from %.loc21_13 to %.loc21_10.2
-// CHECK:STDOUT:   %.loc21_10.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc21_10.5: i32 = tuple_index %.loc21_8.2, %.loc21_10.4
-// CHECK:STDOUT:   %.loc21_10.6: init i32 = initialize_from %.loc21_16 to %.loc21_10.5
-// CHECK:STDOUT:   %.loc21_10.7: init (i32, i32) = tuple_init %.loc21_17, (%.loc21_10.3, %.loc21_10.6)
-// CHECK:STDOUT:   assign %.loc21_8.2, %.loc21_10.7
+// CHECK:STDOUT:   %.loc21_10.1: i32 = tuple_access %.loc21_8.2, member0
+// CHECK:STDOUT:   %.loc21_10.2: init i32 = initialize_from %.loc21_13 to %.loc21_10.1
+// CHECK:STDOUT:   %.loc21_10.3: i32 = tuple_access %.loc21_8.2, member1
+// CHECK:STDOUT:   %.loc21_10.4: init i32 = initialize_from %.loc21_16 to %.loc21_10.3
+// CHECK:STDOUT:   %.loc21_10.5: init (i32, i32) = tuple_init %.loc21_17, (%.loc21_10.2, %.loc21_10.4)
+// CHECK:STDOUT:   assign %.loc21_8.2, %.loc21_10.5
 // CHECK:STDOUT:   %.loc21_8.3: (i32, i32) = tuple_value %.loc21_8.2, (%.loc21_4, %.loc21_7)
 // CHECK:STDOUT:   %n: ref i32 = var "n"
 // CHECK:STDOUT:   %.loc22_16: i32 = int_literal 0
@@ -83,14 +81,12 @@ fn Main() {
 // CHECK:STDOUT:   %.loc26_13: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc26_16: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc26_17: (i32, i32) = tuple_literal (%.loc26_13, %.loc26_16)
-// CHECK:STDOUT:   %.loc26_10.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc26_10.2: i32 = tuple_index %.loc26_8.1, %.loc26_10.1
-// CHECK:STDOUT:   %.loc26_10.3: init i32 = initialize_from %.loc26_13 to %.loc26_10.2
-// CHECK:STDOUT:   %.loc26_10.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc26_10.5: i32 = tuple_index %.loc26_8.1, %.loc26_10.4
-// CHECK:STDOUT:   %.loc26_10.6: init i32 = initialize_from %.loc26_16 to %.loc26_10.5
-// CHECK:STDOUT:   %.loc26_10.7: init (i32, i32) = tuple_init %.loc26_17, (%.loc26_10.3, %.loc26_10.6)
-// CHECK:STDOUT:   assign %.loc26_8.1, %.loc26_10.7
+// CHECK:STDOUT:   %.loc26_10.1: i32 = tuple_access %.loc26_8.1, member0
+// CHECK:STDOUT:   %.loc26_10.2: init i32 = initialize_from %.loc26_13 to %.loc26_10.1
+// CHECK:STDOUT:   %.loc26_10.3: i32 = tuple_access %.loc26_8.1, member1
+// CHECK:STDOUT:   %.loc26_10.4: init i32 = initialize_from %.loc26_16 to %.loc26_10.3
+// CHECK:STDOUT:   %.loc26_10.5: init (i32, i32) = tuple_init %.loc26_17, (%.loc26_10.2, %.loc26_10.4)
+// CHECK:STDOUT:   assign %.loc26_8.1, %.loc26_10.5
 // CHECK:STDOUT:   %.loc22_7.1: i32 = bind_value %n
 // CHECK:STDOUT:   %.loc22_7.2: i32 = bind_value %n
 // CHECK:STDOUT:   %.loc26_8.2: (i32, i32) = tuple_value %.loc26_8.1, (%.loc22_7.1, %.loc22_7.2)

+ 6 - 8
toolchain/check/testdata/pointer/address_of_lvalue.carbon

@@ -55,14 +55,12 @@ fn F() {
 // CHECK:STDOUT:   %.loc14_24: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc14_27: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc14_28: (i32, i32) = tuple_literal (%.loc14_24, %.loc14_27)
-// CHECK:STDOUT:   %.loc14_29.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc14_29.2: ref i32 = tuple_index %t, %.loc14_29.1
-// CHECK:STDOUT:   %.loc14_29.3: init i32 = initialize_from %.loc14_24 to %.loc14_29.2
-// CHECK:STDOUT:   %.loc14_29.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc14_29.5: ref i32 = tuple_index %t, %.loc14_29.4
-// CHECK:STDOUT:   %.loc14_29.6: init i32 = initialize_from %.loc14_27 to %.loc14_29.5
-// CHECK:STDOUT:   %.loc14_29.7: init (i32, i32) = tuple_init %.loc14_28, (%.loc14_29.3, %.loc14_29.6)
-// CHECK:STDOUT:   assign %t, %.loc14_29.7
+// CHECK:STDOUT:   %.loc14_29.1: ref i32 = tuple_access %t, member0
+// CHECK:STDOUT:   %.loc14_29.2: init i32 = initialize_from %.loc14_24 to %.loc14_29.1
+// CHECK:STDOUT:   %.loc14_29.3: ref i32 = tuple_access %t, member1
+// CHECK:STDOUT:   %.loc14_29.4: init i32 = initialize_from %.loc14_27 to %.loc14_29.3
+// CHECK:STDOUT:   %.loc14_29.5: init (i32, i32) = tuple_init %.loc14_28, (%.loc14_29.2, %.loc14_29.4)
+// CHECK:STDOUT:   assign %t, %.loc14_29.5
 // CHECK:STDOUT:   %.loc15_14: type = ptr_type i32
 // CHECK:STDOUT:   %t0: ref i32* = var "t0"
 // CHECK:STDOUT:   %.loc15_21: i32 = int_literal 0

+ 6 - 8
toolchain/check/testdata/return/tuple.carbon

@@ -18,12 +18,10 @@ fn Main() -> (i32, i32) {
 // CHECK:STDOUT:   %.loc9_11: i32 = int_literal 15
 // CHECK:STDOUT:   %.loc9_15: i32 = int_literal 35
 // CHECK:STDOUT:   %.loc9_17: (i32, i32) = tuple_literal (%.loc9_11, %.loc9_15)
-// CHECK:STDOUT:   %.loc9_18.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc9_18.2: ref i32 = tuple_index %return, %.loc9_18.1
-// CHECK:STDOUT:   %.loc9_18.3: init i32 = initialize_from %.loc9_11 to %.loc9_18.2
-// CHECK:STDOUT:   %.loc9_18.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc9_18.5: ref i32 = tuple_index %return, %.loc9_18.4
-// CHECK:STDOUT:   %.loc9_18.6: init i32 = initialize_from %.loc9_15 to %.loc9_18.5
-// CHECK:STDOUT:   %.loc9_18.7: init (i32, i32) = tuple_init %.loc9_17, (%.loc9_18.3, %.loc9_18.6)
-// CHECK:STDOUT:   return %.loc9_18.7
+// CHECK:STDOUT:   %.loc9_18.1: ref i32 = tuple_access %return, member0
+// CHECK:STDOUT:   %.loc9_18.2: init i32 = initialize_from %.loc9_11 to %.loc9_18.1
+// CHECK:STDOUT:   %.loc9_18.3: ref i32 = tuple_access %return, member1
+// CHECK:STDOUT:   %.loc9_18.4: init i32 = initialize_from %.loc9_15 to %.loc9_18.3
+// CHECK:STDOUT:   %.loc9_18.5: init (i32, i32) = tuple_init %.loc9_17, (%.loc9_18.2, %.loc9_18.4)
+// CHECK:STDOUT:   return %.loc9_18.5
 // CHECK:STDOUT: }

+ 5 - 6
toolchain/check/testdata/struct/tuple_as_element.carbon

@@ -20,12 +20,11 @@ var y: {.a: i32, .b: (i32,)} = x;
 // CHECK:STDOUT:   %.loc7_51.1: ref i32 = struct_access %x, member0
 // CHECK:STDOUT:   %.loc7_51.2: init i32 = initialize_from %.loc7_38 to %.loc7_51.1
 // CHECK:STDOUT:   %.loc7_51.3: ref (i32,) = struct_access %x, member1
-// CHECK:STDOUT:   %.loc7_51.4: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_51.5: ref i32 = tuple_index %.loc7_51.3, %.loc7_51.4
-// CHECK:STDOUT:   %.loc7_51.6: init (i32,) = tuple_init %.loc7_49, (%.loc7_47)
-// CHECK:STDOUT:   %.loc7_51.7: init (i32,) = initialize_from %.loc7_51.6 to %.loc7_51.3
-// CHECK:STDOUT:   %.loc7_51.8: init {.a: i32, .b: (i32,)} = struct_init %.loc7_50, (%.loc7_51.2, %.loc7_51.7)
-// CHECK:STDOUT:   assign %x, %.loc7_51.8
+// CHECK:STDOUT:   %.loc7_51.4: ref i32 = tuple_access %.loc7_51.3, member0
+// CHECK:STDOUT:   %.loc7_51.5: init (i32,) = tuple_init %.loc7_49, (%.loc7_47)
+// CHECK:STDOUT:   %.loc7_51.6: init (i32,) = initialize_from %.loc7_51.5 to %.loc7_51.3
+// CHECK:STDOUT:   %.loc7_51.7: init {.a: i32, .b: (i32,)} = struct_init %.loc7_50, (%.loc7_51.2, %.loc7_51.6)
+// CHECK:STDOUT:   assign %x, %.loc7_51.7
 // CHECK:STDOUT:   %.loc8_27: (type,) = tuple_literal (i32)
 // CHECK:STDOUT:   %.loc8_28: type = struct_type {.a: i32, .b: (i32,)}
 // CHECK:STDOUT:   %y: ref {.a: i32, .b: (i32,)} = var "y"

+ 10 - 14
toolchain/check/testdata/tuples/nested_tuple.carbon

@@ -19,18 +19,14 @@ var x: ((i32, i32), i32) = ((12, 76), 6);
 // CHECK:STDOUT:   %.loc7_36: (i32, i32) = tuple_literal (%.loc7_30, %.loc7_34)
 // CHECK:STDOUT:   %.loc7_39: i32 = int_literal 6
 // CHECK:STDOUT:   %.loc7_40: ((i32, i32), i32) = tuple_literal (%.loc7_36, %.loc7_39)
-// CHECK:STDOUT:   %.loc7_41.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_41.2: ref (i32, i32) = tuple_index %x, %.loc7_41.1
-// CHECK:STDOUT:   %.loc7_41.3: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_41.4: ref i32 = tuple_index %.loc7_41.2, %.loc7_41.3
-// CHECK:STDOUT:   %.loc7_41.5: init i32 = initialize_from %.loc7_30 to %.loc7_41.4
-// CHECK:STDOUT:   %.loc7_41.6: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc7_41.7: ref i32 = tuple_index %.loc7_41.2, %.loc7_41.6
-// CHECK:STDOUT:   %.loc7_41.8: init i32 = initialize_from %.loc7_34 to %.loc7_41.7
-// CHECK:STDOUT:   %.loc7_41.9: init (i32, i32) = tuple_init %.loc7_36, (%.loc7_41.5, %.loc7_41.8)
-// CHECK:STDOUT:   %.loc7_41.10: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc7_41.11: ref i32 = tuple_index %x, %.loc7_41.10
-// CHECK:STDOUT:   %.loc7_41.12: init i32 = initialize_from %.loc7_39 to %.loc7_41.11
-// CHECK:STDOUT:   %.loc7_41.13: init ((i32, i32), i32) = tuple_init %.loc7_40, (%.loc7_41.9, %.loc7_41.12)
-// CHECK:STDOUT:   assign %x, %.loc7_41.13
+// CHECK:STDOUT:   %.loc7_41.1: ref (i32, i32) = tuple_access %x, member0
+// CHECK:STDOUT:   %.loc7_41.2: ref i32 = tuple_access %.loc7_41.1, member0
+// CHECK:STDOUT:   %.loc7_41.3: init i32 = initialize_from %.loc7_30 to %.loc7_41.2
+// CHECK:STDOUT:   %.loc7_41.4: ref i32 = tuple_access %.loc7_41.1, member1
+// CHECK:STDOUT:   %.loc7_41.5: init i32 = initialize_from %.loc7_34 to %.loc7_41.4
+// CHECK:STDOUT:   %.loc7_41.6: init (i32, i32) = tuple_init %.loc7_36, (%.loc7_41.3, %.loc7_41.5)
+// CHECK:STDOUT:   %.loc7_41.7: ref i32 = tuple_access %x, member1
+// CHECK:STDOUT:   %.loc7_41.8: init i32 = initialize_from %.loc7_39 to %.loc7_41.7
+// CHECK:STDOUT:   %.loc7_41.9: init ((i32, i32), i32) = tuple_init %.loc7_40, (%.loc7_41.6, %.loc7_41.8)
+// CHECK:STDOUT:   assign %x, %.loc7_41.9
 // CHECK:STDOUT: }

+ 3 - 4
toolchain/check/testdata/tuples/one_element.carbon

@@ -14,10 +14,9 @@ var y: (i32,) = x;
 // CHECK:STDOUT:   %x: ref (i32,) = var "x"
 // CHECK:STDOUT:   %.loc7_18: i32 = int_literal 4
 // CHECK:STDOUT:   %.loc7_20: (i32,) = tuple_literal (%.loc7_18)
-// CHECK:STDOUT:   %.loc7_21.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_21.2: ref i32 = tuple_index %x, %.loc7_21.1
-// CHECK:STDOUT:   %.loc7_21.3: init (i32,) = tuple_init %.loc7_20, (%.loc7_18)
-// CHECK:STDOUT:   assign %x, %.loc7_21.3
+// CHECK:STDOUT:   %.loc7_21.1: ref i32 = tuple_access %x, member0
+// CHECK:STDOUT:   %.loc7_21.2: init (i32,) = tuple_init %.loc7_20, (%.loc7_18)
+// CHECK:STDOUT:   assign %x, %.loc7_21.2
 // CHECK:STDOUT:   %.loc8: (type,) = tuple_literal (i32)
 // CHECK:STDOUT:   %y: ref (i32,) = var "y"
 // CHECK:STDOUT:   %.loc7_5: (i32,) = bind_value %x

+ 6 - 8
toolchain/check/testdata/tuples/two_elements.carbon

@@ -15,14 +15,12 @@ var y: (i32, i32) = x;
 // CHECK:STDOUT:   %.loc7_22: i32 = int_literal 4
 // CHECK:STDOUT:   %.loc7_25: i32 = int_literal 102
 // CHECK:STDOUT:   %.loc7_28: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_25)
-// CHECK:STDOUT:   %.loc7_29.1: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_29.2: ref i32 = tuple_index %x, %.loc7_29.1
-// CHECK:STDOUT:   %.loc7_29.3: init i32 = initialize_from %.loc7_22 to %.loc7_29.2
-// CHECK:STDOUT:   %.loc7_29.4: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc7_29.5: ref i32 = tuple_index %x, %.loc7_29.4
-// CHECK:STDOUT:   %.loc7_29.6: init i32 = initialize_from %.loc7_25 to %.loc7_29.5
-// CHECK:STDOUT:   %.loc7_29.7: init (i32, i32) = tuple_init %.loc7_28, (%.loc7_29.3, %.loc7_29.6)
-// CHECK:STDOUT:   assign %x, %.loc7_29.7
+// CHECK:STDOUT:   %.loc7_29.1: ref i32 = tuple_access %x, member0
+// CHECK:STDOUT:   %.loc7_29.2: init i32 = initialize_from %.loc7_22 to %.loc7_29.1
+// CHECK:STDOUT:   %.loc7_29.3: ref i32 = tuple_access %x, member1
+// CHECK:STDOUT:   %.loc7_29.4: init i32 = initialize_from %.loc7_25 to %.loc7_29.3
+// CHECK:STDOUT:   %.loc7_29.5: init (i32, i32) = tuple_init %.loc7_28, (%.loc7_29.2, %.loc7_29.4)
+// CHECK:STDOUT:   assign %x, %.loc7_29.5
 // CHECK:STDOUT:   %.loc8: (type, type) = tuple_literal (i32, i32)
 // CHECK:STDOUT:   %y: ref (i32, i32) = var "y"
 // CHECK:STDOUT:   %.loc7_5: (i32, i32) = bind_value %x

+ 11 - 0
toolchain/lower/handle.cpp

@@ -394,6 +394,17 @@ auto HandleStubReference(FunctionContext& context, SemIR::NodeId node_id,
   context.SetLocal(node_id, context.GetLocal(node.GetAsStubReference()));
 }
 
+auto HandleTupleAccess(FunctionContext& context, SemIR::NodeId node_id,
+                       SemIR::Node node) -> void {
+  auto [tuple_node_id, index] = node.GetAsTupleAccess();
+  auto* tuple_value = context.GetLocal(tuple_node_id);
+  auto* llvm_type =
+      context.GetType(context.semantics_ir().GetNode(tuple_node_id).type_id());
+  context.SetLocal(
+      node_id, context.GetIndexFromStructOrArray(llvm_type, tuple_value,
+                                                 index.index, "tuple.elem"));
+}
+
 auto HandleTupleIndex(FunctionContext& context, SemIR::NodeId node_id,
                       SemIR::Node node) -> void {
   auto [tuple_node_id, index_node_id] = node.GetAsTupleIndex();

+ 4 - 4
toolchain/lower/testdata/array/assign_return_value.carbon

@@ -14,10 +14,10 @@ fn Run() {
 // CHECK:STDOUT: source_filename = "assign_return_value.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @F(ptr sret({ i32, i32 }) %return) {
-// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 12, ptr %tuple.index, align 4
-// CHECK:STDOUT:   %tuple.index1 = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 24, ptr %tuple.index1, align 4
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
+// CHECK:STDOUT:   store i32 12, ptr %tuple.elem, align 4
+// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
+// CHECK:STDOUT:   store i32 24, ptr %tuple.elem1, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 16 - 16
toolchain/lower/testdata/array/base.carbon

@@ -31,24 +31,24 @@ fn Run() {
 // CHECK:STDOUT:   %array.index6 = getelementptr inbounds [5 x {}], ptr %c, i32 0, i32 3
 // CHECK:STDOUT:   %array.index7 = getelementptr inbounds [5 x {}], ptr %c, i32 0, i32 4
 // CHECK:STDOUT:   %d = alloca { i32, i32, i32 }, align 8
-// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %tuple.index, align 4
-// CHECK:STDOUT:   %tuple.index8 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %tuple.index8, align 4
-// CHECK:STDOUT:   %tuple.index9 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 2
-// CHECK:STDOUT:   store i32 3, ptr %tuple.index9, align 4
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 0
+// CHECK:STDOUT:   store i32 1, ptr %tuple.elem, align 4
+// CHECK:STDOUT:   %tuple.elem8 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 1
+// CHECK:STDOUT:   store i32 2, ptr %tuple.elem8, align 4
+// CHECK:STDOUT:   %tuple.elem9 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 2
+// CHECK:STDOUT:   store i32 3, ptr %tuple.elem9, align 4
 // CHECK:STDOUT:   %e = alloca [3 x i32], align 4
 // CHECK:STDOUT:   %array.index10 = getelementptr inbounds [3 x i32], ptr %e, i32 0, i32 0
-// CHECK:STDOUT:   %tuple.index11 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 0
-// CHECK:STDOUT:   %1 = load i32, ptr %tuple.index11, align 4
+// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 0
+// CHECK:STDOUT:   %1 = load i32, ptr %tuple.index, align 4
 // CHECK:STDOUT:   store i32 %1, ptr %array.index10, align 4
-// CHECK:STDOUT:   %array.index12 = getelementptr inbounds [3 x i32], ptr %e, i32 0, i32 1
-// CHECK:STDOUT:   %tuple.index13 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 1
-// CHECK:STDOUT:   %2 = load i32, ptr %tuple.index13, align 4
-// CHECK:STDOUT:   store i32 %2, ptr %array.index12, align 4
-// CHECK:STDOUT:   %array.index14 = getelementptr inbounds [3 x i32], ptr %e, i32 0, i32 2
-// CHECK:STDOUT:   %tuple.index15 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 2
-// CHECK:STDOUT:   %3 = load i32, ptr %tuple.index15, align 4
-// CHECK:STDOUT:   store i32 %3, ptr %array.index14, align 4
+// CHECK:STDOUT:   %array.index11 = getelementptr inbounds [3 x i32], ptr %e, i32 0, i32 1
+// CHECK:STDOUT:   %tuple.index12 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 1
+// CHECK:STDOUT:   %2 = load i32, ptr %tuple.index12, align 4
+// CHECK:STDOUT:   store i32 %2, ptr %array.index11, align 4
+// CHECK:STDOUT:   %array.index13 = getelementptr inbounds [3 x i32], ptr %e, i32 0, i32 2
+// CHECK:STDOUT:   %tuple.index14 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 2
+// CHECK:STDOUT:   %3 = load i32, ptr %tuple.index14, align 4
+// CHECK:STDOUT:   store i32 %3, ptr %array.index13, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }

+ 6 - 6
toolchain/lower/testdata/function/call/tuple_param_with_return_slot.carbon

@@ -19,14 +19,14 @@ fn Main() {
 // CHECK:STDOUT:   %tuple.index = extractvalue { i32 } %b, 0
 // CHECK:STDOUT:   %tuple.index1 = getelementptr inbounds { i32, i32 }, ptr %c, i32 0, i32 0
 // CHECK:STDOUT:   %tuple.index2 = getelementptr inbounds { i32, i32 }, ptr %c, i32 0, i32 1
-// CHECK:STDOUT:   %tuple.index3 = getelementptr inbounds { i32, i32, i32 }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 %tuple.index, ptr %tuple.index3, align 4
-// CHECK:STDOUT:   %tuple.index4 = getelementptr inbounds { i32, i32, i32 }, ptr %return, i32 0, i32 1
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %return, i32 0, i32 0
+// CHECK:STDOUT:   store i32 %tuple.index, ptr %tuple.elem, align 4
+// CHECK:STDOUT:   %tuple.elem3 = getelementptr inbounds { i32, i32, i32 }, ptr %return, i32 0, i32 1
 // CHECK:STDOUT:   %1 = load i32, ptr %tuple.index1, align 4
-// CHECK:STDOUT:   store i32 %1, ptr %tuple.index4, align 4
-// CHECK:STDOUT:   %tuple.index5 = getelementptr inbounds { i32, i32, i32 }, ptr %return, i32 0, i32 2
+// CHECK:STDOUT:   store i32 %1, ptr %tuple.elem3, align 4
+// CHECK:STDOUT:   %tuple.elem4 = getelementptr inbounds { i32, i32, i32 }, ptr %return, i32 0, i32 2
 // CHECK:STDOUT:   %2 = load i32, ptr %tuple.index2, align 4
-// CHECK:STDOUT:   store i32 %2, ptr %tuple.index5, align 4
+// CHECK:STDOUT:   store i32 %2, ptr %tuple.elem4, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 4 - 4
toolchain/lower/testdata/index/array_element_access.carbon

@@ -18,10 +18,10 @@ fn Run() {
 // CHECK:STDOUT: source_filename = "array_element_access.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @A(ptr sret({ i32, i32 }) %return) {
-// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %tuple.index, align 4
-// CHECK:STDOUT:   %tuple.index1 = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %tuple.index1, align 4
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
+// CHECK:STDOUT:   store i32 1, ptr %tuple.elem, align 4
+// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
+// CHECK:STDOUT:   store i32 2, ptr %tuple.elem1, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 10 - 10
toolchain/lower/testdata/index/tuple_element_access.carbon

@@ -16,19 +16,19 @@ fn Run() -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Run() {
 // CHECK:STDOUT:   %a = alloca { i32, i32, i32 }, align 8
-// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 0
-// CHECK:STDOUT:   store i32 0, ptr %tuple.index, align 4
-// CHECK:STDOUT:   %tuple.index1 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 1
-// CHECK:STDOUT:   store i32 1, ptr %tuple.index1, align 4
-// CHECK:STDOUT:   %tuple.index2 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 2
-// CHECK:STDOUT:   store i32 2, ptr %tuple.index2, align 4
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 0
+// CHECK:STDOUT:   store i32 0, ptr %tuple.elem, align 4
+// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 1
+// CHECK:STDOUT:   store i32 1, ptr %tuple.elem1, align 4
+// CHECK:STDOUT:   %tuple.elem2 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 2
+// CHECK:STDOUT:   store i32 2, ptr %tuple.elem2, align 4
 // CHECK:STDOUT:   %b = alloca i32, align 4
-// CHECK:STDOUT:   %tuple.index3 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 0
-// CHECK:STDOUT:   %1 = load i32, ptr %tuple.index3, align 4
+// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 0
+// CHECK:STDOUT:   %1 = load i32, ptr %tuple.index, align 4
 // CHECK:STDOUT:   store i32 %1, ptr %b, align 4
 // CHECK:STDOUT:   %c = alloca i32, align 4
-// CHECK:STDOUT:   %tuple.index4 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 2
-// CHECK:STDOUT:   %2 = load i32, ptr %tuple.index4, align 4
+// CHECK:STDOUT:   %tuple.index3 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 2
+// CHECK:STDOUT:   %2 = load i32, ptr %tuple.index3, align 4
 // CHECK:STDOUT:   store i32 %2, ptr %c, align 4
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }

+ 4 - 4
toolchain/lower/testdata/index/tuple_return_value_access.carbon

@@ -14,10 +14,10 @@ fn Run() {
 // CHECK:STDOUT: source_filename = "tuple_return_value_access.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @F(ptr sret({ i32, i32 }) %return) {
-// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 12, ptr %tuple.index, align 4
-// CHECK:STDOUT:   %tuple.index1 = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 24, ptr %tuple.index1, align 4
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
+// CHECK:STDOUT:   store i32 12, ptr %tuple.elem, align 4
+// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
+// CHECK:STDOUT:   store i32 24, ptr %tuple.elem1, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 4 - 4
toolchain/lower/testdata/operators/assignment.carbon

@@ -19,9 +19,9 @@ fn Main() {
 // CHECK:STDOUT:   store i32 12, ptr %a, align 4
 // CHECK:STDOUT:   store i32 9, ptr %a, align 4
 // CHECK:STDOUT:   %b = alloca { i32, i32 }, align 8
-// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32 }, ptr %b, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %tuple.index, align 4
-// CHECK:STDOUT:   %tuple.index1 = getelementptr inbounds { i32, i32 }, ptr %b, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %tuple.index1, align 4
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds { i32, i32 }, ptr %b, i32 0, i32 0
+// CHECK:STDOUT:   store i32 1, ptr %tuple.elem, align 4
+// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds { i32, i32 }, ptr %b, i32 0, i32 1
+// CHECK:STDOUT:   store i32 2, ptr %tuple.elem1, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }

+ 1 - 1
toolchain/lower/testdata/tuple/one_entry.carbon

@@ -15,7 +15,7 @@ fn Run() -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Run() {
 // CHECK:STDOUT:   %x = alloca { i32 }, align 8
-// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32 }, ptr %x, i32 0, i32 0
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds { i32 }, ptr %x, i32 0, i32 0
 // CHECK:STDOUT:   store { i32 } { i32 1 }, ptr %x, align 4
 // CHECK:STDOUT:   %y = alloca { i32 }, align 8
 // CHECK:STDOUT:   %1 = load { i32 }, ptr %x, align 4

+ 4 - 4
toolchain/lower/testdata/tuple/two_entries.carbon

@@ -15,10 +15,10 @@ fn Run() -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Run() {
 // CHECK:STDOUT:   %x = alloca { i32, i32 }, align 8
-// CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 0
-// CHECK:STDOUT:   store i32 12, ptr %tuple.index, align 4
-// CHECK:STDOUT:   %tuple.index1 = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 1
-// CHECK:STDOUT:   store i32 7, ptr %tuple.index1, align 4
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 0
+// CHECK:STDOUT:   store i32 12, ptr %tuple.elem, align 4
+// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 1
+// CHECK:STDOUT:   store i32 7, ptr %tuple.elem1, align 4
 // CHECK:STDOUT:   %y = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }

+ 9 - 0
toolchain/sem_ir/file.cpp

@@ -225,6 +225,7 @@ static auto GetTypePrecedence(NodeKind kind) -> int {
     case NodeKind::StubReference:
     case NodeKind::Temporary:
     case NodeKind::TemporaryStorage:
+    case NodeKind::TupleAccess:
     case NodeKind::TupleIndex:
     case NodeKind::TupleLiteral:
     case NodeKind::TupleInit:
@@ -391,6 +392,7 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const
       case NodeKind::StubReference:
       case NodeKind::Temporary:
       case NodeKind::TemporaryStorage:
+      case NodeKind::TupleAccess:
       case NodeKind::TupleIndex:
       case NodeKind::TupleLiteral:
       case NodeKind::TupleInit:
@@ -481,6 +483,12 @@ auto GetExpressionCategory(const File& file, NodeId node_id)
         continue;
       }
 
+      case NodeKind::TupleAccess: {
+        auto [base_id, index_id] = node.GetAsTupleAccess();
+        node_id = base_id;
+        continue;
+      }
+
       case NodeKind::TupleIndex: {
         auto [base_id, index_id] = node.GetAsTupleIndex();
         node_id = base_id;
@@ -554,6 +562,7 @@ auto GetValueRepresentation(const File& file, TypeId type_id)
       case NodeKind::StructValue:
       case NodeKind::Temporary:
       case NodeKind::TemporaryStorage:
+      case NodeKind::TupleAccess:
       case NodeKind::TupleIndex:
       case NodeKind::TupleLiteral:
       case NodeKind::TupleInit:

+ 4 - 1
toolchain/sem_ir/node.h

@@ -198,7 +198,7 @@ struct TypeBlockId : public IndexBase, public Printable<TypeBlockId> {
   }
 };
 
-// An index for member access.
+// An index for member access, for structs and tuples.
 struct MemberIndex : public IndexBase, public Printable<MemberIndex> {
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
@@ -429,6 +429,9 @@ class Node : public Printable<Node> {
 
   using TemporaryStorage = Factory<NodeKind::TemporaryStorage>;
 
+  using TupleAccess = Factory<NodeKind::TupleAccess, NodeId /*tuple_id*/,
+                              MemberIndex /*index*/>;
+
   using TupleIndex =
       Factory<NodeKind::TupleIndex, NodeId /*tuple_id*/, NodeId /*index*/>;
 

+ 2 - 0
toolchain/sem_ir/node_kind.def

@@ -94,6 +94,8 @@ CARBON_SEMANTICS_NODE_KIND_IMPL(StubReference, "stub_reference", Typed,
 CARBON_SEMANTICS_NODE_KIND_IMPL(TemporaryStorage, "temporary_storage", Typed,
                                 NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(Temporary, "temporary", Typed, NotTerminator)
+CARBON_SEMANTICS_NODE_KIND_IMPL(TupleAccess, "tuple_access", Typed,
+                                NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(TupleIndex, "tuple_index", Typed, NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(TupleInit, "tuple_init", Typed, NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(TupleLiteral, "tuple_literal", Typed,