Skip to content

Commit 25c873c

Browse files
authored
Merge pull request #1125 from owenv/multiple-varargs-proposal
Add proposal: Allow Multiple Variadic Parameters in Functions, Subscripts, and Initializers
2 parents 57217f3 + 5ea0786 commit 25c873c

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Allow Multiple Variadic Parameters in Functions, Subscripts, and Initializers
2+
3+
* Proposal: [SE-NNNN](NNNN-multiple-variadic-parameters.md)
4+
* Author: [Owen Voorhees](https://github.com/owenv)
5+
* Review Manager:
6+
* Status:
7+
* Implementation: [apple/swift#29735](https://github.com/apple/swift/pull/29735)
8+
9+
## Introduction
10+
11+
Currently, variadic parameters in Swift are subject to two main restrictions:
12+
13+
- Only one variadic parameter is allowed per parameter list
14+
- If present, the parameter which follows a variadic parameter must be labeled
15+
16+
This proposal seeks to remove the first restriction while leaving the second in place, allowing a function, subscript, or initializer to have multiple variadic parameters so long as every parameter which follows a variadic one has a label.
17+
18+
Swift-evolution thread: [Lifting the 1 variadic param per function restriction](https://forums.swift.org/t/lifting-the-1-variadic-param-per-function-restriction/33787?u=owenv)
19+
20+
## Motivation
21+
22+
Variadic parameters allow programmers to write clear, succinct APIs which operate on a variable, but compile-time fixed number of inputs. One prominent example is the standard library's `print` function. However, restricting each function to a single variadic parameter can sometimes be limiting. For example, consider the following example from the `swift-driver` project:
23+
24+
```swift
25+
func assertArgs(
26+
_ args: String...,
27+
parseTo driverKind: DriverKind,
28+
leaving remainingArgs: ArraySlice<String>,
29+
file: StaticString = #file, line: UInt = #line
30+
) throws { /* Implementation Omitted */ }
31+
32+
try assertArgs("swift", "-foo", "-bar", parseTo: .interactive, leaving: ["-foo", "-bar"])
33+
```
34+
35+
Currently, the `leaving:` parameter cannot be variadic because of the preceding unnamed variadic parameter. This results in an odd inconsistency, where the first list of arguments does not require brackets, but the second does. By allowing multiple variadic parameters, it could be rewritten like so:
36+
37+
```swift
38+
func assertArgs(
39+
_ args: String...,
40+
parseTo driverKind: DriverKind,
41+
leaving remainingArgs: String...,
42+
file: StaticString = #file, line: UInt = #line
43+
) throws { /* Implementation Omitted */ }
44+
45+
try assertArgs("swift", "-foo", "-bar", parseTo: .interactive, leaving: "-foo", "-bar")
46+
```
47+
48+
This results in a cleaner, more consistent interface.
49+
50+
Multiple variadic parameters can also be used to streamline lightweight DSL-like functions. For example, one could write a simple autolayout wrapper like the following:
51+
52+
```swift
53+
extension UIView {
54+
func addSubviews(_ views: UIView..., constraints: NSLayoutConstraint...) {
55+
views.forEach {
56+
addSubview($0)
57+
$0.translatesAutoresizingMaskIntoConstraints = false
58+
}
59+
constraints.forEach { $0.isActive = true }
60+
}
61+
}
62+
63+
myView.addSubviews(v1, v2, constraints: v1.widthAnchor.constraint(equalTo: v2.widthAnchor),
64+
v1.heightAnchor.constraint(equalToConstant: 40),
65+
/* More Constraints... */)
66+
```
67+
68+
## Proposed solution
69+
70+
Lift the arbitrary restriction on variadic parameter count and allow a function/subscript/initializer to have any number of them. Leave in place the restriction which requires any parameter following a variadic one to have a label.
71+
72+
## Detailed design
73+
74+
A variadic parameter can already appear anywhere in a parameter list, so the behavior of multiple variadic parameters in functions and initializers is fully specified by the existing language rules.
75+
76+
```swift
77+
// Note the label on the second parameter is required because it follows a variadic parameter.
78+
func twoVarargs(_ a: Int..., b: Int...) { }
79+
twoVarargs(1, 2, 3, b: 4, 5, 6)
80+
81+
// Variadic parameters can be omitted because they default to [].
82+
twoVarargs(1, 2, 3)
83+
twoVarargs(b: 4, 5, 6)
84+
twoVarargs()
85+
86+
// The third parameter does not require a label because the second isn't variadic.
87+
func splitVarargs(a: Int..., b: Int, _ c: Int...) { }
88+
splitVarargs(a: 1, 2, 3, b: 4, 5, 6, 7)
89+
// a is [1, 2, 3], b is 4, c is [5, 6, 7].
90+
splitVarargs(b: 4)
91+
// a is [], b is 4, c is [].
92+
93+
// Note the third parameter doesn't need a label even though the second has a default expression. This
94+
// is consistent with the current behavior, which allows a variadic parameter followed by a labeled,
95+
// defaulted parameter, followed by an unlabeled required parameter.
96+
func varargsSplitByDefaultedParam(_ a: Int..., b: Int = 42, _ c: Int...) { }
97+
varargsSplitByDefaultedParam(1, 2, 3, b: 4, 5, 6, 7)
98+
// a is [1, 2, 3], b is 4, c is [5, 6, 7].
99+
varargsSplitByDefaultedParam(b: 4, 5, 6, 7)
100+
// a is [], b is 4, c is [5, 6, 7].
101+
varargsSplitByDefaultedParam(1, 2, 3)
102+
// a is [1, 2, 3], b is 42, c is [].
103+
// Note: it is impossible to call varargsSplitByDefaultedParam providing a value for the third parameter
104+
// without also providing a value for the second.
105+
```
106+
107+
This proposal also allows subscripts to have more than one variadic parameter. Like in functions and initializers, a subscript parameter which follows a variadic parameter must have an external label. However, the syntax differs slightly because of the existing labeling rules for subscript parameters:
108+
109+
```swift
110+
struct HasSubscript {
111+
// Not allowed because the second parameter does not have an external label.
112+
subscript(a: Int..., b: Int...) -> [Int] { a + b }
113+
114+
// Allowed
115+
subscript(a: Int..., b b: Int...) -> [Int] { a + b }
116+
}
117+
```
118+
119+
Note that due to a long-standing bug, the following subscript declarations are accepted by the current compiler:
120+
121+
```swift
122+
struct HasBadSubscripts {
123+
// Shouldn't be allowed because the second parameter follows a variadic one and has no
124+
// label. Is accepted by the current compiler but can't be called.
125+
subscript(a: Int..., b: String) -> Int { 0 }
126+
127+
// Shouldn't be allowed because the second parameter follows a variadic one and has no
128+
// label. Is accepted by the current compiler and can be called, but the second
129+
// parameter cannot be manually specified.
130+
subscript(a: Int..., b: String = "hello, world!") -> Bool { false }
131+
}
132+
```
133+
134+
This proposal makes both declarations a compile time error. This is a source compatibility break, but a very small one which only affects declarations with no practical use. This bug also affects closure parameter lists:
135+
136+
```swift
137+
// Currently allowed, but impossible to call.
138+
let closure = {(a: Int..., b: Int) in}
139+
```
140+
141+
Under this proposal, the above code also becomes a compile-time error. Note that because closures do not allow external parameter labels, they cannot support multiple variadic parameters.
142+
143+
## Source compatibility
144+
145+
As noted above, this proposal is source-breaking for any program which has a subscript declaration or closure having an unlabeled parameter following a variadic parameter. With the exception of very specific subscript declarations making use of default parameters, this only affects parameter lists which are syntactically impossible to fulfill. As a result, the break should have no impact on the vast majority of existing codebases. It does not cause any failures in the source compatibility suite.
146+
147+
If this source-breaking change is considered unacceptable, there are two alternatives. One would be to change the error to a warning when applied to subscripts and closures. The other would be to preserve the buggy behavior and emit no diagnostics. In both cases, multiple variadic parameters would continue to be supported by subscripts, but users would retain the ability to write parameter lists which can't be fulfilled in some contexts.
148+
149+
## Effect on ABI stability
150+
151+
This proposal does not require any changes to the ABI. The current ABI representation of variadic parameters already supports more than one per function/subscript/initializer.
152+
153+
## Effect on API resilience
154+
155+
An ABI-public function may not add, remove, or reorder parameters, whether or not they have default arguments or are variadic. This rule is unchanged and applies to all variadic parameters.
156+
157+
## Alternatives considered
158+
159+
Two alternative labeling rules were considered.
160+
161+
1. If a parameter list has more than one variadic parameter, every variadic parameter must have a label.
162+
2. If a parameter list has more than one variadic parameter, every variadic parameter except for the first must have a label.
163+
164+
Both alternatives are more restrictive in terms of the declarations they allow. This increases complexity and makes the parameter labeling rules harder to reason about. However, they might make it more difficult to write confusing APIs which mix variadic, defaulted, and required parameters. Overall, it seems better to trust programmers with greater flexibility, while also minimizing the number of rules they need to learn.

0 commit comments

Comments
 (0)