Generic parameter 'T' could not be inferred after assignment

Viewed 75
// Xcode 11.6 / Swift 5

import Foundation

func f<T>(_: (T) -> Void) { }

@discardableResult
func g(_: Int) -> Int { 0 }

f { g($0) }             // compiles fine

f { let _ = g($0) }     // Generic parameter 'T' could not be inferred

In the above code, the generic function f expects as its argument a function that takes an argument of type T.

The function g takes an argument of type Int.

When I write f { g($0) }, the code compiles. I believe (please correct me if I'm wrong) this compiles because the compiler can infer that T is an Int based on g's argument type.

However, when I try to do something with the return value of g, for example in the let _ = g($0) line, the compiler complains that it can no longer infer the type of T.

It seems to me the return type of g should have no bearing on how the compiler infers T's type, but clearly it does.

Can anyone shed some light on why this happens, and what (if anything) can be done to correct it?

3 Answers

The function f takes in a function as a parameter which in turn takes a parameter of type T and returns nothing (Void). For a closure to infer types automatically, it has to consist of single (and sometimes simple) expression. Anything complex makes it difficult for the compiler to infer (which makes sense from the compiler's standpoint). Apparently, let _ = g($0) is a complex statement as far as the compiler is concerned. For further information, see this mailing list discussion

This may or may not be a compiler bug.

It is known that Swift does not try to infer the types of some closures, namely, multi-statement ones, as said in SR-1570:

This is correct behavior: Swift does not infer parameter or return types from the bodies of multi-statement closures.

However, your closure consists of only one statement, one declaration to be specific. It is possible, albeit weird, that they designed it so that Swift doesn't try to infer types if the closure contains one declaration as well. For example, this does not compile either

f { let x: Int = $0 } // nothing to do with "g"! The issue seems to be with declarations

If this were by-design, the rationale behind it might be because a single declaration in a closure doesn't make much sense anyway. Whatever is declared, won't be used.

But again, this is just speculation, and this could be a bug as well.

To fix it, simply make it a not-a-declaration:

f { _ = g($0) } // this, without the "let", is IMO the idiomatic way to ignore the function result

Or

f { g($0) } // g has @discardableResult anyway, so you don't even need the wildcard

it looks like @discardableResult gives you an ability to have 2 types of functions:

g(_: Int) -> Int and g(_: Int) -> Void (it is when you don't want to use a result of function)

I think that

f { g($0) } - here your f can infer a type because it has the same Type

(_: (T) -> Void) and (_: (Int) -> Void)

f { let _ = g($0) } - in this case the type of g function is different from f function

(_: (T) -> Void) and (_: (Int) -> Int)

If you will remove "let" it will compile again:

f { _ = g($0) }

I think that f { let _ = g($0) } - return only Int value f { _ = g($0) } - return function (_: (Int) -> Int)

Maybe it is a key here

Related