[Breaking change for Swift 6] Infer return type `Never` when every exit path throws

Seems strange. My mental model for never is it never returns. Void seems better here.

Isn’t that because a bare return statement or exiting the function scope without a return actually does return an empty tuple (AKA Void)?

1 Like

As far as I can tell, a function that exits every of its paths with a throw has no implicit return.

There are two options from where I see the return type can be provided from:

  • explicit but unreachable return
  • explicit type for the whole closure which cancels the whole type inference

None of there two options are present in the edge case we're discussing.


My whole point is, that the current inference seems to provide a wrong return type in this case. It does feel like a bug. However it would be a bug that probably would need to go through evolution, hence this thread.

If the core team joins the conversation and tells that there is nothing we can / should do about it, so be it.

4 Likes

Today, return type inference for multi-statement closures does not look at code in the closure body at all. If the closure does not have a type annotation or other type context (e.g. the closure is an argument in a function call where the parameter type provides context), the return type is simply defaulted to Void.

4 Likes

I’m not sure I understand. You’re saying that a closure that returns something else (say an Int for example) is treated as if its return type were Void if it doesn’t have any type annotations?

I tried this in Playgrounds and it wouldn’t compile, complaining that it could not determine the type and wanted an explicit annotation. I don’t have access to XCode at the moment to see what it says, but how would one create the closure with no annotations and an inferred return type that @hborla mentions?

let closure = {
    let value = 42
    return value
} //Error: Unable to infer complex closure return type; 
  //       add explicit type to disambiguate

Here’s an example:


let closure = {
    let a = 1
    print(a)
}

closure()

There’s no type annotation, so type inference defaults the return type to Void. It doesn’t compile if you return a different type of value, because without type context, the only return type inference supported is the default Void type. Like I mentioned upthread, return type inference from statements in a multi-statement closure body is a work in progress.

7 Likes

Okay, I think I understand now. The only instance of a closure that has a different return type than it’s actual return type should be what’s actually the subject of this Pitch: A multiline closure where all paths never return.

I see that type(of:) returns () -> Never for this closure

{
  fatalError()
}

yet it returns () -> () AKA () -> Void when you add an irrelevant line such as in this closure

{
  let _ = 0
  fatalError()
}

so I think this illustrates both what you’re explaining to me and the issue that precipitated this Pitch.

This is because of Swift’s implicit returns from single-expression closures.

{
    fatalError()
}

turns into

{
    return fatalError()
}

so it assumes a return type of () -> Never because of fatalError()’s return type of Never.

On the other hand,

{
    let _ = 0
    fatalError()
}

isn’t a single-expression closure, so the compiler assumes the default return type of () -> () (a.k.a. () -> Void).

1 Like

Even if Never becomes the bottom type, this still would not compile.

let integer = instantiateAndMakeInt(from: Never.self)

This line uses the metatype Never.Type (Never.self) which is not a bottom type. Secondly, making Never the bottom type does not mean that it implicitly conforms to all protocols. These points are spelled out in the original pitch:

I see — I was under the impression that Never as a bottom type meant that it would conform to all protocols.

(Side note: the metatype isn’t actually used in the function’s code — just in its parameters. The metatype is used by the function to specify the I type parameter. Since I would resolve to Never and the function instantiates I, it would be instantiating a value to type Never — at least, if Never conformed to InstantiableAndConvertableToInt. This pattern is used elsewhere in Swift (like here and here).)

I do think Never as the bottom type would actually be helpful (though I think it should be applied to all uninhabited types if possible).

I disagree. Owen addressed this as well, and I agree with his argument: