0
votes

According to the swift language guide, I need to implement buildBlock method when I define a result builder. And the guide also says:

" static func buildBlock(_ components: Component...) -> Component

Combines an array of partial results into a single partial result. A result builder must implement this method. "

Obviously, the parameter type and the return type should be the same. However, if I use different types, it also seems to be no problem. I write some simple code below:

@resultBuilder
struct DoubleBuilder {
    static func buildBlock(_ components: Int) -> Double {
        3
    }
}

It compiles successfully in Xcode. Can the types be different?It seems like to be inconsistent with the official guide.

EDIT:

The parameter type of buildBlock method in ViewBuilder in SwiftUI is also different from the return type. For example:

static func buildBlock<C0, C1>(C0, C1) -> TupleView<(C0, C1)>

1
This WWDC video is a great description of result builders and how they work: developer.apple.com/videos/play/wwdc2021/10253jrturton

1 Answers

1
votes

When they say

static func buildBlock(_ components: Component...) -> Component

They don't strictly mean that there exists one type, Component, that buildBlock must take in and return. They just mean that buildBlock should take in a number of parameters, whose types are the types of "partial results", and return a type that is a type of a "partial result". Calling them both Component might be a bit confusing.

I think the Swift evolution proposal explains a little better (emphasis mine):

The typing here is subtle, as it often is in macro-like features. In the following descriptions, Expression stands for any type that is acceptable for an expression-statement to have (that is, a raw partial result), Component stands for any type that is acceptable for a partial or combined result to have, and FinalResult stands for any type that is acceptable to be ultimately returned by the transformed function.

So Component, Expression and FinalResult etc don't have to be fixed to a single type. You can even overload the buildXXX methods, as demonstrated later in the evolution proposal.

Ultimately, result builders undergo a transformation to calls to buildXXX, when you actually use them. That is when type errors, if you have them, will be reported.

For example,

@resultBuilder
struct MyBuilder {
    static func buildBlock(_ p1: String, _ p2: String) -> Int {
        1
    }
}

func foo(@MyBuilder _ block: () -> Double) -> Double {
    block()
}

foo { // Cannot convert value of type 'Int' to closure result type 'Double'
    10 // Cannot convert value of type 'Int' to expected argument type 'String'
    20 // Cannot convert value of type 'Int' to expected argument type 'String'
}

The first error is because the result builder's result type is Int, but foo's closure parameter expects a return value of type Double. The last two is because the transformation of the result builder attempts to call MyBuilder.buildBlock(10, 20).

It all makes sense if you look at the transformed closure:

foo {
    let v1 = 10
    let v2 = 20
    return MyBuilder.buildBlock(v1, v2)
}

Essentially, as long as the transformed code compiles, there is no problem.

See more details about the transformation here.