- Proposal: SE-0439
- Author: Mateus Rodrigues
- Review Manager: Xiaodi Wu
- Status: Implemented (Swift 6.1)
- Implementation: swiftlang/swift#74522
- Previous Proposal: SE-0084
- Review: (pitch), (review), (acceptance)
- Previous Revision: (1)
This proposal aims to allow the use of trailing commas, currently restricted to array and dictionary literals, in symmetrically delimited comma-separated lists.
A trailing comma is an optional comma after the last item in a list of elements:
let rank = [
"Player 1",
"Player 3",
"Player 2",
]
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.
Other comma-separated lists in the language could also benefit from the flexibility enabled by trailing commas. Consider the function split(separator:maxSplits:omittingEmptySubsequences:)
from the Algorithms package, which has a few parameters with default values.
let numbers = [1, 2, 0, 3, 4, 0, 0, 5]
let subsequences = numbers.split(
separator: 0,
// maxSplits: 1
) ❌ Unexpected ',' separator
Back in 2016, a similar proposal 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.
The language has also seen the introduction of parameter packs, 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.
This proposal adds support for trailing commas in symmetrically delimited comma-separated lists, which are the following:
-
Tuples and tuple patterns.
let velocity = ( 1.66007664274403694e-03, 7.69901118419740425e-03, 6.90460016972063023e-05, ) let ( velocityX, velocityY, velocityZ, ) = velocity
-
Parameter and argument lists of initializers, functions, enum associated values, expression macros and attributes.
func foo( input1: Int = 0, input2: Int = 0, ) { } foo( input1: 1, input2: 1, ) struct S { init( input1: Int = 0, input2: Int = 0, ) { } } enum E { case foo( input1: Int = 0, input2: Int = 0, ) } @Foo( "input 1", "input 2", "input 3", ) struct S { } #foo( "input 1", "input 2", "input 3", ) struct S { #foo( "input 1", "input 2", "input 3", ) }
-
Subscripts, including key path subscripts.
let value = m[ x, y, ] let keyPath = \Foo.bar[ x, y, ] f(\.[ x, y, ])
-
Closure capture lists.
{ [ capturedValue1, capturedValue2, ] in }
-
Generic parameters.
struct S< T1, T2, T3, > { }
-
String interpolation.
let s = "\(1, 2,)"
Trailing commas will be supported in comma-separated lists when symmetric delimiters (including (...)
, [...]
, and <...>
) enable unambiguous parsing.
Note that the requirement for symmetric delimiters means that the following cases will not support trailing comma:
-
if
,guard
andwhile
condition lists.if condition1, condition2, ❌ { } while condition1, condition2, ❌ { } guard condition1, condition2, ❌ else { }
-
Enum case label lists.
enum E { case a, b, c, ❌ }
-
switch
case labels.switch number { case 1, 2, ❌ : ... default: .. }
-
Inheritance clauses.
struct S: P1, P2, P3, ❌ { }
-
Generic
where
clauses.struct S< T1, T2, T3, > where T1: P1, T2: P2, ❌ { }
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.
(1,) // OK
(,) ❌ expected value in tuple
This is a purely additive change with no source compatibility impact.
Comma-separated lists that are not symmetrically delimited could also benefit from trailing comma support; for example, condition lists, in which reordering is fairly common. However, these lists currently rely on the comma after the penultimate element to determine that what comes next is the last element, and some of them present challenges if relying on opening/closing delimiters instead.
At first sight, {
may seem a reasonable closing delimiter for if
and while
condition lists, but conditions can have a {
themselves.
if
condition1,
condition2,
{ true }(),
{ }
This particular case can be handled but, given how complex conditions can be, it's hard to conclude that there's absolutely no corner case where ambiguity can arise in currently valid code.
Inheritance lists and generic where
clauses can appear in protocol definitions where there's no clear delimiter, making it harder to disambiguate where the list ends.
protocol Foo {
associatedtype T:
P1,
P2, ❌ Expected type
...
}
Although some comma-separated lists without symmetric delimiters may have a clear terminator in some cases, this proposal restricts trailing comma support to symmetrically delimited ones where it's clear that the presence of a trailing comma will not cause parsing ambiguity.
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.
print(
"red"
"green"
"blue"
)
This was even proposed and returned for revision back in 2019.
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.
- Update to address acceptance decision of restricting trailing comma to lists with symmetric delimiters.
Thanks to Alex Hoppen, Xiaodi Wu and others for their help on the proposal text and implementation.