So this topic has come up a few times in the past. We currently allow three kinds of conversions between tuple types:
- if two tuples
(x1: T1, x2: T2, ..., xn: Tn)and
(y1: U1, y2: U2, ..., yn: Un)have the same length and labels, one converts to the other if T1 converts to U1, ..., Tn converts to Un.
- a labeled tuple
(x1: T1, ..., xn: Un)converts to an unlabeled tuple
(T1, ..., Tn), and vice versa.
- a labeled tuple
(x1: T1, ..., xn: Un)converts to
(x(sigma(1)): T(sigma(1)), ..., x(sigma(n)): T(sigma(n))), where
sigmais a permutation of the label set
x1, ..., xn.
The first conversion is totally fine, but the second two are tricky to implement correctly with variadic generics, because of the behavior where a pack expansion type on one side can absorb multiple elements on the other side.
Also, the conversion that drops labels is problematic because it allows for violating the invariant where a tuple element with a pack expansion type must be followed by a labeled element; eg, '(T..., x: U...)
would convert to(T..., U...)`, which is no longer well-formed.
In my current work-in-progress implementation, the last two conversions are disabled if either side involves a pack expansion. I think we should get rid of them altogether in Swift 6 mode, even for tuples only involving scalar types.
Another oddity is that today the constraint solver distinguishes between Subtype and Conversion constraints. Conversion admits a few more conversions than Subtype, and is used in a few positions like the arguments of a call expression. A Conversion of function types though, eg
(T) -> U converting to
(T') -> U', only solves if
T is a Subtype of
U is a Subtype of
U'; that is, it's always downgraded from Conversion to Subtype when we walk into function parameter or result position. The two problematic tuple conversions are only allowed with Conversion constraints, not Subtype. The first one is allowed with Subtype. Eliminating these conversions would remove most of the distinction between the two constraint kinds, which would eventually simplify the language if we ever remove pre-Swift 6 mode.
Does anyone have compelling examples that rely on these conversions in practice, and how awkward would it be to write them out by hand instead?