Too complex closure return type?

why is it too complex for the compiler to determine the closure result here?

_ = { // error: Unable to infer complex closure return type; add explicit type to disambiguate
    _ = "" // or anything else here
    return ""
}

Swift only infers a closure's type if it is a single expression (without more context like Array.filter)

3 Likes

Filed [SR-14824] Improve diagnostic for multi-statement closures instead of saying "too complex closure return type" · Issue #57172 · apple/swift · GitHub to improve the diagnostic.

1 Like

i see. in my code it's something like:

{
    bar($0)
}

which suddenly getting transformed into a much messier version should i merely add a print statement:

{ p -> SomeGeneric<Type> in
    print(p)
    return bar(p)
}

and obviously compiler has all the knowledge about return type: if i get the explicit type annotation wrong - compiler will complain, so instead of requiring it and complaining about a type mismatch it could have silently figured out what's needed. i guess i just have to live with this as this is a feature, not a bug.

lifehack version:

{
    (
        print($0),
        return: bar($0)
    ).return
}

or a more cryptic:

{
    (
        print($0),
        bar($0)
    ).1
}
1 Like

The version with the label is a lot safer, as you don't need to remember to change the index if you add more expressions to the tuple. (Although I'm not a fan of either of these.)

Another alternative is to make the call site use infer the type. Array.filter is a good example of this approach:

extension Array {
  public func filter(
    _ predicate (Element) throws -> Bool
  ) -> [Element] {
    // implementation doesn't matter
}

Then at the call site, we can have closures with more statements

let xs = [1,2,3,4,5,6,7,8]
  .filter { number in
    logAnaltyics(for: number)
    logPerformance(for: number)
    print(primeFactors(number))
    return number.isMultiple(of: 3)
}
// xs == [3, 6]

We don't need to add any inference here.

would be great, although i can't figure out how. that's the over-simplified version of the actual code where i encountered the issue:

protocol Proto {}

func foo() -> Generic<Proto> {
    fatalError()
}

struct Generic<T> {
    var t: T
    func bar<R>(execute: (T) -> Generic<R>) {
    }
}

func baz() {
    Generic(t: true).bar { p in // p -> Generic<Proto> in
        print(p)
        return foo()
    }
}

The tough part with Generic<T>.bar<R> is that you introduce the generic parameter R in the return value of a callback (execute). However, it is not obvious where this returned value is used. More specifically, where do you use foo() in baz? Unless your closure is escaping, return foo() looks like it will never be used without more knowledge of Generic uses.

yep, that's a stripped down snippet obviously. "execute" is used within "bar", and the closure is indeed escaping in the actual code.

i agree on both counts. would be quite awkward to actually use this workaround in real code.