ExpressibleBy*Literal in result builders

I've been playing around with result builders a bit. I was thinking of using ExpressibleByStringLiteral to convert a string literal into MyComponent but got hit by a "Cannot convert value of type MyComponent" error.

Is there something I am missing? It seems like the ExpressibleBy*Literal set of protocols would be a great fit for result builders.

enum MyComponent {
    case null
    case string(String)
}

@resultBuilder
struct MyBuilder
{
    static func buildBlock(_ components: MyComponent...) -> MyComponent {
        if let firstComponent = components.first {
            return firstComponent
        } else {
            return .null
        }
    }
}

extension MyComponent: ExpressibleByStringLiteral {
    init(stringLiteral value: String) {
        self = .string(value)
    }
}

extension MyComponent {
    init(@MyBuilder build: () -> MyComponent) {
        self = build()
    }
}

func testItworks() {
    let component = MyComponent {
        "A String" // Error: Cannot convert value of type MyComponent
    }
}

Thanks,
Simon

Result builder type inference seems to be a bit weaker, so I'm not sure if this is another example of that.

I think you might be able to instead write an overload of buildExpression that takes a string:

extension MyBuilder {
    static func buildExpression(_ expression: String) -> MyComponent {
        .string(value)
    }
}
2 Likes

Yeah, IIRC, the buildBlock transform basically does this:

    let component = MyComponent {
        "A String" // Error: Cannot convert value of type MyComponent
    }

gets turned into:

    let component = MyComponent {
        let _partialResult1 = buildExpression("A String")
        let _block = buildBlock(_partialResult1)
        return buildFinalResult(_block)
    }

where buildExpression and buildFinalResult may be omitted if the builder in question doesn't implement them. So the type of the components in buildBlock can't directly influence the inference of the individual partial results. You can see that also here:

@resultBuilder
struct B {
    static func buildBlock(_ components: [Int]...) -> [Int] {
        components.first!
    }
}


func build(@B _ block: () -> [Int]) -> [Int] {
    return block()
}

build {
    [] // Error, cannot convert '[Any]' to '[Int]'
}

but if we add the builder function

    static func buildExpression(_ expression: [Int]) -> [Int] {
        expression
    }

then everything works as expected.

3 Likes

That makes sense, thanks for the help @stephencelis and @Jumhyn !