swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.6k stars 10.37k forks source link

Existential unexpectedly enables splatting behavior with variadic parameter #73925

Open xwu opened 5 months ago

xwu commented 5 months ago

Description

Consider the following error:

func foo(_ x: Int...) { print("\(x.count) variadic argument(s) to 'foo'") }
func bar(_ y: Int...) { foo(y) }
// Error: cannot pass array of type 'Int...' as variadic arguments of type 'Int'

This behavior is deliberate, and there exists no splatting operator to enabling forwarding y as variadic arguments instead of a single argument—or is there?

Consider the following generic functions:

func foo2<T>(_ x: T...) { print("\(x.count) variadic argument(s) to 'foo2'") }
func bar2<T>(_ y: T...) { foo2(y) }
bar2(1, 2, 3) // prints "1 variadic argument(s) to 'foo2'"

This behavior is correct: y is passed as a single argument of type [Int] to foo2. But now consider the following functions that take existential arguments:

protocol P { }
extension Int: P { }
extension Array: P { }

func foo3(_ x: P...) { print("\(x.count) variadic argument(s) to 'foo3'") }
func bar3(_ y: P...) { foo3(y) }
bar3(1, 2, 3) // prints "3 variadic argument(s) to 'foo3'" (!!!)

By using an existential, we have unexpectedly enabled a splatting behavior that is not documented (or, afaict, intended).


Discussed in https://forums.swift.org/t/variadic-parameters-passing/72080/13

Reproduction

protocol P { }
extension Int: P { }
extension Array: P { }

func foo3(_ x: P...) { print("\(x.count) variadic argument(s) to 'foo3'") }
func bar3(_ y: P...) { foo3(y) }
bar3(1, 2, 3) // prints "3 variadic argument(s) to 'foo3'" (!!!)

Expected behavior

Prints "1 variadic argument(s) to 'foo3'" instead

Environment

Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4) Target: arm64-apple-darwin23.3.0

Additional information

No response

xwu commented 5 months ago

cc @slavapestov in case this is of interest to you :)

slavapestov commented 5 months ago

Reduced a bit:

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)

The AST for the call to foo3() doesn't have a vararg_expansion_expr/array_expr:

  (top_level_code_decl range=[/Users/slava/src/swift/var.swift:8:1 - line:8:7]
    (brace_stmt implicit range=[/Users/slava/src/swift/var.swift:8:1 - line:8:7]
      (call_expr type="()" location=/Users/slava/src/swift/var.swift:8:1 range=[/Users/slava/src/swift/var.swift:8:1 - line:8:7] nothrow isolation_crossing="none"
        (declref_expr type="(any P...) -> ()" location=/Users/slava/src/swift/var.swift:8:1 range=[/Users/slava/src/swift/var.swift:8:1 - line:8:1] 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:8:6 range=[/Users/slava/src/swift/var.swift:8:6 - line:8:6] decl="var.(file).x@/Users/slava/src/swift/var.swift:7:5" function_ref=unapplied)))))))