Tuple type inconsistency

It's also OK if they are reordered:

let u = (x: 1, y: 2)
var v = (y: 3, x: 4)
v = u
print(u) // (x: 1, y: 2)
print(v) // (y: 2, x: 1)

And same-named labels are OK too:

let o = (a: 1, a: 2) // Same label repeated is accepted.
var p = (   3, a: 4) // The first label is omitted.
p = o // This is OK, but which of o's two a-elements will go into p's a?
print(o) // (a: 1, a: 2)
print(p) // (   2, a: 1) // Hmm, OK ...

When tuples are used as parameters, the rules seem to be a slightly different:

func f(_ tuple: (x: Int, y: Int)) { print(tuple) }
func g(_ tuple: (a: Int, b: Int)) { print(tuple) }

// As expected, the following works:
f((1, 2))       // (x: 1, y: 2)
f((x: 1, y: 2)) // (x: 1, y: 2)
g((1, 2))       // (a: 1, b: 2)
g((a: 1, b: 2)) // (a: 1, b: 2)

// We can call them with the tuple elements reordered:
f((y: 2, x: 1)) // (x: 1, y: 2)

// We cannot do eg:
// f((a: 1, b: 2)) // ERROR: Cannot convert value of type '(a: Int, b: Int)' to expected argument type '(x: Int, y: Int)'

// The labels are shown as part of the function types
print(type(of: f)) // (x: Int, y: Int) -> ()
print(type(of: g)) // (a: Int, b: Int) -> ()
// (But note that the extra parens for the parameter list is missing (SR-8235).)

// They are different types:
print(type(of: f) != type(of: g)) // true

// OK

// BUT: In the following code we are using the same variable h
// to hold first f and then g, even though f and g are of
// different types:
var h = f
print(type(of: h)) // (x: Int, y: Int) -> ()
h = g
print(type(of: h)) // (x: Int, y: Int) -> ()
h((x: 1, y: 1)) // (a: 1, b: 1)

That is, as the OP noted, we cannot convert a tuple to one with different labels:

let t: (a: Int, b: Int) = (x: 1, y: 2) // ERROR: Cannot convert value of type '(x: Int, y: Int)' to specified type '(a: Int, b: Int)'

But we can cast a tuple type to one with different labels if it's in a parameter:

let i: ((a: Int, b: Int)) -> () = { (tuple: (x: Int, y: Int)) in print(tuple) }

And a somewhat related example:

func j(_ tuple: (x: Int, y: Int)) { print(tuple) }
func j(_ tuple: (a: Int, b: Int)) { print(tuple) }
// It's OK to overload j, because the two funcs have different types.
// They can only be called with correct labels (reordered or partly omitted ok)
// as expected:
j((x: 1, y: 2)) // (x: 1, y: 2)
j((a: 1, b: 2)) // (a: 1, b: 2)
j((b: 1, 2))    // (a: 2, b: 1)
// And without labels it is an ambiguous use of j as expected:
// j((1, 2)) // ERROR: Ambiguous use of `j`
// But for some reason you cannot disambiguate it by giving the type explicitly:
// let k: ((x: Int, y: Int)) -> () = j // ERROR: Ambiguous use of `j`
// let k = j as ((x: Int, y: Int)) -> () // ERROR: Ambiguous use of `j`

I agree that we need a manifesto, and as mentioned elsewhere, I also think we need a clear documentation of the currently intended design and behavior of tuple types (if there is one?), because the way things are now, it's not straight forward to tell intended behavior from bugs.

3 Likes