I'm not sure that would resolve the issue here on its own. Array itself here isn't really relevant — the issue is that Variadic<Any> is implicitly convertible to Any:
func doAThing(with values: Any...) -> Variadic<Any> {
return values
}
let variadic: Variadic<Any> = doAThing(with: 1, 2, 3)
let huh = doAThing(with: variadic /* is this converted to Any, or splatted? */)
We could decide that the semantics of variadics are special — passing in a Variadic<Any> (however this might be spelled in reality) into a function taking Any... could more strongly bind the contents of the variadic rather than passing the collection itself.
My question then becomes, what happens when you do this:
let variadic1 = doAThing(with: 1, 2, 3)
let variadic2 = doAThing(with: "foo", "bar", "baz")
let variadic3 = doAThing(with: variadic1, variadic2)
Are variadic1 and variadic2 splatted in there, or are they passed in verbatim?
-
On the one hand, we could decide that if the type matches the argument type exactly (Variadic<Any> == Variadic<Any>), we splat the contents, such that doAThing(with: variadic1) passes in the contents of the variadic, while doAThing(with: variadic1, variadic2) pass the collections in as-is
-
On the other hand, we could decide that we never splat automatically and always prefer the upcasting rules, and if you want to splat, @Nobody1707's suggestion is an explicit way to do it (i.e. you'd have to write doAThing(splat(variadic1)) to pass in its contents). We could even special case the syntax with something like *vars so you can express all of the following:
doAThing(with: variadic1, variadic2) /* no splatting */
doAThing(with: *variadic1, variadic2) /* splat the contents of variadic1, pass in variadic2 verbatim */
doAThing(with: variadic1, *variadic2) /* and vice versa */
doAThing(with: *variadic1, *variadic2) /* splat everything */
There's a sort of self-consistency to both approaches:
- The first prioritizes type-level consistency: given an exact type match, no upcasting should be necessary [at the cost of making
doAThing(with: variadic1) do something inconsistent with doAThing(with: variadic1, variadic2)]
- The second prioritizes call sites doing the same consistent thing, ignoring type matching [
doAThing(with: variadic1) behaves the same as doAThing(with: variadic1, variadic2)]
Because we don't have a way to fully express this today, the current behavior matches #1 (with Arrays being imperfect type matches):
func doAThing(with values: Any...) -> [Any] {
print(values.count)
return values
}
let a1 = doAThing(with: 1, 2, 3) // prints 3
let a2 = doAThing(with: a1) // prints 1
But there is no current precedent for what would happen here should we be able to return Any...
For what its worth, I think we can come up with a good solution either way — these are just edge cases that immediately come to mind that I think a pitch should address. 