Эх сурвалжийг харах

change tuple index (#4218)

This changes the tuple index from tuple[0] to tuple.0 in accordance with
the accepted propsal
https://github.com/carbon-language/carbon-lang/pull/3646

I messed up syncing to trunk on my original PR
https://github.com/carbon-language/carbon-lang/pull/4186, that's why I'm
starting on a blank state. Please let me know if I missed incorporating
a change from my prior PR.
Brymer Meneses 1 жил өмнө
parent
commit
c353f6bd78
39 өөрчлөгдсөн 382 нэмэгдсэн , 220 устгасан
  1. 0 53
      toolchain/check/handle_index.cpp
  2. 23 2
      toolchain/check/handle_name.cpp
  3. 72 0
      toolchain/check/member_access.cpp
  4. 6 0
      toolchain/check/member_access.h
  5. 3 3
      toolchain/check/testdata/eval/aggregate.carbon
  6. 4 4
      toolchain/check/testdata/expr_category/in_place_tuple_init.carbon
  7. 5 5
      toolchain/check/testdata/function/call/more_param_ir.carbon
  8. 6 6
      toolchain/check/testdata/function/definition/import.carbon
  9. 4 4
      toolchain/check/testdata/index/fail_empty_tuple_access.carbon
  10. 53 7
      toolchain/check/testdata/index/fail_negative_indexing.carbon
  11. 5 5
      toolchain/check/testdata/index/fail_non_deterministic_type.carbon
  12. 49 3
      toolchain/check/testdata/index/fail_non_tuple_access.carbon
  13. 9 9
      toolchain/check/testdata/index/fail_out_of_bound_not_literal.carbon
  14. 4 4
      toolchain/check/testdata/index/fail_tuple_index_error.carbon
  15. 9 9
      toolchain/check/testdata/index/fail_tuple_large_index.carbon
  16. 6 6
      toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon
  17. 4 4
      toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon
  18. 9 9
      toolchain/check/testdata/index/index_not_literal.carbon
  19. 4 4
      toolchain/check/testdata/index/tuple_element_access.carbon
  20. 4 4
      toolchain/check/testdata/index/tuple_return_value_access.carbon
  21. 3 3
      toolchain/check/testdata/let/convert.carbon
  22. 8 8
      toolchain/check/testdata/operators/builtin/assignment.carbon
  23. 6 6
      toolchain/check/testdata/pointer/address_of_lvalue.carbon
  24. 3 3
      toolchain/check/testdata/pointer/fail_address_of_value.carbon
  25. 1 0
      toolchain/diagnostics/diagnostic_kind.def
  26. 4 4
      toolchain/lower/testdata/class/value_access.carbon
  27. 12 12
      toolchain/lower/testdata/function/call/tuple_param_with_return_slot.carbon
  28. 2 6
      toolchain/lower/testdata/index/array_element_access.carbon
  29. 8 8
      toolchain/lower/testdata/index/tuple_element_access.carbon
  30. 4 4
      toolchain/lower/testdata/index/tuple_return_value_access.carbon
  31. 6 6
      toolchain/lower/testdata/let/tuple.carbon
  32. 3 0
      toolchain/parse/handle_period.cpp
  33. 3 2
      toolchain/parse/node_ids.h
  34. 2 1
      toolchain/parse/node_kind.def
  35. 2 1
      toolchain/parse/node_kind.h
  36. 2 11
      toolchain/parse/testdata/basics/fail_invalid_designators.carbon
  37. 26 0
      toolchain/parse/testdata/index/tuple_index.carbon
  38. 5 2
      toolchain/parse/typed_nodes.h
  39. 3 2
      toolchain/sem_ir/typed_insts.h

+ 0 - 53
toolchain/check/handle_index.cpp

@@ -16,26 +16,6 @@ auto HandleParseNode(Context& /*context*/, Parse::IndexExprStartId /*node_id*/)
   return true;
 }
 
