Skip to content

Commit bc3e3fa

Browse files
SE-0439: Allow trailing comma in comma-separated lists (#2344)
* Create nnnn-trailing-comma-tuples-arguments-conditions.md * Add parameters mention and add acknowledgments section * Move proposal file to correct location * Update proposed solution section to clarify when trailing comma is allowed for tuples and arguments/parameters * Update source compatibility section to address implications of changes in the parser * Update trailing comma proposal to more comma-separated lists * Update proposed solution section to highlight when trailing comma won't be allowed * Update and rename nnnn-trailing-comma-tuples-arguments-conditions.md to nnnn-trailing-comma-lists.md * Update trailing comma proposal text * Apply suggestions from code review Co-authored-by: Xiaodi Wu <[email protected]> * Prepare SE-0439 for review * Update SE-0439 header matter --------- Co-authored-by: Xiaodi Wu <[email protected]>
1 parent e8c9d7d commit bc3e3fa

File tree

1 file changed

+233
-0
lines changed

1 file changed

+233
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Allow trailing comma in comma-separated lists
2+
3+
- Proposal: [SE-0439](0439-trailing-comma-lists.md)
4+
- Author: [Mateus Rodrigues](https://github.com/mateusrodriguesxyz)
5+
- Review Manager: [Xiaodi Wu](https://github.com/xwu)
6+
- Status: **Active review (July 1...July 14, 2024)**
7+
- Implementation: https://github.com/swiftlang/swift/pull/74522# gated behind `-enable-experimental-feature TrailingComma`
8+
- Review: [pitch](https://forums.swift.org/t/pitch-allow-trailing-comma-in-tuples-arguments-and-if-guard-while-conditions/70170)
9+
10+
## Introduction
11+
12+
This proposal aims to allow the use of trailing commas, currently restricted to array and dictionary literals, in comma-separated lists whenever there are terminators that enable unambiguous parsing.
13+
14+
## Motivation
15+
16+
### Development Quality of Life Improvement
17+
18+
A trailing comma is an optional comma after the last item in a list of elements:
19+
20+
```swift
21+
let rank = [
22+
"Player 1",
23+
"Player 3",
24+
"Player 2",
25+
]
26+
```
27+
28+
Swift's support for trailing commas in array and dictionary literals makes it as easy to append, remove, reorder, or comment out the last element as any other element.
29+
30+
Other comma-separated lists in the language could also benefit from the flexibility enabled by trailing commas. Consider the function [`split(separator:maxSplits:omittingEmptySubsequences:)`](https://swiftpackageindex.com/apple/swift-algorithms/1.2.0/documentation/algorithms/swift/lazysequenceprotocol/split(separator:maxsplits:omittingemptysubsequences:)-4q4x8) from the [Algorithms](https://github.com/apple/swift-algorithms) package, which has a few parameters with default values.
31+
32+
33+
```swift
34+
let numbers = [1, 2, 0, 3, 4, 0, 0, 5]
35+
36+
let subsequences = numbers.split(
37+
separator: 0,
38+
// maxSplits: 1
39+
) Unexpected ',' separator
40+
```
41+
42+
### The Language Evolved
43+
44+
Back in 2016, a similar [proposal](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0084-trailing-commas.md) with a narrower scope was reviewed and rejected for Swift 3. Since that time, the language has evolved substantially that challenges the basis for rejection. The code style that "puts the terminating right parenthesis on a line following the arguments to that call" has been widely adopted by community, Swift standard library codebase, swift-format, docc documentation and Xcode. Therefore, not encouraging or endorsing this code style doesn't hold true anymore.
45+
46+
The language has also seen the introduction of [parameter packs](https://github.com/apple/swift-evolution/blob/main/proposals/0393-parameter-packs.md), which enables APIs that are generic over variable numbers of type parameters, and code generation tools like plugins and macros that, with trailing comma support, wouldn't have to worry about a special condition for the last element when generating comma-separated lists.
47+
48+
## Proposed solution
49+
50+
This proposal adds support for trailing commas in comma-separated lists when there's a clear terminator, which are the following:
51+
52+
- Tuples and tuple patterns.
53+
54+
```swift
55+
(1, 2,)
56+
let block: (Int, Int,) -> Void = { (a, b,) in }
57+
let (a, b,) = (1, 2,)
58+
for (a, b,) in zip(s1, s2) { }
59+
```
60+
61+
- Parameter and argument lists of initializers, functions, enum associated values, expression macros, attributes, and availability specs.
62+
63+
```swift
64+
65+
func foo(a: Int, b: Int,) { }
66+
67+
foo(a: 1, b: 1,)
68+
69+
struct S {
70+
init(a: Int, b: Int,) { }
71+
}
72+
73+
enum E {
74+
case foo(a: Int, b: Int,)
75+
}
76+
77+
@Foo(1, 2, 3,)
78+
struct S { }
79+
80+
f(_: @foo(1, 2,) Int)
81+
82+
#foo(1, 2,)
83+
84+
struct S {
85+
#foo(1, 2,)
86+
}
87+
88+
if #unavailable(iOS 15, watchOS 9,) { }
89+
90+
```
91+
- Subscripts, including key path subscripts.
92+
93+
```swift
94+
let value = m[x, y,]
95+
96+
let keyPath = \Foo.bar[x,y,]
97+
98+
f(\.[x,y,])
99+
```
100+
101+
- `if`, `guard` and `while` condition lists.
102+
103+
```swift
104+
if a, b, { }
105+
while a, b, { }
106+
guard a, b, else { }
107+
```
108+
109+
- `switch` case labels.
110+
111+
```swift
112+
switch number {
113+
case 1, 2,:
114+
...
115+
default:
116+
..
117+
}
118+
```
119+
120+
- Closure capture lists.
121+
122+
```swift
123+
{ [a, b,] in }
124+
```
125+
126+
- Inheritance clauses.
127+
128+
```swift
129+
struct S: P1, P2, P3, { }
130+
```
131+
132+
- Generic parameters.
133+
134+
```swift
135+
struct S<T1, T2, T3,> { }
136+
```
137+
138+
- Generic `where` clauses.
139+
140+
```swift
141+
struct S<T1, T2, T3> where T1: P1, T2: P2, { }
142+
```
143+
144+
- String interpolation
145+
146+
```swift
147+
let s = "\(1, 2,)"
148+
```
149+
150+
## Detailed Design
151+
152+
Trailing commas will be supported in comma-separated lists whenever there is a terminator clear enough that the parser can determine the end of the list. The terminator can be the symbols like `)`, `]`, `>`, `{` and `:`, a keyword like `where` or a pattern code like the body of a `if`, `guard` and `while` statement.
153+
154+
Note that the requirement for a terminator means that the following cases will not support trailing comma:
155+
156+
Enum case label lists:
157+
158+
```swift
159+
enum E {
160+
case a, b, c, // ❌ Expected identifier after comma in enum 'case' declaration
161+
}
162+
```
163+
164+
Inheritance clauses for associated types in a protocol declaration:
165+
166+
```swift
167+
protocol Foo {
168+
associatedtype T: P1, P2, // ❌ Expected type
169+
}
170+
```
171+
172+
Generic `where` clauses for initializers and functions in a protocol declaration:
173+
174+
```swift
175+
protocol Foo {
176+
func f<T1, T2>(a: T1, b: T2) where T1: P1, T2: P2, // ❌ Expected type
177+
}
178+
```
179+
180+
Trailing commas will be allowed in single-element lists but not in zero-element lists, since the trailing comma is actually attached to the last element. Supporting a zero-element list would require supporting _leading_ commas, which isn't what this proposal is about.
181+
182+
```swift
183+
(1,) // OK
184+
(,) // ❌ expected value in tuple
185+
```
186+
187+
188+
## Source compatibility
189+
190+
Although this change won't impact existing valid code it will change how some invalid code is parsed. Consider the following:
191+
192+
```swift
193+
if
194+
condition1,
195+
condition2,
196+
{ // ❌ Function produces expected type 'Bool'; did you mean to call it with '()'?
197+
return true
198+
}
199+
200+
{ print("something") }
201+
```
202+
203+
Currently the parser uses the last comma to determine that whatever follows is the last condition, so `{ return true }` is a condition and `{ print("something") }` is the `if` body.
204+
205+
With trailing comma support, the parser will terminate the condition list before the first block that is a valid `if` body, so `{ return true }` will be parsed as the `if` body and `{ print("something") }` will be parsed as an unused closure expression.
206+
207+
```swift
208+
if
209+
condition1,
210+
condition2,
211+
{
212+
return true
213+
}
214+
215+
{ print("something") } // ❌ Closure expression is unused
216+
```
217+
218+
## Alternatives considered
219+
220+
### Eliding commas
221+
222+
A different approach to address similar motivations is to allow the comma between two expressions to be elided when they are separated by a newline.
223+
224+
```swift
225+
print(
226+
"red"
227+
"green"
228+
"blue"
229+
)
230+
```
231+
This was even [proposed](https://forums.swift.org/t/se-0257-eliding-commas-from-multiline-expression-lists/22889/188) and returned for revision back in 2019.
232+
233+
The two approaches are not mutually exclusive. There remain unresolved questions about how the language can accommodate elided commas, and adopting this proposal does not prevent that approach from being considered in the future.

0 commit comments

Comments
 (0)