Prechádzať zdrojové kódy

Tuples and tuple indexing (#3646)

Add support for extracting elements of a tuple by their numerical index.

Also formally add the well-established basic syntactic and semantic
rules for
tuples, for which we have had leads issues but no proposal, into the
design.
Richard Smith 2 rokov pred
rodič
commit
a1655b6858

+ 0 - 11
docs/design/expressions/indexing.md

@@ -13,8 +13,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 -   [Overview](#overview)
 -   [Overview](#overview)
 -   [Details](#details)
 -   [Details](#details)
     -   [Examples](#examples)
     -   [Examples](#examples)
--   [Open questions](#open-questions)
-    -   [Tuple indexing](#tuple-indexing)
 -   [Alternatives considered](#alternatives-considered)
 -   [Alternatives considered](#alternatives-considered)
 -   [References](#references)
 -   [References](#references)
 
 
@@ -133,15 +131,6 @@ class Span(T:! type) {
 }
 }
 ```
 ```
 
 
-## Open questions
-
-### Tuple indexing
-
-It is not clear how tuple indexing will be modeled. When indexing a tuple, the
-index value must be a constant, and the type of the expression can depend on
-that value, but we don't yet have the tools to express those properties in a
-Carbon API.
-
 ## Alternatives considered
 ## Alternatives considered
 
 
 -   [Different subscripting syntaxes](/proposals/p2274.md#different-subscripting-syntaxes)
 -   [Different subscripting syntaxes](/proposals/p2274.md#different-subscripting-syntaxes)

+ 51 - 0
docs/design/expressions/member_access.md

@@ -24,6 +24,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 -   [Instance binding](#instance-binding)
 -   [Instance binding](#instance-binding)
 -   [Non-instance members](#non-instance-members)
 -   [Non-instance members](#non-instance-members)
 -   [Non-vacuous member access restriction](#non-vacuous-member-access-restriction)
 -   [Non-vacuous member access restriction](#non-vacuous-member-access-restriction)
+-   [Tuple indexing](#tuple-indexing)
 -   [Precedence and associativity](#precedence-and-associativity)
 -   [Precedence and associativity](#precedence-and-associativity)
 -   [Alternatives considered](#alternatives-considered)
 -   [Alternatives considered](#alternatives-considered)
 -   [References](#references)
 -   [References](#references)
@@ -841,6 +842,56 @@ alias X3 = (i32 as Factory).Make;
 alias X4 = i32.((i32 as Factory).Make);
 alias X4 = i32.((i32 as Factory).Make);
 ```
 ```
 
 
+## Tuple indexing
+
+A tuple indexing expression is of the form:
+
+-   _expression_ `.` _integer-literal_
+-   _expression_ `->` _integer-literal_
+
+The _expression_ is required to be of tuple type.
+
+Each positional element of the tuple is considered to have a name that is the
+corresponding decimal integer: `0`, `1`, and so on. The spelling of the
+_integer-literal_ is required to exactly match one of those names, and the
+result is the corresponding element of the tuple.
+
+```
+// ✅ `a == 42`.
+let a: i32 = (41, 42, 43).1;
+// ❌ Error: no tuple element named `0x1`.
+let b: i32 = (1, 2, 3).0x1;
+// ❌ Error: no tuple element named `2`.
+let c: i32 = (1, 2).2;
+
+var t: (i32, i32, i32) = (1, 2, 3);
+let p: (i32, i32, i32)* = &t;
+// ✅ `m == 3`.
+let m: i32 = p->2;
+```
+
+In a compound member access of the form:
+
+-   _expression_ `.` `(` _expression_ `)`
+-   _expression_ `->` `(` _expression_ `)`
+
+in which the first _expression_ is a tuple and the second _expression_ is of
+integer or integer literal type, the second _expression_ is required to be a
+non-negative template constant that is less than the number of tuple elements,
+and the result is the corresponding positional element of the tuple.
+
+```
+// ✅ `d == 43`.
+let d: i32 = (41, 42, 43).(1 + 1);
+// ✅ `e == 2`.
+let template e:! i32 = (1, 2, 3).(0x1);
+// ❌ Error: no tuple element with index 4.
+let f: i32 = (1, 2).(2 * 2);
+
+// ✅ `n == 3`.
+let n: i32 = p->(e);
+```
+
 ## Precedence and associativity
 ## Precedence and associativity
 
 
 Member access expressions associate left-to-right:
 Member access expressions associate left-to-right:

+ 8 - 1
docs/design/lexical_conventions/README.md

@@ -33,4 +33,11 @@ A _lexical element_ is one of the following:
 -   a [symbolic token](symbolic_tokens.md)
 -   a [symbolic token](symbolic_tokens.md)
 
 
 The sequence of lexical elements is formed by repeatedly removing the longest
 The sequence of lexical elements is formed by repeatedly removing the longest
-initial sequence of characters that forms a valid lexical element.
+initial sequence of characters that forms a valid lexical element, with the
+following exception:
+
+-   When a numeric literal immediately follows a `.` or `->` token, with no
+    intervening whitespace, a real literal is never formed. Instead, the token
+    will end no later than the next `.` character. For example, `tuple.1.2` is
+    five tokens, `tuple` `.` `1` `.` `2`, not three tokens, `tuple` `.` `1.2`.
+    However, `tuple . 1.2` is lexed as three tokens.

+ 6 - 1
docs/design/lexical_conventions/numeric_literals.md

@@ -70,7 +70,12 @@ or C++ octal literal (other than `0`) is invalid in Carbon.
 Real numbers are written as a decimal or hexadecimal integer followed by a
 Real numbers are written as a decimal or hexadecimal integer followed by a
 period (`.`) followed by a sequence of one or more decimal or hexadecimal
 period (`.`) followed by a sequence of one or more decimal or hexadecimal
 digits, respectively. A digit is required on each side of the period. `0.` and
 digits, respectively. A digit is required on each side of the period. `0.` and
-`.3` are both invalid.
+`.3` are both lexed as two separate tokens: `0.(Util.Abs)()` and `tuple.3` both
+treat the period as member or element access, not as a radix point.
+
+To support tuple indexing, a real number literal is never formed immediately
+following a `.` token with no intervening whitespace. Instead, the result is an
+integer literal.
 
 
 A real number can be followed by an exponent character, an optional `+` or `-`
 A real number can be followed by an exponent character, an optional `+` or `-`
 (defaulting to `+` if absent), and a character sequence matching the grammar of
 (defaulting to `+` if absent), and a character sequence matching the grammar of

+ 92 - 59
docs/design/tuples.md

@@ -10,30 +10,25 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 
 ## Table of contents
 ## Table of contents
 
 
--   [TODO](#todo)
 -   [Overview](#overview)
 -   [Overview](#overview)
+-   [Element access](#element-access)
     -   [Empty tuples](#empty-tuples)
     -   [Empty tuples](#empty-tuples)
-    -   [Indices as compile-time constants](#indices-as-compile-time-constants)
+    -   [Trailing commas and single-element tuples](#trailing-commas-and-single-element-tuples)
+    -   [Tuple of types and tuple types](#tuple-of-types-and-tuple-types)
     -   [Operations performed field-wise](#operations-performed-field-wise)
     -   [Operations performed field-wise](#operations-performed-field-wise)
+    -   [Pattern matching](#pattern-matching)
 -   [Open questions](#open-questions)
 -   [Open questions](#open-questions)
+    -   [Tuple slicing](#tuple-slicing)
     -   [Slicing ranges](#slicing-ranges)
     -   [Slicing ranges](#slicing-ranges)
-    -   [Single-value tuples](#single-value-tuples)
-    -   [Function pattern match](#function-pattern-match)
-    -   [Type vs tuple of types](#type-vs-tuple-of-types)
+-   [Alternatives considered](#alternatives-considered)
+-   [References](#references)
 
 
 <!-- tocstop -->
 <!-- tocstop -->
 
 
-## TODO
-
-This is a skeletal design, added to support [the overview](README.md). It should
-not be treated as accepted by the core team; rather, it is a placeholder until
-we have more time to examine this detail. Please feel welcome to rewrite and
-update as appropriate.
-
 ## Overview
 ## Overview
 
 
-The primary composite type involves simple aggregation of other types as a tuple
-(called a "product type" in formal type theory):
+The primary composite type involves simple aggregation of other types as a
+tuple, called a "product type" in formal type theory:
 
 
 ```
 ```
 fn DoubleBoth(x: i32, y: i32) -> (i32, i32) {
 fn DoubleBoth(x: i32, y: i32) -> (i32, i32) {
@@ -49,39 +44,55 @@ and second elements are expressions referring to the `i32` type. The only
 difference is the type of these expressions. Both are tuples, but one is a tuple
 difference is the type of these expressions. Both are tuples, but one is a tuple
 of types.
 of types.
 
 
-Element access uses subscript syntax:
+## Element access
+
+Element access uses a syntax similar to field access, with an element index
+instead of a field name:
 
 
 ```
 ```
-fn Bar(x: i32, y: i32) -> i32 {
+fn Sum(x: i32, y: i32) -> i32 {
   var t: (i32, i32) = (x, y);
   var t: (i32, i32) = (x, y);
-  return t[0] + t[1];
+  return t.0 + t.1;
 }
 }
 ```
 ```
 
 
-Tuples also support multiple indices and slicing to restructure tuple elements:
+A parenthesized template constant expression can also be used to index a tuple:
 
 
 ```
 ```
-fn Baz(x: i32, y: i32, z: i32) -> (i32, i32) {
-  var t1: (i32, i32, i32) = (x, y, z);
-  var t2: (i32, i32, i32) = t1[(2, 1, 0)];
-  return t2[0 .. 2];
+fn Choose(template N:! i32) -> i32 {
+  return (1, 2, 3).(N % 3);
 }
 }
 ```
 ```
 
 
-This code first reverses the tuple, and then extracts a slice using a half-open
-range of indices.
-
 ### Empty tuples
 ### Empty tuples
 
 
 `()` is the empty tuple. This is used in other parts of the design, such as
 `()` is the empty tuple. This is used in other parts of the design, such as
-[functions](functions.md).
+[functions](functions.md), where a type with a single value is needed.
+
+### Trailing commas and single-element tuples
+
+The final element in a tuple literal may be followed by a trailing comma, such
+as `(1, 2,)`. This trailing comma is optional in tuples with two or more
+elements, and mandatory in a tuple with a single element: `(x,)` is a one-tuple,
+whereas `(x)` is a parenthesized single expression.
 
 
-### Indices as compile-time constants
+### Tuple of types and tuple types
 
 
-In the example `t1[(2, 1, 0)]`, we will likely want to restrict these indices to
-compile-time constants. Without that, run-time indexing would need to suddenly
-switch to a variant-style return type to handle heterogeneous tuples. This would
-both be surprising and complex for little or no value.
+A tuple of types can be used in contexts where a type is needed. This is made
+possible by a built-in implicit conversion: a tuple can be implicitly converted
+to type `type` if all of its elements can be converted to type `type`, and the
+result of the conversion is the corresponding tuple type.
+
+For example, `(i32, i32)` is a value of type `(type, type)`, which is not a type
+but can be implicitly converted to a type. `(i32, i32) as type` can be used to
+explicitly refer to the corresponding tuple type, which is the type of
+expressions such as `(1 as i32, 2 as i32)`. However, this is rarely necessary,
+as contexts requiring a type will implicitly convert their operand to a type:
+
+```carbon
+// OK, both (i32, i32) values are implicitly converted to `type`.
+fn F(x: (i32, i32)) -> (i32, i32);
+```
 
 
 ### Operations performed field-wise
 ### Operations performed field-wise
 
 
@@ -100,12 +111,39 @@ For binary operations, the two tuples must have the same number of components
 and the operation must be defined for the corresponding component types of the
 and the operation must be defined for the corresponding component types of the
 two tuples.
 two tuples.
 
 
-**References:** The rules for assignment, comparison, and implicit conversion
-for argument passing were decided in
-[question-for-leads issue #710](https://github.com/carbon-language/carbon-lang/issues/710).
+### Pattern matching
+
+Tuple values can be matched using a
+[tuple pattern](/docs/design/pattern_matching.md#tuple-patterns), which is
+written as a tuple of element patterns:
+
+```carbon
+let tup: (i32, i32, i32) = (1, 2, 3);
+match (tup) {
+  case (a: i32, 2, var c: i32) => {
+    c = a;
+    return c + 1;
+  }
+}
+```
 
 
 ## Open questions
 ## Open questions
 
 
+### Tuple slicing
+
+Tuples could support multiple indices and slicing to restructure tuple elements:
+
+```
+fn Baz(x: i32, y: i32, z: i32) -> (i32, i32) {
+  var t1: (i32, i32, i32) = (x, y, z);
+  var t2: (i32, i32, i32) = t1.((2, 1, 0));
+  return t2.(0 .. 2);
+}
+```
+
+This code would first reverse the tuple, and then extract a slice using a
+half-open range of indices.
+
 ### Slicing ranges
 ### Slicing ranges
 
 
 The intent of `0 .. 2` is to be syntax for forming a sequence of indices based
 The intent of `0 .. 2` is to be syntax for forming a sequence of indices based
@@ -125,28 +163,23 @@ answer here:
     -   Do we want to require the `..` to be surrounded by whitespace to
     -   Do we want to require the `..` to be surrounded by whitespace to
         minimize that collision?
         minimize that collision?
 
 
-### Single-value tuples
-
-This remains an area of active investigation. There are serious problems with
-all approaches here. Without the collapse of one-tuples to scalars we need to
-distinguish between a parenthesized single expression (`(42)`) and a one-tuple
-(in Python or Rust, `(42,)`), and if we distinguish them then we cannot model a
-function call as simply a function name followed by a tuple of arguments; one of
-`f(0)` and `f(0,)` becomes a special case. With the collapse, we either break
-genericity by forbidding `(42)[0]` from working, or it isn't clear what it means
-to access a nested tuple's first element from a parenthesized single expression:
-`((1, 2))[0]`.
-
-### Function pattern match
-
-There are some interesting corner cases we need to expand on to fully and more
-precisely talk about the exact semantic model of function calls and their
-pattern match here, especially to handle variadic patterns and forwarding of
-tuples as arguments. We are hoping for a purely type system answer here without
-needing templates to be directly involved outside the type system as happens in
-C++ variadics.
-
-### Type vs tuple of types
-
-Is `(i32, i32)` a type, a tuple of types, or is there even a difference between
-the two? Is different syntax needed for these cases?
+## Alternatives considered
+
+-   [Indexing with square brackets](/proposals/p3646.md#square-bracket-notation)
+-   [Indexing from the end of a tuple](/proposals/p3646.md#negative-indexing-from-the-end-of-the-tuple)
+-   [Restrict indexes to decimal integers](/proposals/p3646.md#decimal-indexing-restriction)
+-   [Alternatives to trailing commas](/proposals/p3646.md#trailing-commas)
+
+## References
+
+-   Proposal
+    [#2188: Pattern matching syntax and semantics](https://github.com/carbon-language/carbon-lang/pull/2188)
+-   Proposal
+    [#2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360)
+-   Proposal
+    [#3646: Tuples and tuple indexing](https://github.com/carbon-language/carbon-lang/pull/3646)
+-   Leads issue
+    [#710](https://github.com/carbon-language/carbon-lang/issues/710)
+    established rules for assignment, comparison, and implicit conversion
+-   Leads issue
+    [#2191: one-tuples and one-tuple syntax](https://github.com/carbon-language/carbon-lang/issues/2191)

+ 295 - 0
proposals/p3646.md

@@ -0,0 +1,295 @@
+# Tuples and tuple indexing
+
+<!--
+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
+-->
+
+[Pull request](https://github.com/carbon-language/carbon-lang/pull/3646)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Abstract](#abstract)
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Details](#details)
+    -   [Lexing](#lexing)
+    -   [Indexes as names](#indexes-as-names)
+    -   [Precedence](#precedence)
+    -   [Expression operand](#expression-operand)
+    -   [Bounds](#bounds)
+    -   [Tuple slicing](#tuple-slicing)
+-   [Rationale](#rationale)
+-   [Alternatives considered](#alternatives-considered)
+    -   [Alternative lexing rule](#alternative-lexing-rule)
+    -   [Decimal indexing restriction](#decimal-indexing-restriction)
+    -   [Square bracket notation](#square-bracket-notation)
+    -   [Negative indexing from the end of the tuple](#negative-indexing-from-the-end-of-the-tuple)
+    -   [Trailing commas](#trailing-commas)
+
+<!-- tocstop -->
+
+## Abstract
+
+Add support for extracting elements of a tuple by their numerical index.
+
+Also formally add the well-established basic syntactic and semantic rules for
+tuples, for which we have had leads issues but no proposal, into the design.
+
+## Problem
+
+Currently, the only way to access the elements of a tuple is through pattern
+matching. While this handles many cases well, it is sometimes desirable to
+access an element of a tuple more succinctly, especially in cases where only a
+single element's value is needed.
+
+## Background
+
+In Python, tuple indexing is performed using square brackets:
+
+```python
+tup = (1, 2, 3)
+# Prints 2.
+print(tup[1])
+```
+
+In C++, `std::pair` is indexed using `.first` and `.second`, and `std::tuple` is
+indexed using `std::get<I>`.
+
+In Rust and Swift, a tuple is indexed using `.N`, where `N` is a decimal integer
+literal.
+
+-   Rust disallows digit separators and base prefixes in `N`, but allows certain
+    literal suffixes
+    [for historical reasons](https://github.com/rust-lang/rust/issues/60210).
+-   Swift disallows digit separators and base prefixes in `N`. `swiftc` allows
+    leading `0` digits, although this appears to be an unintentional consequence
+    of `llvm::StringRef::getAsInteger` allowing them.
+
+The current Carbon documentation suggests using `tuple[i]` for tuple indexing,
+but this has not been the subject of an approved proposal.
+
+## Proposal
+
+Formally, we have not yet approved a proposal that says that Carbon has tuple
+types, although we have approved several proposals that explicitly include
+support for tuples. So, this proposal does that: tuples exist in Carbon, and are
+product types with unnamed positional elements.
+
+This proposal also updates the design to match other decisions that have been
+made in leads issues but not captured by a proposal, specifically:
+
+-   Leads issue #2191 (one-tuples and one-tuple syntax), despite being focused
+    on one-tuples, established the syntax for tuples of all arities.
+-   Leads issue #710 established rules for assignment, comparison, and implicit
+    conversion of tuples. These operations are performed elementwise, with
+    relational comparisons being performed lexicographically.
+
+Finally, the main intent of this proposal is to add support for indexing tuples,
+using the following syntaxes:
+
+-   `.` _N_, where _N_ is an integer literal, and
+-   `.` `(` _expr_ `)`, where _expr_ is a template constant of integer type.
+
+For pointers to tuples, `->` _N_ and `->` `(` _expr_ `)` are also supported.
+
+## Details
+
+### Lexing
+
+Multi-level tuple indexing will result in constructs such as
+`tuple_of_tuples.1.2`. It's important that these are lexed as two tuple indexing
+operations, not as `tuple_of_tuples` `.` `1.2`, as it would be under the current
+lexical rules, so a new rule is introduced:
+
+-   When a `.` or `->` token is followed immediately by a digit, it is lexed as
+    a `.` or `->` token followed by an integer literal, never a real literal.
+
+Note that this results in lexing being slightly contextual: the rule to lex a
+token after a `.` or `->` is different from the rule to lex a token in any other
+context. However, there is an alternative equivalent formulation of the rule
+that is not context-sensitive: that `.integer` is treated as a single lexeme
+that produces two tokens, and likewise for `->integer`.
+
+### Indexes as names
+
+The elements of a tuple are treated as if they had decimal integers as their
+names: `.0`, `.1`, and so on. It is an error to use a different spelling of that
+integer in a simple member access, because that spelling would not match the
+element name. For example, `(1, 2).0x0` is invalid, as is `large_tuple.1_2`.
+These spellings can be used as an [expression operand](#expression-operand) as
+described below: `(1, 2).(0x0)` and `large_tuple.(1_2)` are both valid.
+
+### Precedence
+
+The `.` _N_ syntax has the same precedence as postfix member access syntax, `.`
+_name_, and can be combined in the same expression: `a.0.x.1` is valid.
+
+The `.` `(` _expr_ `)` syntax is not new in this proposal, and continues to have
+the same precedence as `.` _name_.
+
+### Expression operand
+
+In the `.` `(` _expr_ `)` syntax, if the first operand is a tuple and the second
+operand is a constant of any integer type, the result is the corresponding tuple
+element, as if specified by a decimal integer literal. This rule is built into
+the language; the `.` `(` ... `)` notion is not currently overloadable.
+
+### Bounds
+
+If the tuple index is not between 0 and one less than the number of elements in
+the tuple, inclusive, the indexing is invalid.
+
+### Tuple slicing
+
+The current skeleton design suggests using `tuple[a .. b]` to slice tuples. For
+example, `tuple[0 .. 2]` could be used to extract the first two elements of a
+tuple. Tuple slicing support is not covered by this proposal, but could be added
+in the future with syntax such as `tuple.(0 .. 2)`. However, note that there is
+a risk that this syntax may lead to an incorrect theory about how Carbon works:
+namely, that `tuple.__` gives an element whereas `tuple.(__)` gives a tuple.
+
+## Rationale
+
+Goals:
+
+-   [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
+    -   The lexing rule is relatively simple to implement. Tools such as syntax
+        highlighters can treat `.i` as a distinct kind of token rather than
+        implementing any kind of context-sensitive lexing.
+-   [Software and language evolution](/docs/project/goals.md#software-and-language-evolution)
+    -   Consistent use of tuple field indexes can be used to support code that
+        adds new tuple elements over time.
+-   [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
+    -   This feature allows tuple access to be written more concisely than
+        pattern matching would allow.
+    -   Lexing `.1.2` as four tokens rather than two avoids a surprise that
+        would make chained member access hard to write.
+    -   For simple member access, requiring a decimal integer with no digit
+        separators allows the member access to be treated as an element name,
+        making the indexing easier to understand.
+-   [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code)
+    -   This feature provides a migration syntax for existing use of `.first`,
+        `.second`, and `std::get<I>`. The permission to use expressions rather
+        than only literals supports migration of `std::get<expression>`.
+
+Principles:
+
+-   [Low context sensitivity](/docs/project/principles/low_context_sensitivity.md).
+    -   We look only at the character immediately before a numeric literal to
+        determine whether it is lexed as a tuple index that stops before the
+        next `.` or as a general numeric literal.
+
+## Alternatives considered
+
+### Alternative lexing rule
+
+We could lex `.0`, `.1`, ... as a single token rather than as separate `.` and
+`0`, `1`, ... tokens. This would somewhat simplify the lexing rules, because
+they would no longer be contextual. We choose to not do this because:
+
+-   This would be inconsistent with our handling of `struct.fieldname`.
+-   Either `tuple . 0` would be invalid, unlike `struct.fieldname`, or it would
+    need to use a distinct grammar production from `tuple.0`.
+
+We could lex an integer literal when the previous token is `.`, regardless of
+whether the literal follows the `.` immediately. For example, we could treat
+
+```carbon
+let n: i32 = ((1, 2, 3), 4) . 0.1;
+```
+
+as tuple indexing, rather than as a tuple followed by a `.` and a real literal.
+This is what Swift does. We choose to not do this because:
+
+-   The `0.1` literal in this case looks like a real literal, not tuple
+    indexing, so this would likely cause surprise for readers.
+-   This would make the context-sensitive lexing be non-local. The chosen rule
+    can be interpreted as lexing `.[0-9]*` as a single lexeme, but forming two
+    tokens from it, whereas this alternative rule would be much more firmly a
+    context-sensitive lexing rule.
+
+We could get a similar result in other ways:
+
+-   We could allowing a real literal after a `.`, and split it into a pair of
+    member accesses when needed. This is
+    [what `rustc` does](https://github.com/rust-lang/rust/pull/71322).
+-   We could lex a real literal as three tokens: an integer token, a `.` token,
+    and a suffix token, and merge them back together in the parser. This is
+    [what `intellij` does](https://github.com/intellij-rust/intellij-rust/commit/f82f6cd68567e574bf1e30f5e0d263ee15d1d36e)
+    when parsing Rust.
+
+Note that these approaches are not entirely equivalent to each other. In Rust,
+for example, the difference is observable in proc macros. Also, using any kind
+of token merging or splitting approach would result in the token stream not
+matching the interpretation of the program, which is problematic for tooling.
+For example, many common Rust syntax highlighters do not properly highlight
+chained tuple indexing.
+
+### Decimal indexing restriction
+
+Carbon follows Rust and Swift in restricting tuple indexes to being decimal
+integers:
+
+```carbon
+// OK
+let a: i32 = (1, 2, 3).0;
+
+// Error, invalid index for tuple element.
+let b: i32 = (1, 2, 3).0x0;
+```
+
+This restriction introduces an inconsistency between `.0x0` and `.(0x0)`, and we
+could easily drop it. However, the restriction allows us to consider `.0`, `.1`,
+and so on to simply be the names of the tuple elements, analogous to struct
+field names, and there isn't a clear utility for permitting a base prefix or a
+digit separator in a tuple index.
+
+### Square bracket notation
+
+Instead of `tuple.0` and `tuple.(IndexConstant)`, we could use `tuple[0]` and
+`tuple[IndexConstant]`. This would result in more consistent syntax for indexing
+with a constant versus with an expression, but would make accessing an element
+of a tuple less consistent with accessing an element of a struct. We expect
+tuple access with a non-literal index to be a rare operation, so the consistency
+with that syntax seems to have lower value.
+
+Also, the use of `.` notation aims to convey the intent of the developer better:
+we intend `x[n]` notation to be used primarily for _homogenous_ indexing,
+whereas `.` notation is used for _heterogenous_ access. This also reflects the
+difference in phase: tuple indexing requires a constant index in the same way
+that struct member access requires a constant name, whereas array or container
+indexing would typically be expected to permit a runtime index.
+
+The `.N` notation can also be extended to perform member indexing into a struct
+or class, at least the latter of which would not be reasonable to support with
+`[]` notation. However, such support is not part of this proposal.
+
+Use of `[]` notation has the advantage of reducing visual ambiguity for cases
+such as `O.0`, `l.0`, and `Z.0`, which might be visually confused with `0.0`,
+`1.0`, and `2.0`, respectively. However, we're not aware of this being a problem
+in practice in Rust or Swift, which use this notation, and the same problem
+exists even without the `.0` suffix: `F(O, l, Z)` may resemble `F(0, 1, 2)`.
+
+### Negative indexing from the end of the tuple
+
+We could support `tuple.-1`, or perhaps `tuple.(-1)`, as a notation for "the
+last element of the tuple", as used for example in Python. We choose not to
+support this at this time because such notation can be confusing and has awkward
+edge cases. An off-by-one error, or an attempt to access a one-past-the-start
+element, will sometimes be accepted and silently do the wrong thing.
+
+If a future proposal introduces tuple slicing, it should revisit this question,
+because this kind of indexing from the end is often desirable when forming a
+slice. The possibility of using a different notation for this operation should
+be considered, such as `tuple.(.size - 1)`.
+
+### Trailing commas
+
+Carbon permits optional trailing commas in tuples, with mandatory trailing
+commas for one-tuples. Alternatives to this choice were considered in
+[leads issue #2191](https://github.com/carbon-language/carbon-lang/issues/2191).