Hi all,
We've been making quite a bit of progress on the implementation of function builders within the Swift compiler's type checker. While I'm not quite ready to reopen the big design discussion, I wanted to call attention to some of the work we're doing (some of which has made it into Swift releases, some that is still landing in master
) to bring it closer to the proposal and get this feature to realize its full potential. The hope here is to get the technology into place to allow us to really explore all of the cool ideas that have come up in the design of this feature, improving the development experience along the way.
Here's a summary of what we've been up to:
- One-way constraints are a change to the way in which we perform type checking for function builder closures. At a high level, this change makes type checking flow from the "top" of a closure down to the "bottom" (but not backwards). This better matches how normal function type checking works and also provides a significant improvement in type checking performance; technical details are in the code base and early pull request. This was shipped as part of Swift 5.1.1.
- The new diagnostic architecture allows us to properly diagnose errors deep within function builder closures by honing in on the specific problems that prevent a proper type check. This is technically separate from function builders, but is critical to their usability for developers. The bulk of this landed in Swift 5.2, and we hope the difference is night and day.
-
buildExpression
support: one can introduce a static methodbuildExpression
into a function builder type, and it will be called to map each raw expression within a function builder closure to a value that will then be passed tobuildBlock
.buildExpression
can be overloaded to provide some contextual translation of expressions that would be unwieldy withbuildBlock
alone. -
#warning
and#error
support, which are small-but-useful additions. -
Delayed constraint generation for single-expression closure bodies lets the type checker use more contextual information to type check closure bodies. For function builders, it means that single-expression closures will correctly make
buildBlock
andbuildExpression
calls (they were previously skipped in this case). -
Statement-based translation of function builders makes the implementation model in the compiler match what is specified in the proposal. Prior to this change, function builders were implemented by folding the entire closure into a single-expression closure with one giant expression. With this refactor, it becomes easy to support aspects of statements that cannot be described in expressions. Since then, we've added support for many more kinds of statements:
-
Multiple conditions in an
if
(Swift 5.3) -
if #available
(Swift 5.3) -
Local
let
andvar
declarations so long as they have initializers (Swift 5.3) -
if let
andif case
(Swift 5.3) -
switch
(Swift 5.3) -
for...in
(master only)
-
Multiple conditions in an
Not coincidentally, a lot of these changes impact on our ability to evolve the language further in the future, particularly in the area of closures. For example, the delayed constraint generation for single-expression closures could allow code like this to compile:
let f: (Int, Int) -> Bool = { $0.isMultiple(of: 2) }
Right now, one would have to add something like (x, _) in
to the closure to show that it takes two parameters.
The generalization of function builders to work with more-or-less arbitrary statements opens up the potential for better type inference through multi-statement closures. For example, right now a single-expression closure can infer a closure result type:
let result = someCollection.map { $0.method() }
but a multi-statement closure does not:
let result = someCollection.map {
if $0.someProperty {
return $0.method()
}
return $0.someOtherMethod()
}
and one will have to provide some type annotations to make this code compile. We should be able to infer the result type of the closure to avoid the need for extra type annotations.
No promises, of course, but it's fun to see where improvements in the architecture and implementation of the type checker can take Swift, especially in making function builders more powerful and more expressive.
EDIT: Updated in early May to reflect the additional support for if let
, switch
, and so on, as well as reflect what went into Swift 5.2 vs. Swift 5.3 vs. only on master.
Doug