Swift accepts inference from multi-statement closure bodies
SE-0326 lets information from a closure body help determine its parameter and result types. Its incremental rules remove a surprising statement-count distinction while retaining limits on inference order and overload resolution.
Adding a logging statement to a closure should not make its result type unknowable. Swift currently treats single-expression and multi-statement closures differently during type checking, so an otherwise harmless edit can expose exactly that kind of distinction. SE-0326 was accepted on November 15 to let a multi-statement closure contribute parameter and result information from its body.
The accepted change concerns information flow in the type checker. It does not make an arbitrary collection of statements into one giant expression. The design retains incremental processing and gives earlier statements a defined role in determining types used later. That is important because reducing annotation requirements has to remain compatible with tractable compilation.
I appreciate Swift's type system, but build times remain a frustrating part of using it. This proposal interests me because it addresses both a language inconsistency and the implementation problem that prevented a simple extension of existing expression inference. The useful result is a more predictable closure model with stated limits.
The old distinction comes from when bodies are checked
Pavel Yaskevich's accepted proposal explains that a single-expression closure participates in checking the surrounding expression. Its body can therefore help establish the type of the call receiving it. A multi-statement body is checked separately after the enclosing call has been resolved.
That ordering restricts the information available when a generic function's result depends on its callback. The surrounding context can tell the closure what types it expects. Information discovered only inside the body cannot then help select the surrounding call in the same way. A programmer can see an obvious return expression while the checker still asks for an explicit signature.
The proposal makes the body participate in determining the enclosing expression without abandoning statement-by-statement analysis. Context remains the primary source of parameter and result information. When context leaves a type unresolved, the body can supply information under the new rules. Single-expression closure checking remains unchanged.
As of acceptance, the proposal directs experimenters to trunk development snapshots with -Xfrontend -experimental-multi-statement-closures. Acceptance therefore establishes the language direction and an experimental route to evaluating it. It does not mean every installed stable Swift compiler now enables the behavior.
The first return can establish the result type
The accepted text uses a collection transformation whose first return expression has an unsigned integer type. A later return of the literal zero can then use that established type. The following smaller example illustrates the same rule.
struct Parcel {
let weight: UInt
}
func adjustedWeights(_ parcels: [Parcel], minimum: UInt) -> [UInt] {
let result = parcels.map {
if $0.weight > minimum {
return $0.weight
}
return 0
}
return result
}
The array determines the closure parameter's type. Inside the body, the first return establishes UInt for the result. That information reaches the later literal and the surrounding map expression. The intermediate local no longer needs a separate annotation simply because the transformation uses a branch.
Order remains significant. The proposal does not compute an unrestricted common type by joining every return expression. When the first return cannot supply enough information, a later return does not necessarily repair the omission. An early nil return in a generic optional-producing closure is the proposal's example of a case that can still need an explicit result type.
This is a practical limit to document. A reader should not infer from broader body inference that all annotations are obsolete. Context and explicit signatures remain useful where a body intentionally leaves the type underdetermined. The accepted behavior reduces a particular source of missing information without promising whole-body deduction in every direction.
Statements do not send arbitrary information backward
The design processes the body from the first statement to the last. Without contextual information, the first reference to an anonymous parameter determines its type. A later statement that demands a different type produces an error. This follows the general pattern of a declaration acquiring a type that later uses must respect.
Closures without explicit return statements default to a Void result. Adding several expression statements does not implicitly return the last expression as a value. That rule distinguishes inference improvements from a change in return syntax. A programmer who intends to produce a value from a multi-statement body still needs the appropriate return statements.
The proposal also leaves inference of inout from a multi-statement body out of scope. An explicit parameter declaration or contextual function type must supply that behavior. The document identifies broader unification as future work with its own compatibility questions. Keeping it separate allows this proposal to improve ordinary parameter and result inference without settling every closure inconsistency at once.
Better inference can expose ambiguous overloads
More information does not always select a unique overload. The compatibility section illustrates two overloads accepting different pointer callback types. Under the old rules, ignoring a multi-statement body can leave one generic candidate without enough information, making another candidate win. Letting the body participate can make both candidates viable.
The resulting ambiguity is a source compatibility concern even though the change improves consistency. A previously accepted call may need an explicit parameter type to say which overload was intended. This is a consequence of changing which solutions the checker can find, rather than a runtime change to pointer behavior.
Ben Cohen's acceptance message acknowledges the possibility of required source edits. The Core Team reports finding one instance in a very large source corpus and considers the benefit sufficient without waiting for a new language mode. That observation describes the reported corpus, not a universal guarantee about the frequency of affected programs.
I regard explicit types as useful at genuine overload boundaries. The undesirable requirement is an annotation that becomes necessary only because an unrelated statement changed the closure's checking category. SE-0326 reduces that inconsistency while retaining annotations as the way to resolve an actually ambiguous interface.
The implementation uses incremental checking because earlier attempts ran into the cost of treating larger bodies like single expressions. Its benefit is therefore tied to a specific information-flow model. Context informs the body, earlier statements establish types for later ones, and the body can inform its enclosing expression. That model gives developers a concrete explanation for both newly accepted code and cases that still need a signature.