tera
1
The following looks a bug, is it not?
func foo(_ v: Int...) {}
func bar(_ v: Int...) {
foo(v) // 🛑 Cannot pass array of type 'Int...' as variadic arguments of type 'Int'
}
A non-ideal workaround:
switch v.count {
case 0: foo()
case 1: foo(v[0])
case 2: foo(v[0], v[1])
case 3: foo(v[0], v[1], v[2])
case 4: foo(v[0], v[1], v[2], v[3])
default: fatalError("TODO implement \(v.count) case")
}
or:
func foo(_ v: [Int]) {
// move the functionality here
}
func foo(_ v: Int...) {
foo(v)
}
although this leads to a potentially unwanted ability to call "foo" with normal array parameters:
foo([1, 2, 3]) // 🤔
2 Likes
vns
2
Either this is a bug or not, I’d say this seems to be a desirable behavior to have. For instance, in Go you can pass it as foo(v...), which is highly convenient, and resolves disambiguity of array passing.
2 Likes
It's not a bug, it's a design flaw that we haven't been able to fix because no one can agree on a syntax for splatting an array into a variadic parameter list.
3 Likes
Given this, it sounds like the syntax the core team wants for splatting is:
func foo<T>(_ arg: T...) {
print(repeat each arg)
}
Which isn't that bad, and I can't think of any reason it won't work. It's basically just an overload of repeat each that takes an array instead of a pack parameter.
The implementation in the pitch has probably bit-rotted though.
2 Likes
tera
6
I think it could have worked without introducing any special syntax:
func foo(_ v: Int...) { }
func bar(a: [Int], b: Int...) {
foo(b) // ✅
foo(a) // 🛑
// even though:
let c: [Int] = b // ✅
}
i.e. the type "Int..." carries some internal marker that helps to distinguish it from normal [Int].
Then you would need special syntax to not splat the array.
tera
8
Array (or tuple) splatting while look related, is a different feature that could be considered separately. You don't need array splatting in the example above to pass "Int..." function parameter as an argument to a function that requires "Int..." parameter, and whilst "Int..." function parameter is treated as an array inside the function there could be some internal difference to treat it differently to a normal array (to not allow normal [Int] array passed to "Int..." function parameter).
vns
9
It is when you go beyond simple case with Int… it becomes an issue. Say, we want a variadic parameter to be a protocol that an Array can confirm too. Now there is no way to know either we want to pass an array as a single argument, or expand to be a multiple arguments, and that’s why syntax for this matters. Without that feature would be incomplete and narrowly applicable.
1 Like
tera
10
I believe this is an anti-feature and should not be allowed...
I don't see any issue in your example:
protocol P {}
extension Array: P {}
extension Int: P {}
func foo(_ v: P...) {}
func bar() {
foo(1, 2, 3) // ✅ (three parameters passed)
foo([1, 2, 3]) // ✅ (one parameter passed)
foo([1], [2], [3]) // ✅ (three parameters passed)
}
1 Like
vns
11
Assume that these variadic parameters are collected into an array to later be passed to the function, like building a format string. You now limited in this application of an array to collect arguments.
BTW, oddly that this example will compile right now and print accordingly results:
protocol P {}
extension Int: P {}
extension Array: P {}
func foo(_ v: P...) {
print(v)
}
func bar(_ v: P...) {
foo(v)
}
bar(1, 2, 3) // will output [1, 2, 3]
bar([1, 2, 3]) // will output [[1, 2, 3]]
bar([1], [2], [3]) // will output [[1], [2], [3]]
While when you are passing concrete Int... it won't.
And with generics it compiles too, yet output differs:
func foo<T>(_ v: T...) {
print(v)
}
func bar<T>(_ v: T...) {
foo(v)
}
bar(1, 2, 3) // will output [[1, 2, 3]]
bar([1, 2, 3]) // will output [[[1, 2, 3]]]
bar([1], [2], [3]) // will output [[[1], [2], [3]]]
1 Like
tera
12
Indeed. There are typos in your example but after I fixed those, namely:
extension Int: P {}
extension Array: P {}
it works with P..., and works the way I would expect it to work.
Which is exactly what I suggest to treat "a bug" and fix so it works like it now works with "P..." above 
2 Likes
vns
13
Already corrected there, thanks for pointing
Got confused myself while trying several options.
Yep, now it is really seems like something does not right in here...
1 Like
xwu
(Xiaodi Wu)
14
4 Likes
vns
15
Hope it won't break anything, since the idea to check protocol and generic came from similar use in a small util I've written around Swift 3 and remembered that the initial case has worked there, so this behaviour has been around for a while I guess.
tera
16
From my POV the current behaviour of "Existential..." is expected, logical and actually useful!
And the current behaviour of "generic..." is at least questionable. Consider that instead of:
func foo<T>(_ v: T...) {
print(v)
}
func bar<T>(_ v: T...) {
foo(v)
}
I use a seemingly equivalent:
func foo<T>(_ v: T) {
print(v)
}
func bar<T>(_ v: T) {
foo(v)
}
func foo<T>(_ v1: T, _ v2: T) {
print(v1, v2)
}
func bar<T>(_ v1: T, _ v2: T) {
foo(v1, v2)
}
func foo<T>(_ v1: T, _ v2: T, _ v3: T) {
print(v1, v2, v3)
}
func bar<T>(_ v1: T, _ v2: T, _ v3: T) {
foo(v1, v2, v3)
}
// and so on up to N parameters
The result should be the same, right? No... In this case it will be different:
1 2 3
[1, 2, 3]
[1] [2] [3]
1 Like
bbrk24
17
But this is not equivalent. The direct equivalent to
func bar<T>(_ v: T...) {
// body
}
for exactly two parameters is
func bar<T>(_ v0: T, _ v1: T) {
let v = [v0, v1]
// body
}
tera
18
I suggest we have a difference between [v0, v1] and _ v: T...
e.g.:
func foo(_ variadic: Int...) {
var variadic = variadic // just to make it var
var array: [Int] = variadic // ✅
variadic = array // 🛑 Error
}
as if Int... (variadic) acts as a "subclass" of [Int] (array).
It already kind of is:
foo([1, 2, 3]) // 🛑
1 Like
tera
19
Note if that's considered a bug it would be a breaking change to fix it:
func test(_ arguments: Any..., execute: (Any...) -> Void) { // ✅ ok on all godbolt Swift versions
execute(arguments)
}
2 Likes
It's working as expected because bar2() calls foo2() with T := Array<T>. This makes it clear:
func foo2<T>(_ x: T...) { print("\(T.self), \(x)") }
func bar2<T>(_ y: T...) { foo2(y) }
bar2(1, 2, 3)
If we desugar the variadic parameters by hand:
func foo2<T>(_ x: [T]) { print("\(T.self), \(x)") }
func bar2<T>(_ y: [T]) { foo2([y]) }
bar2([1, 2, 3])
EDIT: Ok, I misunderstood your original bug report because you started with two examples that work as expected. Yes, it's a bug that the argument value is not wrapped inside of an array here:
protocol P { }
extension Int: P { }
extension Array: P { }
func foo3(_ x: P...) { print("\(x.count) variadic argument(s) to 'foo3'") }
let x: [P] = [1, 2, 3]
foo3(x)
(func_decl range=[/Users/slava/src/swift/var.swift:6:1 - line:6:31] "bar3(_:)" interface type="([any P]) -> ()" access=internal
(parameter_list range=[/Users/slava/src/swift/var.swift:6:10 - line:6:19]
(parameter "y" interface type="[any P]"))
(brace_stmt range=[/Users/slava/src/swift/var.swift:6:21 - line:6:31]
(call_expr type="()" location=/Users/slava/src/swift/var.swift:6:23 range=[/Users/slava/src/swift/var.swift:6:23 - line:6:29] nothrow isolation_crossing="none"
(declref_expr type="(any P...) -> ()" location=/Users/slava/src/swift/var.swift:6:23 range=[/Users/slava/src/swift/var.swift:6:23 - line:6:23] decl="var.(file).foo3@/Users/slava/src/swift/var.swift:5:6" function_ref=single)
(argument_list
(argument
(declref_expr type="[any P]" location=/Users/slava/src/swift/var.swift:6:28 range=[/Users/slava/src/swift/var.swift:6:28 - line:6:28] decl="var.(file).bar3(_:).y@/Users/slava/src/swift/var.swift:6:13" function_ref=unapplied))))))
@xedin You might want to take a look.
2 Likes