[Pitch #2] Function builders

Really like the additions of switch and if let!

One unexpected behaviour i encountered (with the Xcode 12 Beta 4) is that the functionbuilder transform doesn't seem to consider protocols and/or protocol extensions.

My example is a "default" function-builder.
The idea was to provide a protocol with default implementations for most of the build methods.

indirect enum FunctionBuilder<Expression> {
    case expression(Expression)
    case block([FunctionBuilder])
    case either(Either<FunctionBuilder, FunctionBuilder>)
    case optional(FunctionBuilder?)
}

protocol FunctionBuilderProtocol {
    associatedtype Expression
    typealias Component = FunctionBuilder<Expression>
    associatedtype Return
    
    static func buildExpression(_ expression: Expression) -> Component
    static func buildBlock(_ components: Component...) -> Component
    static func buildDo(_ components: Component...) -> Component
    static func buildOptional(_ optional: Component?) -> Component
    static func buildArray(_ components: [Component]) -> Component
    static func buildLimitedAvailability(_ component: Component) -> Component
    
    static func buildFinalResult(_ components: Component) -> Return
}

and provide default implementations as protocol extensions

extension FunctionBuilderProtocol {
    static func buildExpression(_ expression: Expression) -> Component { .expression(expression) }
    static func buildBlock(_ components: Component...) -> Component { .block(components) }
    static func buildDo(_ components: Component...) -> Component { .block(components) }
    static func buildOptional(_ optional: Component?) -> Component { .optional(optional) }
    static func buildArray(_ components: [Component]) -> Component { .block(components) }
    static func buildLimitedAvailability(_ component: Component) -> Component { component }
}

With this in place the only thing that needs to be implemented is buildFinalResult.

@_functionBuilder
enum ArrayBuilder<E>: FunctionBuilderProtocol {
    typealias Expression = E
    typealias Return = [E]
    
    static func buildFinalResult(_ components: Component) -> Return {
        switch components {
        case .expression(let e): return [e]
        case .block(let children): return children.flatMap(buildFinalResult)
        case .either(.left(let child)): return buildFinalResult(child)
        case .either(.right(let child)): return buildFinalResult(child)
        case .optional(let child?): return buildFinalResult(child)
        case .optional(nil): return []
        }
    }
}

But this doesn't compile

func buildArray(@ArrayBuilder<String> build: () -> [String]) -> [String] {
    return build()
}


let a = buildArray {
    "1"
    "2"
    if Bool.random() {
        "maybe 3"
    }
}


// error: closure containing control flow statement cannot be used with function builder 'ArrayBuilder'
//    if Bool.random() {
//    ^
//
// note: generic enum 'ArrayBuilder' declared here
// enum ArrayBuilder<E>: FunctionBuilderProtocol {
//      ^
//
// error: cannot convert value of type 'String' to expected argument type 'FunctionBuilder<ArrayBuilder<String>.Expression>' (aka 'FunctionBuilder<String>')
//    "1"
//     ^
// error: cannot convert value of type 'String' to expected argument type 'FunctionBuilder<ArrayBuilder<String>.Expression>' (aka 'FunctionBuilder<String>')
//    "2"

1 Like