Why can the same parameter pack both have labels and not have labels?

It is so weird to me that a labeled tuple both can and cannot be a parameter pack, simultaneously, depending on where in a function signature it is.

// All of this compiles.
let tuple = (1, true)
typealias Unlabeled = (Int, Bool)
_ = itself(tuple) as Unlabeled
typealias Labeled = (i: Int, b: Bool)
_ = itself(tuple, Unlabeled.self) as Labeled

// ❌ Type of expression is ambiguous without a type annotation
_ = itself(tuple, Labeled.self)
func itself<each Element>(
  _ element: (repeat each Element),
  _: (repeat each Element).Type = (repeat each Element).self
) -> (repeat each Element) {
  element
}

Can it be explained using the proposal? The key to it is not clicking for me yet, if so.

1 Like

In a function parameter, a tuple with a pack expansion (repeat each T) will only match an unlabeled tuple argument type, because packs don’t carry labels. There is no way to “abstract” over a tuple with labels - you can’t concatenate two tuples preserving labels, or produce a new tuple with the same labels but different types, etc.

However, tuples with labels can be converted to tuples without labels, and vice versa. (You can even re-order labels.) This predates parameter packs; it’s just how concrete tuple types always worked.

In the first example, the result of the function is a value of unlabeled tuple type, which is then converted into a value of labeled tuple type using as.

In the second example, the thing to know is that there is no corresponding conversion between labeled and unlabeled tuple metatypes. Since Labeled.self is a metatype and not an instance, there is no implicit conversion to discard the labels. Therefore the type of the argument doesn’t match the parameter type of the function and we diagnose an error. (However, it shouldn’t give the fallback error. Do you mind filing an issue about this?)

1 Like