-// Validates that the index (required to be an IntLiteral) is valid within the
-// tuple size. Returns the index on success, or nullptr on failure.
-static auto ValidateTupleIndex(Context& context, Parse::NodeId node_id,
-                               SemIR::Inst operand_inst,
-                               SemIR::IntLiteral index_inst, int size)
-    -> const llvm::APInt* {
-  const auto& index_val = context.ints().Get(index_inst.int_id);
-  if (index_val.uge(size)) {
-    CARBON_DIAGNOSTIC(
-        TupleIndexOutOfBounds, Error,
-        "Tuple element index `{0}` is past the end of type `{1}`.", TypedInt,
-        SemIR::TypeId);
-    context.emitter().Emit(node_id, TupleIndexOutOfBounds,
-                           {.type = index_inst.type_id, .value = index_val},
-                           operand_inst.type_id());
-    return nullptr;
-  }
-  return &index_val;
-}
-
 auto HandleParseNode(Context& context, Parse::IndexExprId node_id) -> bool {
   auto index_inst_id = context.node_stack().PopExpr();
   auto operand_inst_id = context.node_stack().PopExpr();
@@ -72,39 +52,6 @@ auto HandleParseNode(Context& context, Parse::IndexExprId node_id) -> bool {
       context.node_stack().Push(node_id, elem_id);
       return true;
     }
-    case CARBON_KIND(SemIR::TupleType tuple_type): {
-      SemIR::TypeId element_type_id = SemIR::TypeId::Error;
-      auto index_node_id = context.insts().GetLocId(index_inst_id);
-      index_inst_id = ConvertToValueOfType(
-          context, index_node_id, index_inst_id,
-          context.GetBuiltinType(SemIR::BuiltinInstKind::IntType));
-      auto index_const_id = context.constant_values().Get(index_inst_id);
-      if (index_const_id == SemIR::ConstantId::Error) {
-        index_inst_id = SemIR::InstId::BuiltinError;
-      } else if (!index_const_id.is_template()) {
-        // TODO: Decide what to do if the index is a symbolic constant.
-        CARBON_DIAGNOSTIC(TupleIndexNotConstant, Error,
-                          "Tuple index must be a constant.");
-        context.emitter().Emit(node_id, TupleIndexNotConstant);
-        index_inst_id = SemIR::InstId::BuiltinError;
-      } else {
-        auto index_literal = context.insts().GetAs<SemIR::IntLiteral>(
-            context.constant_values().GetInstId(index_const_id));
-        auto type_block = context.type_blocks().Get(tuple_type.elements_id);
-        if (const auto* index_val =
-                ValidateTupleIndex(context, node_id, operand_inst,
-                                   index_literal, type_block.size())) {
-          element_type_id = type_block[index_val->getZExtValue()];
-        } else {
-          index_inst_id = SemIR::InstId::BuiltinError;
-        }
-      }
-      context.AddInstAndPush<SemIR::TupleIndex>(node_id,
-                                                {.type_id = element_type_id,
-                                                 .tuple_id = operand_inst_id,
-                                                 .index_id = index_inst_id});
-      return true;
-    }
     default: {
       if (operand_type_id != SemIR::TypeId::Error) {
         CARBON_DIAGNOSTIC(TypeNotIndexable, Error,

+ 23 - 2
toolchain/check/handle_name.cpp

@@ -16,12 +16,22 @@ namespace Carbon::Check {
 
 auto HandleParseNode(Context& context, Parse::MemberAccessExprId node_id)
     -> bool {
-  if (context.node_stack().PeekIs<Parse::NodeKind::ParenExpr>()) {
+  auto node_kind = context.node_stack().PeekNodeKind();
+
+  if (node_kind == Parse::NodeKind::ParenExpr) {
     auto member_expr_id = context.node_stack().PopExpr();
     auto base_id = context.node_stack().PopExpr();
     auto member_id =
         PerformCompoundMemberAccess(context, node_id, base_id, member_expr_id);
     context.node_stack().Push(node_id, member_id);
+  } else if (node_kind == Parse::NodeKind::IntLiteral) {
+    auto index_inst_id = context.node_stack().PopExpr();
+    auto tuple_inst_id = context.node_stack().PopExpr();
+
+    auto tuple_value_inst_id =
+        PerformTupleIndex(context, node_id, tuple_inst_id, index_inst_id);
+
+    context.node_stack().Push(node_id, tuple_value_inst_id);
   } else {
     SemIR::NameId name_id = context.node_stack().PopName();
     auto base_id = context.node_stack().PopExpr();
@@ -44,7 +54,9 @@ auto HandleParseNode(Context& context, Parse::PointerMemberAccessExprId node_id)
     builder.Emit();
   };
 
-  if (context.node_stack().PeekIs<Parse::NodeKind::ParenExpr>()) {
+  auto node_kind = context.node_stack().PeekNodeKind();
+
+  if (node_kind == Parse::NodeKind::ParenExpr) {
     auto member_expr_id = context.node_stack().PopExpr();
     auto base_id = context.node_stack().PopExpr();
     auto deref_base_id = PerformPointerDereference(context, node_id, base_id,
@@ -52,6 +64,15 @@ auto HandleParseNode(Context& context, Parse::PointerMemberAccessExprId node_id)
     auto member_id = PerformCompoundMemberAccess(context, node_id,
                                                  deref_base_id, member_expr_id);
     context.node_stack().Push(node_id, member_id);
+  } else if (node_kind == Parse::NodeKind::IntLiteral) {
+    auto index_inst_id = context.node_stack().PopExpr();
+    auto tuple_pointer_inst_id = context.node_stack().PopExpr();
+    auto tuple_inst_id = PerformPointerDereference(
+        context, node_id, tuple_pointer_inst_id, diagnose_not_pointer);
+    auto tuple_value_inst_id =
+        PerformTupleIndex(context, node_id, tuple_inst_id, index_inst_id);
+
+    context.node_stack().Push(node_id, tuple_value_inst_id);
   } else {
     SemIR::NameId name_id = context.node_stack().PopName();
     auto base_id = context.node_stack().PopExpr();

+ 72 - 0
toolchain/check/member_access.cpp

@@ -309,6 +309,26 @@ static auto PerformInstanceBinding(Context& context, Parse::NodeId node_id,
   }
 }
 
+// Validates that the index (required to be an IntLiteral) is valid within the
+// tuple size. Returns the index on success, or nullptr on failure.
+static auto ValidateTupleIndex(Context& context, Parse::NodeId node_id,
+                               SemIR::Inst operand_inst,
+                               SemIR::IntLiteral index_inst, int size)
+    -> const llvm::APInt* {
+  const auto& index_val = context.ints().Get(index_inst.int_id);
+  if (index_val.uge(size)) {
+    CARBON_DIAGNOSTIC(
+        TupleIndexOutOfBounds, Error,
+        "Tuple element index `{0}` is past the end of type `{1}`.", TypedInt,
+        SemIR::TypeId);
+    context.emitter().Emit(node_id, TupleIndexOutOfBounds,
+                           {.type = index_inst.type_id, .value = index_val},
+                           operand_inst.type_id());
+    return nullptr;
+  }
+  return &index_val;
+}
+
 auto PerformMemberAccess(Context& context, Parse::NodeId node_id,
                          SemIR::InstId base_id, SemIR::NameId name_id)
     -> SemIR::InstId {
@@ -403,6 +423,9 @@ auto PerformCompoundMemberAccess(Context& context, Parse::NodeId node_id,
           member.type_id())) {
     member_id = PerformImplLookup(context, node_id, base_type_const_id,
                                   *assoc_type, member_id);
+  } else if (context.insts().Is<SemIR::TupleType>(
+                 context.constant_values().GetInstId(base_type_const_id))) {
+    return PerformTupleIndex(context, node_id, base_id, member_expr_id);
   }
 
   // Perform instance binding if we found an instance member.
@@ -422,4 +445,53 @@ auto PerformCompoundMemberAccess(Context& context, Parse::NodeId node_id,
   return member_id;
 }
 
+auto PerformTupleIndex(Context& context, Parse::NodeId node_id,
+                       SemIR::InstId tuple_inst_id, SemIR::InstId index_inst_id)
+    -> SemIR::InstId {
+  tuple_inst_id = ConvertToValueOrRefExpr(context, tuple_inst_id);
+  auto tuple_inst = context.insts().Get(tuple_inst_id);
+  auto tuple_type_id = tuple_inst.type_id();
+
+  auto tuple_type = context.types().TryGetAs<SemIR::TupleType>(tuple_type_id);
+  if (!tuple_type) {
+    CARBON_DIAGNOSTIC(TupleIndexOnANonTupleType, Error,
+                      "Type `{0}` does not support tuple indexing. Only "
+                      "tuples can be indexed that way.",
+                      SemIR::TypeId);
+    context.emitter().Emit(node_id, TupleIndexOnANonTupleType, tuple_type_id);
+    return SemIR::InstId::BuiltinError;
+  }
+
+  SemIR::TypeId element_type_id = SemIR::TypeId::Error;
+  auto index_node_id = context.insts().GetLocId(index_inst_id);
+  index_inst_id = ConvertToValueOfType(
+      context, index_node_id, index_inst_id,
+      context.GetBuiltinType(SemIR::BuiltinInstKind::IntType));
+  auto index_const_id = context.constant_values().Get(index_inst_id);
+  if (index_const_id == SemIR::ConstantId::Error) {
+    index_inst_id = SemIR::InstId::BuiltinError;
+  } else if (!index_const_id.is_template()) {
+    // TODO: Decide what to do if the index is a symbolic constant.
+    CARBON_DIAGNOSTIC(TupleIndexNotConstant, Error,
+                      "Tuple index must be a constant.");
+    context.emitter().Emit(node_id, TupleIndexNotConstant);
+    index_inst_id = SemIR::InstId::BuiltinError;
+  } else {
+    auto index_literal = context.insts().GetAs<SemIR::IntLiteral>(
+        context.constant_values().GetInstId(index_const_id));
+    auto type_block = context.type_blocks().Get(tuple_type->elements_id);
+    if (const auto* index_val = ValidateTupleIndex(
+            context, node_id, tuple_inst, index_literal, type_block.size())) {
+      element_type_id = type_block[index_val->getZExtValue()];
+    } else {
+      index_inst_id = SemIR::InstId::BuiltinError;
+    }
+  }
+
+  return context.AddInst<SemIR::TupleIndex>(node_id,
+                                            {.type_id = element_type_id,
+                                             .tuple_id = tuple_inst_id,
+                                             .index_id = index_inst_id});
+}
+
 }  // namespace Carbon::Check

+ 6 - 0
toolchain/check/member_access.h

@@ -23,6 +23,12 @@ auto PerformCompoundMemberAccess(Context& context, Parse::NodeId node_id,
                                  SemIR::InstId base_id,
                                  SemIR::InstId member_expr_id) -> SemIR::InstId;
 
+// Creates SemIR to perform a tuple index with base expression `tuple_inst_id`
+// and index expression `index_inst_id`. Returns the result of the access.
+auto PerformTupleIndex(Context& context, Parse::NodeId node_id,
+                       SemIR::InstId tuple_inst_id, SemIR::InstId index_inst_id)
+    -> SemIR::InstId;
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_MEMBER_ACCESS_H_

+ 3 - 3
toolchain/check/testdata/eval/aggregate.carbon

@@ -12,7 +12,7 @@ var tuple_copy: (i32, i32) = (1, 2) as (i32, i32);
 
 var struct_copy: {.a: i32, .b: i32, .c: i32} = {.c = 3, .b = 2, .a = 1} as {.b: i32, .a: i32, .c: i32};
 
-var tuple_index: [i32; 1] = (0,) as [i32; (5, 7, 1, 9)[2]];
+var tuple_index: [i32; 1] = (0,) as [i32; (5, 7, 1, 9).2];
 
 var struct_access: [i32; 1] = (0,) as [i32; {.a = 3, .b = 1}.b];
 
@@ -174,10 +174,10 @@ var struct_access: [i32; 1] = (0,) as [i32; {.a = 3, .b = 1}.b];
 // CHECK:STDOUT:   %.loc15_56: i32 = int_literal 2 [template = constants.%.6]
 // CHECK:STDOUT:   %tuple: %.20 = tuple_value (%.loc15_44, %.loc15_47, %.loc15_50, %.loc15_53) [template = constants.%tuple.2]
 // CHECK:STDOUT:   %.loc15_54.2: %.20 = converted %.loc15_54.1, %tuple [template = constants.%tuple.2]
-// CHECK:STDOUT:   %.loc15_57: i32 = tuple_index %.loc15_54.2, %.loc15_56 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc15_55: i32 = tuple_index %.loc15_54.2, %.loc15_56 [template = constants.%.5]
 // CHECK:STDOUT:   %.loc15_38.1: type = value_of_initializer %int.make_type_32.loc15 [template = i32]
 // CHECK:STDOUT:   %.loc15_38.2: type = converted %int.make_type_32.loc15, %.loc15_38.1 [template = i32]
-// CHECK:STDOUT:   %.loc15_58: type = array_type %.loc15_57, i32 [template = constants.%.13]
+// CHECK:STDOUT:   %.loc15_57: type = array_type %.loc15_55, i32 [template = constants.%.13]
 // CHECK:STDOUT:   %.loc15_5: ref %.13 = splice_block file.%tuple_index.var {}
 // CHECK:STDOUT:   %.loc15_32.2: i32 = int_literal 0 [template = constants.%.15]
 // CHECK:STDOUT:   %.loc15_32.3: ref i32 = array_index %.loc15_5, %.loc15_32.2

+ 4 - 4
toolchain/check/testdata/expr_category/in_place_tuple_init.carbon

@@ -17,7 +17,7 @@ fn G() -> (i32, i32) {
 }
 
 fn H() -> i32 {
-  return G()[0];
+  return G().0;
 }
 
 // CHECK:STDOUT: --- in_place_tuple_init.carbon
@@ -128,8 +128,8 @@ fn H() -> i32 {
 // CHECK:STDOUT:   %G.call: init %.3 = call %G.ref() to %.loc20_11.1
 // CHECK:STDOUT:   %.loc20_14: i32 = int_literal 0 [template = constants.%.5]
 // CHECK:STDOUT:   %.loc20_11.2: ref %.3 = temporary %.loc20_11.1, %G.call
-// CHECK:STDOUT:   %.loc20_15.1: ref i32 = tuple_index %.loc20_11.2, %.loc20_14
-// CHECK:STDOUT:   %.loc20_15.2: i32 = bind_value %.loc20_15.1
-// CHECK:STDOUT:   return %.loc20_15.2
+// CHECK:STDOUT:   %.loc20_13.1: ref i32 = tuple_index %.loc20_11.2, %.loc20_14
+// CHECK:STDOUT:   %.loc20_13.2: i32 = bind_value %.loc20_13.1
+// CHECK:STDOUT:   return %.loc20_13.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 5 - 5
toolchain/check/testdata/function/call/more_param_ir.carbon

@@ -13,7 +13,7 @@ fn Foo(a: i32, b: i32) {}
 fn Main() {
   var x: (i32,) = (1,);
   // Generates multiple IR instructions for the first parameter.
-  Foo(x[0], 6);
+  Foo(x.0, 6);
 }
 
 // CHECK:STDOUT: --- more_param_ir.carbon
@@ -94,10 +94,10 @@ fn Main() {
 // CHECK:STDOUT:   %Foo.ref: %Foo.type = name_ref Foo, file.%Foo.decl [template = constants.%Foo]
 // CHECK:STDOUT:   %x.ref: ref %.3 = name_ref x, %x
 // CHECK:STDOUT:   %.loc16_9: i32 = int_literal 0 [template = constants.%.5]
-// CHECK:STDOUT:   %.loc16_10.1: ref i32 = tuple_index %x.ref, %.loc16_9
-// CHECK:STDOUT:   %.loc16_13: i32 = int_literal 6 [template = constants.%.6]
-// CHECK:STDOUT:   %.loc16_10.2: i32 = bind_value %.loc16_10.1
-// CHECK:STDOUT:   %Foo.call: init %.1 = call %Foo.ref(%.loc16_10.2, %.loc16_13)
+// CHECK:STDOUT:   %.loc16_8.1: ref i32 = tuple_index %x.ref, %.loc16_9
+// CHECK:STDOUT:   %.loc16_12: i32 = int_literal 6 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc16_8.2: i32 = bind_value %.loc16_8.1
+// CHECK:STDOUT:   %Foo.call: init %.1 = call %Foo.ref(%.loc16_8.2, %.loc16_12)
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 6 - 6
toolchain/check/testdata/function/definition/import.carbon

@@ -18,7 +18,7 @@ library "fns";
 
 fn A() {}
 fn B(b: i32) -> i32 { return b; }
-fn C(c: (i32,)) -> {.c: i32} { return {.c = c[0]}; }
+fn C(c: (i32,)) -> {.c: i32} { return {.c = c.0}; }
 fn D();
 
 // --- extern.carbon
@@ -183,11 +183,11 @@ fn D() {}
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %c.ref: %.3 = name_ref c, %c
 // CHECK:STDOUT:   %.loc6_47: i32 = int_literal 0 [template = constants.%.5]
-// CHECK:STDOUT:   %.loc6_48: i32 = tuple_index %c.ref, %.loc6_47
-// CHECK:STDOUT:   %.loc6_49: %.4 = struct_literal (%.loc6_48)
-// CHECK:STDOUT:   %struct: %.4 = struct_value (%.loc6_48)
-// CHECK:STDOUT:   %.loc6_50: %.4 = converted %.loc6_49, %struct
-// CHECK:STDOUT:   return %.loc6_50
+// CHECK:STDOUT:   %.loc6_46: i32 = tuple_index %c.ref, %.loc6_47
+// CHECK:STDOUT:   %.loc6_48: %.4 = struct_literal (%.loc6_46)
+// CHECK:STDOUT:   %struct: %.4 = struct_value (%.loc6_46)
+// CHECK:STDOUT:   %.loc6_49: %.4 = converted %.loc6_48, %struct
+// CHECK:STDOUT:   return %.loc6_49
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @D();

+ 4 - 4
toolchain/check/testdata/index/fail_empty_tuple_access.carbon

@@ -12,9 +12,9 @@ fn F() {}
 
 fn Run() {
   // CHECK:STDERR: fail_empty_tuple_access.carbon:[[@LINE+3]]:3: ERROR: Tuple element index `0` is past the end of type `()`.
-  // CHECK:STDERR:   F()[0];
-  // CHECK:STDERR:   ^~~~~~
-  F()[0];
+  // CHECK:STDERR:   F().0;
+  // CHECK:STDERR:   ^~~~~
+  F().0;
 }
 
 // CHECK:STDOUT: --- fail_empty_tuple_access.carbon
@@ -63,7 +63,7 @@ fn Run() {
 // CHECK:STDOUT:   %.loc17_7: i32 = int_literal 0 [template = constants.%.2]
 // CHECK:STDOUT:   %.loc17_4.1: ref %.1 = temporary_storage
 // CHECK:STDOUT:   %.loc17_4.2: ref %.1 = temporary %.loc17_4.1, %F.call
-// CHECK:STDOUT:   %.loc17_8: ref <error> = tuple_index %.loc17_4.2, <error> [template = <error>]
+// CHECK:STDOUT:   %.loc17_6: ref <error> = tuple_index %.loc17_4.2, <error> [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 53 - 7
toolchain/check/testdata/index/fail_negative_indexing.carbon

@@ -9,10 +9,18 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/index/fail_negative_indexing.carbon
 
 var a: (i32, i32) = (12, 6);
+// CHECK:STDERR: fail_negative_indexing.carbon:[[@LINE+4]]:17: ERROR: Cannot access member of interface Negate in type i32 that does not implement that interface.
+// CHECK:STDERR: var b: i32 = a.(-10);
+// CHECK:STDERR:                 ^~~
+// CHECK:STDERR:
+var b: i32 = a.(-10);
+
+var c: [i32; 2] = (42, 42);
 // CHECK:STDERR: fail_negative_indexing.carbon:[[@LINE+3]]:16: ERROR: Cannot access member of interface Negate in type i32 that does not implement that interface.
-// CHECK:STDERR: var b: i32 = a[-10];
+// CHECK:STDERR: var d: i32 = c[-10];
 // CHECK:STDERR:                ^~~
-var b: i32 = a[-10];
+var d: i32 = c[-10];
+
 
 // CHECK:STDOUT: --- fail_negative_indexing.carbon
 // CHECK:STDOUT:
@@ -33,6 +41,13 @@ var b: i32 = a[-10];
 // CHECK:STDOUT:   %Op: %Op.type = struct_value () [template]
 // CHECK:STDOUT:   %.9: type = assoc_entity_type %.8, %Op.type [template]
 // CHECK:STDOUT:   %.10: %.9 = assoc_entity element0, imports.%import_ref.6 [template]
+// CHECK:STDOUT:   %.11: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.12: type = array_type %.11, i32 [template]
+// CHECK:STDOUT:   %.13: type = ptr_type %.12 [template]
+// CHECK:STDOUT:   %.14: i32 = int_literal 42 [template]
+// CHECK:STDOUT:   %.15: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.16: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %array: %.12 = tuple_value (%.14, %.14) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -60,6 +75,8 @@ var b: i32 = a[-10];
 // CHECK:STDOUT:     .Core = imports.%Core
 // CHECK:STDOUT:     .a = %a
 // CHECK:STDOUT:     .b = %b
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:     .d = %d
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %int.make_type_32.loc11_9: init type = call constants.%Int32() [template = i32]
@@ -72,11 +89,23 @@ var b: i32 = a[-10];
 // CHECK:STDOUT:   %.loc11_17.6: type = converted %.loc11_17.1, constants.%.3 [template = constants.%.3]
 // CHECK:STDOUT:   %a.var: ref %.3 = var a
 // CHECK:STDOUT:   %a: ref %.3 = bind_name a, %a.var
-// CHECK:STDOUT:   %int.make_type_32.loc15: init type = call constants.%Int32() [template = i32]
-// CHECK:STDOUT:   %.loc15_8.1: type = value_of_initializer %int.make_type_32.loc15 [template = i32]
-// CHECK:STDOUT:   %.loc15_8.2: type = converted %int.make_type_32.loc15, %.loc15_8.1 [template = i32]
+// CHECK:STDOUT:   %int.make_type_32.loc16: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc16_8.1: type = value_of_initializer %int.make_type_32.loc16 [template = i32]
+// CHECK:STDOUT:   %.loc16_8.2: type = converted %int.make_type_32.loc16, %.loc16_8.1 [template = i32]
 // CHECK:STDOUT:   %b.var: ref i32 = var b
 // CHECK:STDOUT:   %b: ref i32 = bind_name b, %b.var
+// CHECK:STDOUT:   %int.make_type_32.loc18: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc18_14: i32 = int_literal 2 [template = constants.%.11]
+// CHECK:STDOUT:   %.loc18_9.1: type = value_of_initializer %int.make_type_32.loc18 [template = i32]
+// CHECK:STDOUT:   %.loc18_9.2: type = converted %int.make_type_32.loc18, %.loc18_9.1 [template = i32]
+// CHECK:STDOUT:   %.loc18_15: type = array_type %.loc18_14, i32 [template = constants.%.12]
+// CHECK:STDOUT:   %c.var: ref %.12 = var c
+// CHECK:STDOUT:   %c: ref %.12 = bind_name c, %c.var
+// CHECK:STDOUT:   %int.make_type_32.loc22: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc22_8.1: type = value_of_initializer %int.make_type_32.loc22 [template = i32]
+// CHECK:STDOUT:   %.loc22_8.2: type = converted %int.make_type_32.loc22, %.loc22_8.1 [template = i32]
+// CHECK:STDOUT:   %d.var: ref i32 = var d
+// CHECK:STDOUT:   %d: ref i32 = bind_name d, %d.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Negate {
@@ -107,9 +136,26 @@ var b: i32 = a[-10];
 // CHECK:STDOUT:   %.loc11_28: init %.3 = converted %.loc11_27.1, %.loc11_27.6 [template = constants.%tuple]
 // CHECK:STDOUT:   assign file.%a.var, %.loc11_28
 // CHECK:STDOUT:   %a.ref: ref %.3 = name_ref a, file.%a
-// CHECK:STDOUT:   %.loc15_17: i32 = int_literal 10 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc15_19: ref <error> = tuple_index %a.ref, <error> [template = <error>]
+// CHECK:STDOUT:   %.loc16_18: i32 = int_literal 10 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc16_15: ref <error> = tuple_index %a.ref, <error> [template = <error>]
 // CHECK:STDOUT:   assign file.%b.var, <error>
+// CHECK:STDOUT:   %.loc18_20: i32 = int_literal 42 [template = constants.%.14]
+// CHECK:STDOUT:   %.loc18_24: i32 = int_literal 42 [template = constants.%.14]
+// CHECK:STDOUT:   %.loc18_26.1: %.3 = tuple_literal (%.loc18_20, %.loc18_24)
+// CHECK:STDOUT:   %.loc18_26.2: i32 = int_literal 0 [template = constants.%.15]
+// CHECK:STDOUT:   %.loc18_26.3: ref i32 = array_index file.%c.var, %.loc18_26.2
+// CHECK:STDOUT:   %.loc18_26.4: init i32 = initialize_from %.loc18_20 to %.loc18_26.3 [template = constants.%.14]
+// CHECK:STDOUT:   %.loc18_26.5: i32 = int_literal 1 [template = constants.%.16]
+// CHECK:STDOUT:   %.loc18_26.6: ref i32 = array_index file.%c.var, %.loc18_26.5
+// CHECK:STDOUT:   %.loc18_26.7: init i32 = initialize_from %.loc18_24 to %.loc18_26.6 [template = constants.%.14]
+// CHECK:STDOUT:   %.loc18_26.8: init %.12 = array_init (%.loc18_26.4, %.loc18_26.7) to file.%c.var [template = constants.%array]
+// CHECK:STDOUT:   %.loc18_27: init %.12 = converted %.loc18_26.1, %.loc18_26.8 [template = constants.%array]
+// CHECK:STDOUT:   assign file.%c.var, %.loc18_27
+// CHECK:STDOUT:   %c.ref: ref %.12 = name_ref c, file.%c
+// CHECK:STDOUT:   %.loc22_17: i32 = int_literal 10 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc22_19.1: ref i32 = array_index %c.ref, <error> [template = <error>]
+// CHECK:STDOUT:   %.loc22_19.2: i32 = bind_value %.loc22_19.1
+// CHECK:STDOUT:   assign file.%d.var, %.loc22_19.2
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 5 - 5
toolchain/check/testdata/index/fail_non_deterministic_type.carbon

@@ -11,9 +11,9 @@
 var a: (i32, i32) = (2, 3);
 var b: i32 = 0;
 // CHECK:STDERR: fail_non_deterministic_type.carbon:[[@LINE+3]]:14: ERROR: Tuple index must be a constant.
-// CHECK:STDERR: var c: i32 = a[b];
-// CHECK:STDERR:              ^~~~
-var c: i32 = a[b];
+// CHECK:STDERR: var c: i32 = a.(b);
+// CHECK:STDERR:              ^~~~~
+var c: i32 = a.(b);
 
 // CHECK:STDOUT: --- fail_non_deterministic_type.carbon
 // CHECK:STDOUT:
@@ -92,8 +92,8 @@ var c: i32 = a[b];
 // CHECK:STDOUT:   assign file.%b.var, %.loc12
 // CHECK:STDOUT:   %a.ref: ref %.3 = name_ref a, file.%a
 // CHECK:STDOUT:   %b.ref: ref i32 = name_ref b, file.%b
-// CHECK:STDOUT:   %.loc16_16: i32 = bind_value %b.ref
-// CHECK:STDOUT:   %.loc16_17: ref <error> = tuple_index %a.ref, <error> [template = <error>]
+// CHECK:STDOUT:   %.loc16_17: i32 = bind_value %b.ref
+// CHECK:STDOUT:   %.loc16_15: ref <error> = tuple_index %a.ref, <error> [template = <error>]
 // CHECK:STDOUT:   assign file.%c.var, <error>
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 49 - 3
toolchain/check/testdata/index/fail_non_tuple_access.carbon

@@ -9,10 +9,17 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/index/fail_non_tuple_access.carbon
 
 fn Main() {
-  // CHECK:STDERR: fail_non_tuple_access.carbon:[[@LINE+3]]:3: ERROR: Type `i32` does not support indexing.
+  // CHECK:STDERR: fail_non_tuple_access.carbon:[[@LINE+4]]:3: ERROR: Type `i32` does not support indexing.
   // CHECK:STDERR:   0[1];
   // CHECK:STDERR:   ^~~~
+  // CHECK:STDERR:
   0[1];
+
+  var non_tuple: [i32; 2] = (5, 5);
+  // CHECK:STDERR: fail_non_tuple_access.carbon:[[@LINE+3]]:20: ERROR: Type `[i32; 2]` does not support tuple indexing. Only tuples can be indexed that way.
+  // CHECK:STDERR:   var first: i32 = non_tuple.0;
+  // CHECK:STDERR:                    ^~~~~~~~~~~
+  var first: i32 = non_tuple.0;
 }
 
 // CHECK:STDOUT: --- fail_non_tuple_access.carbon
@@ -23,10 +30,19 @@ fn Main() {
 // CHECK:STDOUT:   %Main: %Main.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: i32 = int_literal 0 [template]
 // CHECK:STDOUT:   %.3: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.5: type = array_type %.4, i32 [template]
+// CHECK:STDOUT:   %.6: type = ptr_type %.5 [template]
+// CHECK:STDOUT:   %.7: i32 = int_literal 5 [template]
+// CHECK:STDOUT:   %.8: type = tuple_type (i32, i32) [template]
+// CHECK:STDOUT:   %array: %.5 = tuple_value (%.7, %.7) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int32 = %import_ref
 // CHECK:STDOUT:     import Core//prelude
 // CHECK:STDOUT:     import Core//prelude/operators
 // CHECK:STDOUT:     import Core//prelude/types
@@ -35,6 +51,7 @@ fn Main() {
 // CHECK:STDOUT:     import Core//prelude/operators/comparison
 // CHECK:STDOUT:     import Core//prelude/types/bool
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: %Int32.type = import_ref Core//prelude/types, inst+4, loaded [template = constants.%Int32]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -48,8 +65,37 @@ fn Main() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Main() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc15_3: i32 = int_literal 0 [template = constants.%.2]
-// CHECK:STDOUT:   %.loc15_5: i32 = int_literal 1 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc16_3: i32 = int_literal 0 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc16_5: i32 = int_literal 1 [template = constants.%.3]
+// CHECK:STDOUT:   %int.make_type_32.loc18: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc18_24: i32 = int_literal 2 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc18_19.1: type = value_of_initializer %int.make_type_32.loc18 [template = i32]
+// CHECK:STDOUT:   %.loc18_19.2: type = converted %int.make_type_32.loc18, %.loc18_19.1 [template = i32]
+// CHECK:STDOUT:   %.loc18_25: type = array_type %.loc18_24, i32 [template = constants.%.5]
+// CHECK:STDOUT:   %non_tuple.var: ref %.5 = var non_tuple
+// CHECK:STDOUT:   %non_tuple: ref %.5 = bind_name non_tuple, %non_tuple.var
+// CHECK:STDOUT:   %.loc18_30: i32 = int_literal 5 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc18_33: i32 = int_literal 5 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc18_34.1: %.8 = tuple_literal (%.loc18_30, %.loc18_33)
+// CHECK:STDOUT:   %.loc18_34.2: i32 = int_literal 0 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc18_34.3: ref i32 = array_index %non_tuple.var, %.loc18_34.2
+// CHECK:STDOUT:   %.loc18_34.4: init i32 = initialize_from %.loc18_30 to %.loc18_34.3 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc18_34.5: i32 = int_literal 1 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc18_34.6: ref i32 = array_index %non_tuple.var, %.loc18_34.5
+// CHECK:STDOUT:   %.loc18_34.7: init i32 = initialize_from %.loc18_33 to %.loc18_34.6 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc18_34.8: init %.5 = array_init (%.loc18_34.4, %.loc18_34.7) to %non_tuple.var [template = constants.%array]
+// CHECK:STDOUT:   %.loc18_35: init %.5 = converted %.loc18_34.1, %.loc18_34.8 [template = constants.%array]
+// CHECK:STDOUT:   assign %non_tuple.var, %.loc18_35
+// CHECK:STDOUT:   %int.make_type_32.loc22: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc22_14.1: type = value_of_initializer %int.make_type_32.loc22 [template = i32]
+// CHECK:STDOUT:   %.loc22_14.2: type = converted %int.make_type_32.loc22, %.loc22_14.1 [template = i32]
+// CHECK:STDOUT:   %first.var: ref i32 = var first
+// CHECK:STDOUT:   %first: ref i32 = bind_name first, %first.var
+// CHECK:STDOUT:   %non_tuple.ref: ref %.5 = name_ref non_tuple, %non_tuple
+// CHECK:STDOUT:   %.loc22_30: i32 = int_literal 0 [template = constants.%.2]
+// CHECK:STDOUT:   assign %first.var, <error>
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:

+ 9 - 9
toolchain/check/testdata/index/fail_out_of_bound_not_literal.carbon

@@ -10,9 +10,9 @@
 
 var a: (i32, i32) = (12, 34);
 // CHECK:STDERR: fail_out_of_bound_not_literal.carbon:[[@LINE+3]]:14: ERROR: Tuple element index `2` is past the end of type `(i32, i32)`.
-// CHECK:STDERR: var b: i32 = a[{.index = 2}.index];
-// CHECK:STDERR:              ^~~~~~~~~~~~~~~~~~~~~
-var b: i32 = a[{.index = 2}.index];
+// CHECK:STDERR: var b: i32 = a.({.index = 2}.index);
+// CHECK:STDERR:              ^~~~~~~~~~~~~~~~~~~~~~
+var b: i32 = a.({.index = 2}.index);
 
 // CHECK:STDOUT: --- fail_out_of_bound_not_literal.carbon
 // CHECK:STDOUT:
@@ -84,12 +84,12 @@ var b: i32 = a[{.index = 2}.index];
 // CHECK:STDOUT:   %.loc11_29: init %.3 = converted %.loc11_28.1, %.loc11_28.6 [template = constants.%tuple]
 // CHECK:STDOUT:   assign file.%a.var, %.loc11_29
 // CHECK:STDOUT:   %a.ref: ref %.3 = name_ref a, file.%a
-// CHECK:STDOUT:   %.loc15_26: i32 = int_literal 2 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc15_27.1: %.8 = struct_literal (%.loc15_26)
-// CHECK:STDOUT:   %struct: %.8 = struct_value (%.loc15_26) [template = constants.%struct]
-// CHECK:STDOUT:   %.loc15_27.2: %.8 = converted %.loc15_27.1, %struct [template = constants.%struct]
-// CHECK:STDOUT:   %.loc15_28: i32 = struct_access %.loc15_27.2, element0 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc15_34: ref <error> = tuple_index %a.ref, <error> [template = <error>]
+// CHECK:STDOUT:   %.loc15_27: i32 = int_literal 2 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc15_28.1: %.8 = struct_literal (%.loc15_27)
+// CHECK:STDOUT:   %struct: %.8 = struct_value (%.loc15_27) [template = constants.%struct]
+// CHECK:STDOUT:   %.loc15_28.2: %.8 = converted %.loc15_28.1, %struct [template = constants.%struct]
+// CHECK:STDOUT:   %.loc15_29: i32 = struct_access %.loc15_28.2, element0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc15_15: ref <error> = tuple_index %a.ref, <error> [template = <error>]
 // CHECK:STDOUT:   assign file.%b.var, <error>
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 4 - 4
toolchain/check/testdata/index/fail_tuple_index_error.carbon

@@ -9,10 +9,10 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/index/fail_tuple_index_error.carbon
 
 var a: (i32, i32) = (12, 6);
-// CHECK:STDERR: fail_tuple_index_error.carbon:[[@LINE+3]]:16: ERROR: Name `oops` not found.
-// CHECK:STDERR: var b: i32 = a[oops];
-// CHECK:STDERR:                ^~~~
-var b: i32 = a[oops];
+// CHECK:STDERR: fail_tuple_index_error.carbon:[[@LINE+3]]:17: ERROR: Name `oops` not found.
+// CHECK:STDERR: var b: i32 = a.(oops);
+// CHECK:STDERR:                 ^~~~
+var b: i32 = a.(oops);
 
 // CHECK:STDOUT: --- fail_tuple_index_error.carbon
 // CHECK:STDOUT:

+ 9 - 9
toolchain/check/testdata/index/fail_tuple_large_index.carbon

@@ -11,14 +11,14 @@
 var a: (i32,) = (12,);
 var b: (i32,) = a;
 // CHECK:STDERR: fail_tuple_large_index.carbon:[[@LINE+4]]:14: ERROR: Tuple element index `1` is past the end of type `(i32,)`.
-// CHECK:STDERR: var c: i32 = b[1];
-// CHECK:STDERR:              ^~~~
+// CHECK:STDERR: var c: i32 = b.1;
+// CHECK:STDERR:              ^~~
 // CHECK:STDERR:
-var c: i32 = b[1];
+var c: i32 = b.1;
 // CHECK:STDERR: fail_tuple_large_index.carbon:[[@LINE+3]]:14: ERROR: Tuple element index `2147483647` is past the end of type `(i32,)`.
-// CHECK:STDERR: var d: i32 = b[0x7FFF_FFFF];
-// CHECK:STDERR:              ^~~~~~~~~~~~~~
-var d: i32 = b[0x7FFF_FFFF];
+// CHECK:STDERR: var d: i32 = b.(0x7FFF_FFFF);
+// CHECK:STDERR:              ^~~~~~~~~~~~~~~
+var d: i32 = b.(0x7FFF_FFFF);
 
 // CHECK:STDOUT: --- fail_tuple_large_index.carbon
 // CHECK:STDOUT:
@@ -100,11 +100,11 @@ var d: i32 = b[0x7FFF_FFFF];
 // CHECK:STDOUT:   assign file.%b.var, %.loc12_18
 // CHECK:STDOUT:   %b.ref.loc17: ref %.3 = name_ref b, file.%b
 // CHECK:STDOUT:   %.loc17_16: i32 = int_literal 1 [template = constants.%.5]
-// CHECK:STDOUT:   %.loc17_17: ref <error> = tuple_index %b.ref.loc17, <error> [template = <error>]
+// CHECK:STDOUT:   %.loc17_15: ref <error> = tuple_index %b.ref.loc17, <error> [template = <error>]
 // CHECK:STDOUT:   assign file.%c.var, <error>
 // CHECK:STDOUT:   %b.ref.loc21: ref %.3 = name_ref b, file.%b
-// CHECK:STDOUT:   %.loc21_16: i32 = int_literal 2147483647 [template = constants.%.6]
-// CHECK:STDOUT:   %.loc21_27: ref <error> = tuple_index %b.ref.loc21, <error> [template = <error>]
+// CHECK:STDOUT:   %.loc21_17: i32 = int_literal 2147483647 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc21_15: ref <error> = tuple_index %b.ref.loc21, <error> [template = <error>]
 // CHECK:STDOUT:   assign file.%d.var, <error>
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

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

@@ -9,10 +9,10 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon
 
 var a: (i32, i32) = (12, 6);
-// CHECK:STDERR: fail_tuple_non_int_indexing.carbon:[[@LINE+3]]:16: ERROR: Cannot implicitly convert from `f64` to `i32`.
-// CHECK:STDERR: var b: i32 = a[2.6];
-// CHECK:STDERR:                ^~~
-var b: i32 = a[2.6];
+// CHECK:STDERR: fail_tuple_non_int_indexing.carbon:[[@LINE+3]]:17: ERROR: Cannot implicitly convert from `f64` to `i32`.
+// CHECK:STDERR: var b: i32 = a.(2.6);
+// CHECK:STDERR:                 ^~~
+var b: i32 = a.(2.6);
 
 // CHECK:STDOUT: --- fail_tuple_non_int_indexing.carbon
 // CHECK:STDOUT:
@@ -82,8 +82,8 @@ var b: i32 = a[2.6];
 // CHECK:STDOUT:   %.loc11_28: init %.3 = converted %.loc11_27.1, %.loc11_27.6 [template = constants.%tuple]
 // CHECK:STDOUT:   assign file.%a.var, %.loc11_28
 // CHECK:STDOUT:   %a.ref: ref %.3 = name_ref a, file.%a
-// CHECK:STDOUT:   %.loc15_16: f64 = float_literal 2.6000000000000001 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc15_19: ref <error> = tuple_index %a.ref, <error> [template = <error>]
+// CHECK:STDOUT:   %.loc15_17: f64 = float_literal 2.6000000000000001 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc15_15: ref <error> = tuple_index %a.ref, <error> [template = <error>]
 // CHECK:STDOUT:   assign file.%b.var, <error>
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 4 - 4
toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon

@@ -10,9 +10,9 @@
 
 var a: (i32, i32) = (12, 6);
 // CHECK:STDERR: fail_tuple_out_of_bound_access.carbon:[[@LINE+3]]:14: ERROR: Tuple element index `2` is past the end of type `(i32, i32)`.
-// CHECK:STDERR: var b: i32 = a[2];
-// CHECK:STDERR:              ^~~~
-var b: i32 = a[2];
+// CHECK:STDERR: var b: i32 = a.2;
+// CHECK:STDERR:              ^~~
+var b: i32 = a.2;
 
 // CHECK:STDOUT: --- fail_tuple_out_of_bound_access.carbon
 // CHECK:STDOUT:
@@ -83,7 +83,7 @@ var b: i32 = a[2];
 // CHECK:STDOUT:   assign file.%a.var, %.loc11_28
 // CHECK:STDOUT:   %a.ref: ref %.3 = name_ref a, file.%a
 // CHECK:STDOUT:   %.loc15_16: i32 = int_literal 2 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc15_17: ref <error> = tuple_index %a.ref, <error> [template = <error>]
+// CHECK:STDOUT:   %.loc15_15: ref <error> = tuple_index %a.ref, <error> [template = <error>]
 // CHECK:STDOUT:   assign file.%b.var, <error>
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 9 - 9
toolchain/check/testdata/index/index_not_literal.carbon

@@ -9,7 +9,7 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/index/index_not_literal.carbon
 
 var a: (i32, i32) = (12, 34);
-var b: i32 = a[{.index = 1}.index];
+var b: i32 = a.({.index = 1}.index);
 
 // CHECK:STDOUT: --- index_not_literal.carbon
 // CHECK:STDOUT:
@@ -81,14 +81,14 @@ var b: i32 = a[{.index = 1}.index];
 // CHECK:STDOUT:   %.loc11_29: init %.3 = converted %.loc11_28.1, %.loc11_28.6 [template = constants.%tuple]
 // CHECK:STDOUT:   assign file.%a.var, %.loc11_29
 // CHECK:STDOUT:   %a.ref: ref %.3 = name_ref a, file.%a
-// CHECK:STDOUT:   %.loc12_26: i32 = int_literal 1 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc12_27.1: %.8 = struct_literal (%.loc12_26)
-// CHECK:STDOUT:   %struct: %.8 = struct_value (%.loc12_26) [template = constants.%struct]
-// CHECK:STDOUT:   %.loc12_27.2: %.8 = converted %.loc12_27.1, %struct [template = constants.%struct]
-// CHECK:STDOUT:   %.loc12_28: i32 = struct_access %.loc12_27.2, element0 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc12_34.1: ref i32 = tuple_index %a.ref, %.loc12_28
-// CHECK:STDOUT:   %.loc12_34.2: i32 = bind_value %.loc12_34.1
-// CHECK:STDOUT:   assign file.%b.var, %.loc12_34.2
+// CHECK:STDOUT:   %.loc12_27: i32 = int_literal 1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc12_28.1: %.8 = struct_literal (%.loc12_27)
+// CHECK:STDOUT:   %struct: %.8 = struct_value (%.loc12_27) [template = constants.%struct]
+// CHECK:STDOUT:   %.loc12_28.2: %.8 = converted %.loc12_28.1, %struct [template = constants.%struct]
+// CHECK:STDOUT:   %.loc12_29: i32 = struct_access %.loc12_28.2, element0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc12_15.1: ref i32 = tuple_index %a.ref, %.loc12_29
+// CHECK:STDOUT:   %.loc12_15.2: i32 = bind_value %.loc12_15.1
+// CHECK:STDOUT:   assign file.%b.var, %.loc12_15.2
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -10,7 +10,7 @@
 
 var a: (i32,) = (12,);
 var b: (i32,) = a;
-var c: i32 = b[0];
+var c: i32 = b.0;
 
 // CHECK:STDOUT: --- tuple_element_access.carbon
 // CHECK:STDOUT:
@@ -85,9 +85,9 @@ var c: i32 = b[0];
 // CHECK:STDOUT:   assign file.%b.var, %.loc12_18
 // CHECK:STDOUT:   %b.ref: ref %.3 = name_ref b, file.%b
 // CHECK:STDOUT:   %.loc13_16: i32 = int_literal 0 [template = constants.%.5]
-// CHECK:STDOUT:   %.loc13_17.1: ref i32 = tuple_index %b.ref, %.loc13_16
-// CHECK:STDOUT:   %.loc13_17.2: i32 = bind_value %.loc13_17.1
-// CHECK:STDOUT:   assign file.%c.var, %.loc13_17.2
+// CHECK:STDOUT:   %.loc13_15.1: ref i32 = tuple_index %b.ref, %.loc13_16
+// CHECK:STDOUT:   %.loc13_15.2: i32 = bind_value %.loc13_15.1
+// CHECK:STDOUT:   assign file.%c.var, %.loc13_15.2
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -11,7 +11,7 @@
 fn F() -> (i32,) { return (0,); }
 
 fn Run() -> i32 {
-  return F()[0];
+  return F().0;
 }
 
 // CHECK:STDOUT: --- tuple_return_value_access.carbon
@@ -85,8 +85,8 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   %.loc14_14: i32 = int_literal 0 [template = constants.%.4]
 // CHECK:STDOUT:   %.loc14_11.1: ref %.3 = temporary_storage
 // CHECK:STDOUT:   %.loc14_11.2: ref %.3 = temporary %.loc14_11.1, %F.call
-// CHECK:STDOUT:   %.loc14_15.1: ref i32 = tuple_index %.loc14_11.2, %.loc14_14
-// CHECK:STDOUT:   %.loc14_15.2: i32 = bind_value %.loc14_15.1
-// CHECK:STDOUT:   return %.loc14_15.2
+// CHECK:STDOUT:   %.loc14_13.1: ref i32 = tuple_index %.loc14_11.2, %.loc14_14
+// CHECK:STDOUT:   %.loc14_13.2: i32 = bind_value %.loc14_13.1
+// CHECK:STDOUT:   return %.loc14_13.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 3
toolchain/check/testdata/let/convert.carbon

@@ -12,7 +12,7 @@ fn F() -> i32 {
   var v: (i32, i32, i32) = (1, 2, 3);
   // Convert from object representation to value representation.
   let w: (i32, i32, i32) = v;
-  return w[1];
+  return w.1;
 }
 
 // CHECK:STDOUT: --- convert.carbon
@@ -113,7 +113,7 @@ fn F() -> i32 {
 // CHECK:STDOUT:   %w: %.3 = bind_name w, %.loc14_29
 // CHECK:STDOUT:   %w.ref: %.3 = name_ref w, %w
 // CHECK:STDOUT:   %.loc15_12: i32 = int_literal 1 [template = constants.%.5]
-// CHECK:STDOUT:   %.loc15_13: i32 = tuple_index %w.ref, %.loc15_12
-// CHECK:STDOUT:   return %.loc15_13
+// CHECK:STDOUT:   %.loc15_11: i32 = tuple_index %w.ref, %.loc15_12
+// CHECK:STDOUT:   return %.loc15_11
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -13,8 +13,8 @@ fn Main() {
   a = 9;
 
   var b: (i32, i32) = (1, 2);
-  b[0] = 3;
-  b[1] = 4;
+  b.0 = 3;
+  b.1 = 4;
 
   var c: {.a: i32, .b: i32} = {.a = 1, .b = 2};
   c.a = 3;
@@ -111,14 +111,14 @@ fn Main() {
 // CHECK:STDOUT:   assign %b.var, %.loc15_29
 // CHECK:STDOUT:   %b.ref.loc16: ref %.5 = name_ref b, %b
 // CHECK:STDOUT:   %.loc16_5: i32 = int_literal 0 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc16_6: ref i32 = tuple_index %b.ref.loc16, %.loc16_5
-// CHECK:STDOUT:   %.loc16_10: i32 = int_literal 3 [template = constants.%.10]
-// CHECK:STDOUT:   assign %.loc16_6, %.loc16_10
+// CHECK:STDOUT:   %.loc16_4: ref i32 = tuple_index %b.ref.loc16, %.loc16_5
+// CHECK:STDOUT:   %.loc16_9: i32 = int_literal 3 [template = constants.%.10]
+// CHECK:STDOUT:   assign %.loc16_4, %.loc16_9
 // CHECK:STDOUT:   %b.ref.loc17: ref %.5 = name_ref b, %b
 // CHECK:STDOUT:   %.loc17_5: i32 = int_literal 1 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc17_6: ref i32 = tuple_index %b.ref.loc17, %.loc17_5
-// CHECK:STDOUT:   %.loc17_10: i32 = int_literal 4 [template = constants.%.11]
-// CHECK:STDOUT:   assign %.loc17_6, %.loc17_10
+// CHECK:STDOUT:   %.loc17_4: ref i32 = tuple_index %b.ref.loc17, %.loc17_5
+// CHECK:STDOUT:   %.loc17_9: i32 = int_literal 4 [template = constants.%.11]
+// CHECK:STDOUT:   assign %.loc17_4, %.loc17_9
 // CHECK:STDOUT:   %int.make_type_32.loc19_15: init type = call constants.%Int32() [template = i32]
 // CHECK:STDOUT:   %.loc19_15.1: type = value_of_initializer %int.make_type_32.loc19_15 [template = i32]
 // CHECK:STDOUT:   %.loc19_15.2: type = converted %int.make_type_32.loc19_15, %.loc19_15.1 [template = i32]

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

@@ -16,8 +16,8 @@ fn F() {
   var r: i32* = &s.b;
 
   var t: (i32, i32) = (1, 2);
-  var t0: i32* = &t[0];
-  var t1: i32* = &t[1];
+  var t0: i32* = &t.0;
+  var t1: i32* = &t.1;
 }
 
 // CHECK:STDOUT: --- address_of_lvalue.carbon
@@ -146,8 +146,8 @@ fn F() {
 // CHECK:STDOUT:   %t0: ref %.6 = bind_name t0, %t0.var
 // CHECK:STDOUT:   %t.ref.loc19: ref %.8 = name_ref t, %t
 // CHECK:STDOUT:   %.loc19_21: i32 = int_literal 0 [template = constants.%.10]
-// CHECK:STDOUT:   %.loc19_22: ref i32 = tuple_index %t.ref.loc19, %.loc19_21
-// CHECK:STDOUT:   %.loc19_18: %.6 = addr_of %.loc19_22
+// CHECK:STDOUT:   %.loc19_20: ref i32 = tuple_index %t.ref.loc19, %.loc19_21
+// CHECK:STDOUT:   %.loc19_18: %.6 = addr_of %.loc19_20
 // CHECK:STDOUT:   assign %t0.var, %.loc19_18
 // CHECK:STDOUT:   %int.make_type_32.loc20: init type = call constants.%Int32() [template = i32]
 // CHECK:STDOUT:   %.loc20_14.1: type = value_of_initializer %int.make_type_32.loc20 [template = i32]
@@ -157,8 +157,8 @@ fn F() {
 // CHECK:STDOUT:   %t1: ref %.6 = bind_name t1, %t1.var
 // CHECK:STDOUT:   %t.ref.loc20: ref %.8 = name_ref t, %t
 // CHECK:STDOUT:   %.loc20_21: i32 = int_literal 1 [template = constants.%.4]
-// CHECK:STDOUT:   %.loc20_22: ref i32 = tuple_index %t.ref.loc20, %.loc20_21
-// CHECK:STDOUT:   %.loc20_18: %.6 = addr_of %.loc20_22
+// CHECK:STDOUT:   %.loc20_20: ref i32 = tuple_index %t.ref.loc20, %.loc20_21
+// CHECK:STDOUT:   %.loc20_18: %.6 = addr_of %.loc20_20
 // CHECK:STDOUT:   assign %t1.var, %.loc20_18
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 3 - 3
toolchain/check/testdata/pointer/fail_address_of_value.carbon

@@ -86,10 +86,10 @@ fn AddressOfType() {
 
 fn AddressOfTupleElementValue() {
   // CHECK:STDERR: fail_address_of_value.carbon:[[@LINE+4]]:3: ERROR: Cannot take the address of non-reference expression.
-  // CHECK:STDERR:   &((1, 2)[0]);
+  // CHECK:STDERR:   &((1, 2).0);
   // CHECK:STDERR:   ^
   // CHECK:STDERR:
-  &((1, 2)[0]);
+  &((1, 2).0);
 }
 
 fn AddressOfParam(param: i32) {
@@ -277,7 +277,7 @@ fn AddressOfParam(param: i32) {
 // CHECK:STDOUT:   %.loc92_12: i32 = int_literal 0 [template = constants.%.3]
 // CHECK:STDOUT:   %tuple: %.13 = tuple_value (%.loc92_6, %.loc92_9) [template = constants.%tuple]
 // CHECK:STDOUT:   %.loc92_10.2: %.13 = converted %.loc92_10.1, %tuple [template = constants.%tuple]
-// CHECK:STDOUT:   %.loc92_13: i32 = tuple_index %.loc92_10.2, %.loc92_12 [template = constants.%.11]
+// CHECK:STDOUT:   %.loc92_11: i32 = tuple_index %.loc92_10.2, %.loc92_12 [template = constants.%.11]
 // CHECK:STDOUT:   %.loc92_3: %.4 = addr_of <error> [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -303,6 +303,7 @@ CARBON_DIAGNOSTIC_KIND(StructInitMissingFieldInConversion)
 CARBON_DIAGNOSTIC_KIND(StructNameDuplicate)
 CARBON_DIAGNOSTIC_KIND(StructNamePrevious)
 CARBON_DIAGNOSTIC_KIND(TupleIndexNotConstant)
+CARBON_DIAGNOSTIC_KIND(TupleIndexOnANonTupleType)
 CARBON_DIAGNOSTIC_KIND(TupleIndexOutOfBounds)
 CARBON_DIAGNOSTIC_KIND(TupleInitElementCountMismatch)
 CARBON_DIAGNOSTIC_KIND(ReturnedVarHere)

+ 4 - 4
toolchain/lower/testdata/class/value_access.carbon

@@ -16,7 +16,7 @@ fn F(c: C) -> i32 {
   // TODO: `c.a` is a value expression here, which forces a value binding as
   // part of the member access, creating a tuple value temporary. We could
   // defer performing the value binding to avoid creating this temporary.
-  return c.a[1];
+  return c.a.1;
 }
 
 // CHECK:STDOUT: ; ModuleID = 'value_access.carbon'
@@ -38,7 +38,7 @@ fn F(c: C) -> i32 {
 // CHECK:STDOUT:   store i32 %.loc19_11.5, ptr %tuple2, align 4
 // CHECK:STDOUT:   %tuple3 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %tuple, i32 0, i32 2
 // CHECK:STDOUT:   store i32 %.loc19_11.7, ptr %tuple3, align 4
-// CHECK:STDOUT:   %.loc19_15.tuple.index = getelementptr inbounds nuw { i32, i32, i32 }, ptr %tuple, i32 0, i32 1
-// CHECK:STDOUT:   %.loc19_15.tuple.index.load = load i32, ptr %.loc19_15.tuple.index, align 4
-// CHECK:STDOUT:   ret i32 %.loc19_15.tuple.index.load
+// CHECK:STDOUT:   %.loc19_13.tuple.index = getelementptr inbounds nuw { i32, i32, i32 }, ptr %tuple, i32 0, i32 1
+// CHECK:STDOUT:   %.loc19_13.tuple.index.load = load i32, ptr %.loc19_13.tuple.index, align 4
+// CHECK:STDOUT:   ret i32 %.loc19_13.tuple.index.load
 // CHECK:STDOUT: }

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

@@ -9,7 +9,7 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/function/call/tuple_param_with_return_slot.carbon
 
 fn F(a: (), b: (i32,), c: (i32, i32)) -> (i32, i32, i32) {
-  return (b[0], c[0], c[1]);
+  return (b.0, c.0, c.1);
 }
 
 fn Main() {
@@ -23,17 +23,17 @@ fn Main() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @F(ptr sret({ i32, i32, i32 }) %return, { i32 } %b, ptr %c) {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %.loc12_14.tuple.index = extractvalue { i32 } %b, 0
-// CHECK:STDOUT:   %.loc12_20.tuple.index = getelementptr inbounds nuw { i32, i32 }, ptr %c, i32 0, i32 0
-// CHECK:STDOUT:   %.loc12_20.tuple.index.load = load i32, ptr %.loc12_20.tuple.index, align 4
-// CHECK:STDOUT:   %.loc12_26.tuple.index = getelementptr inbounds nuw { i32, i32 }, ptr %c, i32 0, i32 1
-// CHECK:STDOUT:   %.loc12_26.tuple.index.load = load i32, ptr %.loc12_26.tuple.index, align 4
-// CHECK:STDOUT:   %.loc12_27.2.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 %.loc12_14.tuple.index, ptr %.loc12_27.2.tuple.elem, align 4
-// CHECK:STDOUT:   %.loc12_27.4.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 %.loc12_20.tuple.index.load, ptr %.loc12_27.4.tuple.elem, align 4
-// CHECK:STDOUT:   %.loc12_27.6.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 2
-// CHECK:STDOUT:   store i32 %.loc12_26.tuple.index.load, ptr %.loc12_27.6.tuple.elem, align 4
+// CHECK:STDOUT:   %.loc12_12.tuple.index = extractvalue { i32 } %b, 0
+// CHECK:STDOUT:   %.loc12_17.tuple.index = getelementptr inbounds nuw { i32, i32 }, ptr %c, i32 0, i32 0
+// CHECK:STDOUT:   %.loc12_17.tuple.index.load = load i32, ptr %.loc12_17.tuple.index, align 4
+// CHECK:STDOUT:   %.loc12_22.tuple.index = getelementptr inbounds nuw { i32, i32 }, ptr %c, i32 0, i32 1
+// CHECK:STDOUT:   %.loc12_22.tuple.index.load = load i32, ptr %.loc12_22.tuple.index, align 4
+// CHECK:STDOUT:   %.loc12_24.2.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 0
+// CHECK:STDOUT:   store i32 %.loc12_12.tuple.index, ptr %.loc12_24.2.tuple.elem, align 4
+// CHECK:STDOUT:   %.loc12_24.4.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 1
+// CHECK:STDOUT:   store i32 %.loc12_17.tuple.index.load, ptr %.loc12_24.4.tuple.elem, align 4
+// CHECK:STDOUT:   %.loc12_24.6.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 2
+// CHECK:STDOUT:   store i32 %.loc12_22.tuple.index.load, ptr %.loc12_24.6.tuple.elem, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 6
toolchain/lower/testdata/index/array_element_access.carbon

@@ -13,7 +13,7 @@ fn B() -> [i32; 2] { return (1, 2); }
 
 fn Run() {
   var a: [i32; 2] = A();
-  var b: i32 = A()[0];
+  var b: i32 = 1;
   var c: i32 = a[b];
   var d: i32 = B()[1];
 }
@@ -54,11 +54,7 @@ fn Run() {
 // CHECK:STDOUT:   %.loc15_22.11.array.index = getelementptr inbounds [2 x i32], ptr %a.var, i32 0, i32 1
 // CHECK:STDOUT:   store i32 %.loc15_22.9, ptr %.loc15_22.11.array.index, align 4
 // CHECK:STDOUT:   %b.var = alloca i32, align 4
-// CHECK:STDOUT:   %.loc16_17.1.temp = alloca { i32, i32 }, align 8
-// CHECK:STDOUT:   call void @A(ptr %.loc16_17.1.temp)
-// CHECK:STDOUT:   %.loc16_21.1.tuple.index = getelementptr inbounds nuw { i32, i32 }, ptr %.loc16_17.1.temp, i32 0, i32 0
-// CHECK:STDOUT:   %.loc16_21.2 = load i32, ptr %.loc16_21.1.tuple.index, align 4
-// CHECK:STDOUT:   store i32 %.loc16_21.2, ptr %b.var, align 4
+// CHECK:STDOUT:   store i32 1, ptr %b.var, align 4
 // CHECK:STDOUT:   %c.var = alloca i32, align 4
 // CHECK:STDOUT:   %.loc17_18 = load i32, ptr %b.var, align 4
 // CHECK:STDOUT:   %.loc17_19.1.array.index = getelementptr inbounds [2 x i32], ptr %a.var, i32 0, i32 %.loc17_18

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

@@ -10,8 +10,8 @@
 
 fn Run() -> i32 {
   var a: (i32, i32, i32) = (0, 1, 2);
-  var b: i32 = a[0];
-  var c: i32 = a[2];
+  var b: i32 = a.0;
+  var c: i32 = a.2;
   return 0;
 }
 
@@ -28,13 +28,13 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   %.loc12_36.6.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %a.var, i32 0, i32 2
 // CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a.var, ptr align 4 @tuple.loc12_37, i64 12, i1 false)
 // CHECK:STDOUT:   %b.var = alloca i32, align 4
-// CHECK:STDOUT:   %.loc13_19.1.tuple.index = getelementptr inbounds nuw { i32, i32, i32 }, ptr %a.var, i32 0, i32 0
-// CHECK:STDOUT:   %.loc13_19.2 = load i32, ptr %.loc13_19.1.tuple.index, align 4
-// CHECK:STDOUT:   store i32 %.loc13_19.2, ptr %b.var, align 4
+// CHECK:STDOUT:   %.loc13_17.1.tuple.index = getelementptr inbounds nuw { i32, i32, i32 }, ptr %a.var, i32 0, i32 0
+// CHECK:STDOUT:   %.loc13_17.2 = load i32, ptr %.loc13_17.1.tuple.index, align 4
+// CHECK:STDOUT:   store i32 %.loc13_17.2, ptr %b.var, align 4
 // CHECK:STDOUT:   %c.var = alloca i32, align 4
-// CHECK:STDOUT:   %.loc14_19.1.tuple.index = getelementptr inbounds nuw { i32, i32, i32 }, ptr %a.var, i32 0, i32 2
-// CHECK:STDOUT:   %.loc14_19.2 = load i32, ptr %.loc14_19.1.tuple.index, align 4
-// CHECK:STDOUT:   store i32 %.loc14_19.2, ptr %c.var, align 4
+// CHECK:STDOUT:   %.loc14_17.1.tuple.index = getelementptr inbounds nuw { i32, i32, i32 }, ptr %a.var, i32 0, i32 2
+// CHECK:STDOUT:   %.loc14_17.2 = load i32, ptr %.loc14_17.1.tuple.index, align 4
+// CHECK:STDOUT:   store i32 %.loc14_17.2, ptr %c.var, align 4
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -11,7 +11,7 @@
 fn F() -> (i32, i32) { return (12, 24); }
 
 fn Run() {
-  var t: i32 = F()[1];
+  var t: i32 = F().1;
 }
 
 // CHECK:STDOUT: ; ModuleID = 'tuple_return_value_access.carbon'
@@ -32,9 +32,9 @@ fn Run() {
 // CHECK:STDOUT:   %t.var = alloca i32, align 4
 // CHECK:STDOUT:   %.loc14_17.1.temp = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   call void @F(ptr %.loc14_17.1.temp)
-// CHECK:STDOUT:   %.loc14_21.1.tuple.index = getelementptr inbounds nuw { i32, i32 }, ptr %.loc14_17.1.temp, i32 0, i32 1
-// CHECK:STDOUT:   %.loc14_21.2 = load i32, ptr %.loc14_21.1.tuple.index, align 4
-// CHECK:STDOUT:   store i32 %.loc14_21.2, ptr %t.var, align 4
+// CHECK:STDOUT:   %.loc14_19.1.tuple.index = getelementptr inbounds nuw { i32, i32 }, ptr %.loc14_17.1.temp, i32 0, i32 1
+// CHECK:STDOUT:   %.loc14_19.2 = load i32, ptr %.loc14_19.1.tuple.index, align 4
+// CHECK:STDOUT:   store i32 %.loc14_19.2, ptr %t.var, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 6 - 6
toolchain/lower/testdata/let/tuple.carbon

@@ -12,7 +12,7 @@ fn F() -> i32 {
   var a: (i32, i32, i32) = (1, 2, 3);
   var b: (i32, i32) = (4, 5);
   let c: ((i32, i32, i32), (i32, i32)) = (a, b);
-  return c[1][1];
+  return c.1.(1);
 }
 
 // CHECK:STDOUT: ; ModuleID = 'tuple.carbon'
@@ -59,11 +59,11 @@ fn F() -> i32 {
 // CHECK:STDOUT:   store ptr %tuple.loc14_43, ptr %tuple.loc14_476, align 8
 // CHECK:STDOUT:   %tuple.loc14_477 = getelementptr inbounds nuw { ptr, ptr }, ptr %tuple.loc14_47, i32 0, i32 1
 // CHECK:STDOUT:   store ptr %tuple.loc14_46, ptr %tuple.loc14_477, align 8
-// CHECK:STDOUT:   %.loc15_13.tuple.index = getelementptr inbounds nuw { ptr, ptr }, ptr %tuple.loc14_47, i32 0, i32 1
-// CHECK:STDOUT:   %.loc15_13.tuple.index.load = load ptr, ptr %.loc15_13.tuple.index, align 8
-// CHECK:STDOUT:   %.loc15_16.tuple.index = getelementptr inbounds nuw { i32, i32 }, ptr %.loc15_13.tuple.index.load, i32 0, i32 1
-// CHECK:STDOUT:   %.loc15_16.tuple.index.load = load i32, ptr %.loc15_16.tuple.index, align 4
-// CHECK:STDOUT:   ret i32 %.loc15_16.tuple.index.load
+// CHECK:STDOUT:   %.loc15_11.tuple.index = getelementptr inbounds nuw { ptr, ptr }, ptr %tuple.loc14_47, i32 0, i32 1
+// CHECK:STDOUT:   %.loc15_11.tuple.index.load = load ptr, ptr %.loc15_11.tuple.index, align 8
+// CHECK:STDOUT:   %.loc15_13.tuple.index = getelementptr inbounds nuw { i32, i32 }, ptr %.loc15_11.tuple.index.load, i32 0, i32 1
+// CHECK:STDOUT:   %.loc15_13.tuple.index.load = load i32, ptr %.loc15_13.tuple.index, align 4
+// CHECK:STDOUT:   ret i32 %.loc15_13.tuple.index.load
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)

+ 3 - 0
toolchain/parse/handle_period.cpp

@@ -24,6 +24,9 @@ static auto HandlePeriodOrArrow(Context& context, NodeKind node_kind,
   } else if (context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Base,
                                              NodeKind::BaseName)) {
     // OK, `.base`.
+  } else if (context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::IntLiteral,
+                                             NodeKind::IntLiteral)) {
+    // OK, '.42'.
   } else if (paren_state != State::Invalid &&
              context.PositionIs(Lex::TokenKind::OpenParen)) {
     state.state = paren_state;

+ 3 - 2
toolchain/parse/node_ids.h

@@ -71,8 +71,9 @@ struct NodeIdInCategory : public NodeId {
 using AnyDeclId = NodeIdInCategory<NodeCategory::Decl>;
 using AnyExprId = NodeIdInCategory<NodeCategory::Expr>;
 using AnyImplAsId = NodeIdInCategory<NodeCategory::ImplAs>;
-using AnyMemberNameOrMemberExprId =
-    NodeIdInCategory<NodeCategory::MemberName | NodeCategory::MemberExpr>;
+using AnyMemberAccessId =
+    NodeIdInCategory<NodeCategory::MemberName | NodeCategory::MemberExpr |
+                     NodeCategory::IntConst>;
 using AnyModifierId = NodeIdInCategory<NodeCategory::Modifier>;
 using AnyPatternId = NodeIdInCategory<NodeCategory::Pattern>;
 using AnyStatementId = NodeIdInCategory<NodeCategory::Statement>;

+ 2 - 1
toolchain/parse/node_kind.def

@@ -212,9 +212,10 @@ CARBON_PARSE_NODE_KIND(MemberAccessExpr)
 
 CARBON_PARSE_NODE_KIND(PointerMemberAccessExpr)
 
+CARBON_PARSE_NODE_KIND(IntLiteral)
+
 CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(BoolLiteralFalse, False)
 CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(BoolLiteralTrue, True)
-CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(IntLiteral, IntLiteral)
 CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(RealLiteral, RealLiteral)
 CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(StringLiteral, StringLiteral)
 

+ 2 - 1
toolchain/parse/node_kind.h

@@ -33,9 +33,10 @@ class NodeCategory : public Printable<NodeCategory> {
     Modifier = 1 << 5,
     Pattern = 1 << 6,
     Statement = 1 << 7,
+    IntConst = 1 << 8,
     None = 0,
 
-    LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Statement)
+    LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/IntConst)
   };
 
   // Support implicit conversion so that the difference with the member enum is

+ 2 - 11
toolchain/parse/testdata/basics/fail_invalid_designators.carbon

@@ -15,15 +15,10 @@ fn F() {
   // CHECK:STDERR:     ^
   // CHECK:STDERR:
   a.;
-  // CHECK:STDERR: fail_invalid_designators.carbon:[[@LINE+4]]:5: ERROR: Expected identifier after `.`.
+  // CHECK:STDERR: fail_invalid_designators.carbon:[[@LINE+3]]:5: ERROR: Expected identifier after `.`.
   // CHECK:STDERR:   a.fn;
   // CHECK:STDERR:     ^~
-  // CHECK:STDERR:
   a.fn;
-  // CHECK:STDERR: fail_invalid_designators.carbon:[[@LINE+3]]:5: ERROR: Expected identifier after `.`.
-  // CHECK:STDERR:   a.42;
-  // CHECK:STDERR:     ^~
-  a.42;
 }
 
 // CHECK:STDOUT: - filename: fail_invalid_designators.carbon
@@ -42,10 +37,6 @@ fn F() {
 // CHECK:STDOUT:           {kind: 'IdentifierName', text: 'fn', has_error: yes},
 // CHECK:STDOUT:         {kind: 'MemberAccessExpr', text: '.', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
-// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
-// CHECK:STDOUT:           {kind: 'IdentifierName', text: '42', has_error: yes},
-// CHECK:STDOUT:         {kind: 'MemberAccessExpr', text: '.', subtree_size: 3},
-// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 4},
-// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 26 - 0
toolchain/parse/testdata/index/tuple_index.carbon

@@ -0,0 +1,26 @@
+// 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/parse/testdata/index/tuple_index.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/index/tuple_index.carbon
+
+let x: i32 = tuple.0;
+
+// CHECK:STDOUT: - filename: tuple_index.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'LetIntroducer', text: 'let'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'x'},
+// CHECK:STDOUT:         {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'LetInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'tuple'},
+// CHECK:STDOUT:         {kind: 'IntLiteral', text: '0'},
+// CHECK:STDOUT:       {kind: 'MemberAccessExpr', text: '.', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'LetDecl', text: ';', subtree_size: 9},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 5 - 2
toolchain/parse/typed_nodes.h

@@ -859,7 +859,7 @@ struct MemberAccessExpr {
 
   AnyExprId lhs;
   Lex::PeriodTokenIndex token;
-  AnyMemberNameOrMemberExprId rhs;
+  AnyMemberAccessId rhs;
 };
 
 // An indirect member access expression: `a->b` or `a->(b)`.
@@ -869,7 +869,7 @@ struct PointerMemberAccessExpr {
 
   AnyExprId lhs;
   Lex::MinusGreaterTokenIndex token;
-  AnyMemberNameOrMemberExprId rhs;
+  AnyMemberAccessId rhs;
 };
 
 // A prefix operator expression.
@@ -924,6 +924,9 @@ struct PostfixOperator {
       PostfixOperator<NodeKind::PostfixOperator##Name, Lex::Name##TokenIndex>;
 #include "toolchain/parse/node_kind.def"
 
+using IntLiteral = LeafNode<NodeKind::IntLiteral, Lex::IntLiteralTokenIndex,
+                            NodeCategory::Expr | NodeCategory::IntConst>;
+
 // `extern` as a standalone modifier.
 using ExternModifier = LeafNode<NodeKind::ExternModifier, Lex::ExternTokenIndex,
                                 NodeCategory::Modifier>;

+ 3 - 2
toolchain/sem_ir/typed_insts.h

@@ -975,8 +975,9 @@ struct TupleAccess {
 
 // Access to a tuple member by index, such as `tuple[index]`.
 struct TupleIndex {
-  static constexpr auto Kind = InstKind::TupleIndex.Define<Parse::IndexExprId>(
-      {.ir_name = "tuple_index"});
+  // TODO: Make Parse::NodeId more specific.
+  static constexpr auto Kind =
+      InstKind::TupleIndex.Define<Parse::NodeId>({.ir_name = "tuple_index"});
 
   TypeId type_id;
   InstId tuple_id;