Am I holding parameter packs wrong? Type checker unable to infer generic parameter pack

I was working with result builders when I noticed that the following code fails to compile:

struct List<each Value> { }

func buildEither<each TrueValue, each FalseValue>(first: repeat each TrueValue) -> List<repeat each TrueValue, repeat each FalseValue> {
    .init()
}

func buildEither<each TrueValue, each FalseValue>(second: repeat each FalseValue) -> List<repeat each TrueValue, repeat each FalseValue> {
    .init()
}

let foo = true ? buildEither(first: "") : buildEither(second: 1)
Result values in '? :' expression have mismatching types 'List<repeat each TrueValue, Int>' and 'List<String, repeat each FalseValue>'

Result values in '? :' expression have mismatching types 'List<String, repeat each FalseValue>' and 'List<String, repeat each FalseValue>'

Generic parameter 'each FalseValue' could not be inferred

Generic parameter 'each TrueValue' could not be inferred

This is a 'lil bit of a tricky scenario — Swift must infer the functions' return type based the parameters passed to them and the context they're used in. This is similar to what SwiftUI does with ConditionalView.

The non-parameter pack version works fine:

struct List<A, B> { }

func buildEither<TrueValue, FalseValue>(first: TrueValue) -> List<TrueValue, FalseValue> {
    .init()
}

func buildEither<TrueValue, FalseValue>(second: FalseValue) -> List<TrueValue, FalseValue> {
    .init()
}

let foo = true ? buildEither(first: "") : buildEither(second: 1)

(As far as I can tell, the specific issue Swift is running into is inference and comparison around the new concatenated parameter pack.)

I'm new to parameter packs so:

  1. Shouldn't this work? Am I holding this wrong?
  2. Is there a workaround that accomplishes the same thing?
2 Likes

Parameter packs are sadly mostly just kinda broken.

struct List<each Value> { }

func buildEither<each TrueValue, each FalseValue>(
  first: repeat each TrueValue,
  falseType: repeat (each FalseValue).Type
) -> List<repeat each TrueValue, repeat each FalseValue> {
  .init()
}

func buildEither<each TrueValue, each FalseValue>(
  second: repeat each FalseValue,
  trueType: repeat (each TrueValue).Type
)
-> List<repeat each TrueValue, repeat each FalseValue> {
  .init()
}
// All of this compiles.
let first = buildEither(first: "", falseType: Int.self)
let second = buildEither(second: 1, trueType: String.self)
_ = true ? first : second

// Cannot convert value of type 'List<String, Int>' to type 'List<String, Int>' in coercion
buildEither(first: "", falseType: Int.self) as List<String, Int>

_ = true ? first
  // Result values in '? :' expression have mismatching types 'List<String, Int>' and 'List<String, Int>'
  : buildEither(second: 1, trueType: String.self)
3 Likes

It's good to know that this isn't just me. I just wrote a bunch of code that uses parameter packs pretty heavily, and that works, but it does seem to kind of... fall apart in this instance.

1 Like

I think this might represent the minimal reduction of the problem you're encountering.

struct Packer<each T> { }

func f<each T>(_: repeat (each T).Type)
-> Packer<repeat each T, repeat each T>.Type {
  Packer.self
}
typealias PackerType = Packer<Never, Never>.Type
let packerType = f(Never.self) // Compiles
_ = packerType as PackerType // Compiles.
_ = f(Never.self) as PackerType // Type of expression is ambiguous without a type annotation
3 Likes

For what it's worth, this problem doesn't seem to happen with tuples:

func f<each T>(_: repeat (each T).Type)
-> (repeat each T, repeat each T).Type {
    fatalError()
}

typealias PackerType = (Never, Never).Type
let packerType = f(Never.self) // Compiles.
_ = packerType as PackerType // Compiles.
_ = f(Never.self) as PackerType // Compiles.

This at least gives me a workaround — I can traffic around tuples in places where this is an issue and use functions to turn them into the structs I need.

1 Like