20

So I was writing code to differentiate multiple versions of my app:

static var jsonURLNL =  {
    if ProcessInfo.processInfo.environment["CONSUMER"] != nil {
        return URL(string: "consumerURL")!
    }
    return URL(string: "professionalURL")!
}()

But I got a compiler error:

Unable to infer complex closure return type; add explicit type to disambiguate

Why can't the Swift compiler know that this will return a URL? I think it is fairly obvious in this case.

My goal with this question is not to give critique on Xcode or Swift, it is to increase my knowledge of how the compiler infers types in Swift.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
vrwim
  • 13,020
  • 13
  • 63
  • 118

2 Answers2

24

The return type of a closure is only inferred automatically if the closure consists of a single expression, for example:

static var jsonURLNL =  { return URL(string: "professionalURL")! }()

or if the type can be inferred from the calling context:

static var jsonURLNL: URL =  {
    if ProcessInfo.processInfo.environment["CONSUMER"] != nil {
        return URL(string: "consumerURL")!
    }
    return URL(string: "professionalURL")!
}()

or

static var jsonURLNL = {
    if ProcessInfo.processInfo.environment["CONSUMER"] != nil {
        return URL(string: "consumerURL")!
    }
    return URL(string: "professionalURL")!
}() as URL

Simplified examples: This single-expression closure compiles:

let cl1 = { return 42 }

but this multi-expression closure doesn't:

let cl2 = { print("Hello"); return 42 }
// error: unable to infer complex closure return type; add explicit type to disambiguate

The following lines compile because the type is inferred from the context:

let cl3 = { print("Hello"); return 42 } as () -> Int

let y1: Int = { print("Hello"); return 42 }()

let y2 = { print("Hello"); return 42 }() as Int

See also the quote from Jordan Rose in this mailing list discussion:

Swift's type inference is currently statement-oriented, so there's no easy way to do [multiple-statement closure] inference. This is at least partly a compilation-time concern: Swift's type system allows many more possible conversions than, say, Haskell or OCaml, so solving the types for an entire multi-statement function is not a trivial problem, possibly not a tractable problem.

and the SR-1570 bug report.

(Both links and the quote are copied from How flatMap API contract transforms Optional input to Non Optional result?).

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • But it could check all return statements in the closure and if they all specify the same return type, it would just use that type as the return type of that closure? Or am I oversimplifying? I don't really know how Swift's type inference works. – vrwim Mar 01 '17 at 15:22
  • @vrwim: I don't know the details either, but here is an example: You can overload functions with different return types. In a closure like `{ if condition { return f() } else { return g() } }` the compiler would have to figure out if there is *any* `f` and *any* `g` which have the same (or a compatible) return type. As I understand it from the above quotes, it is not trivial, and could make the compilation process slow. – Martin R Mar 01 '17 at 15:44
7

The inference of the anonymous function's return type might look easy to you, but it turns out that it would be prohibitively difficult to build it into the compiler.

As a result, it is in general not permitted to write a define-and-call initializer without specifying the type as part of the declaration.

But it's not a big problem. All you have to do is specify the type!

static var jsonURLNL : URL = ...

(What I do inside my head is treat the inclusion of the type as part of the syntax for define-and-call initializers. So I always include it. So I never encounter this error message!)


Afterthought: Consider the following:

static var jsonURLNL =  {
    if ProcessInfo.processInfo.environment["CONSUMER"] != nil {
        return "Howdy"
    }
    return URL(string: "professionalURL")!
}()

Do you see the problem? Now nothing can be inferred, even by a human being, because you've accidentally been inconsistent in your return types. But if you write : URL, now the compiler knows what you were supposed to return and knows that "Howdy" is the wrong type.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • The problem is not that it is a "define-and-call initializer". `static var jsonURLNL = { return URL(string: "professionalURL")! }()` would compile. – Martin R Mar 01 '17 at 14:54
  • @MartinR That's right, and https://bugs.swift.org/browse/SR-1570 makes it explicit that it is only when we get into a multi-line anonymous function that it becomes too hard for the compiler to deal with. But that seems like Too Much Information. As I said in my answer, I just always include the type in the declaration. My answer responds to the OP's actual use case, and describes the way I think about that use case. – matt Mar 01 '17 at 14:55
  • In other words, Swift's compiler is broken. Just build machine learning into it, simple. Easy fix! – CommaToast Sep 23 '17 at 01:26
  • 2
    @CommaToast If you think this is easy, I'm sure a job awaits you at Apple. – matt Sep 23 '17 at 01:29