Swift ResultBuilder Type Inference Issue

Problem Description

I'm encountering a type inference problem when using Swift's ResultBuilder pattern. In the code shown below, the compiler reports an error at the closure call to the build method: "Type of expression is ambiguous without a type annotation".

protocol PA {
}

protocol B: PA {
}

class C: B {
}
class D: B {
}

class E {
    func build(
        @Builder builder: () -> [any PA]
    ) {
        
    }
}

class F {
    func getD() -> D? {
        return D()
    }
    func test() {
        var e = E()
        e.build { //Type of expression is ambiguous without a type annotation
            C()
            if true {
                getD()!
            }
        }
    }
}

@resultBuilder
final class Builder: BaseBuilder<any PA> {
}

class BaseBuilder<E> {
    typealias Expression = E
    typealias Component = [E]
    
    // MARK: - Expression
    static func buildExpression(_ expression: E) -> E {
        return expression
    }
    
    // MARK: - Block overload
    static func buildBlock(_ expressions: E...) -> Component {
        return Array(expressions)
    }
    
    static func buildBlock(_ expressions: E?...) -> Component {
        return expressions.compactMap { $0 }
    }
    
    static func buildBlock(_ expressions: [Component]) -> Component {
        return expressions.flatMap { $0 }
    }
    
    // MARK: - Optional overload
    static func buildOptional(_ expression: E?) -> Component {
        return if let expression { [expression] } else { [] }
    }
    
    static func buildOptional(_ expressions: E?...) -> Component {
        return expressions.compactMap { $0 }
    }
    
    // MARK: - If-else overload
    static func buildEither(first child: Component) -> Component {
        return child
    }
    
    static func buildEither(second child: Component) -> Component {
        return child
    }
    
    // MARK: - Array overload
    static func buildArray(_ components: [E]) -> Component {
        return components
    }
}

Solution I've Tried

I've tried adding an explicit type annotation, as shown below:

e.build { () -> [any PA] in
    C()
    if true {
        getD()!
    }
}

However, this approach still doesn't resolve the issue.

Analysis of the Problem

Looking at the code structure, I have the following type relationships:

  • Protocol PA
  • Protocol B inherits from PA
  • Classes C and D both implement protocol B

In the build method, I'm trying to return both C() and getD()! (of type D) within the closure. Both should conform to the any PA type requirement, but the compiler cannot correctly infer the type.

My Questions

  1. Why is the compiler still unable to resolve the type of this closure even with an explicit type annotation?
  2. In the ResultBuilder pattern, how should I correctly handle returning different types that implement the same protocol?
  3. Are there other ways to refactor this code to avoid this type inference issue?

Thank you for your help!

After some experimentation, I've successfully resolved the issue. The key was modifying the BaseBuilderimplementation, particularly the buildExpression method.

Here's the corrected code that works:

@resultBuilder
final class Builder: BaseBuilder<any PA> {
}

class BaseBuilder<E> {
    typealias Expression = E
    typealias Component = [E]
    
    // MARK: - Expression
    static func buildExpression(_ expression: E) -> Component {
        return [expression]
    }
    
    // MARK: - Block overload
    static func buildBlock(_ expressions: E...) -> Component {
        return Array(expressions)
    }
    
    static func buildBlock(_ expressions: [E]) -> Component {
        return expressions
    }
    
    static func buildBlock(_ expressions: [E]...) -> Component {
        return expressions.flatMap { $0 }
    }
    
    // MARK: - Optional overload
    static func buildOptional(_ expressions: [E]?) -> Component {
        return if let expressions { expressions } else { [] }
    }
    
    // MARK: - If-else overload
    static func buildEither(first child: Component) -> Component {
        return child
    }
    
    static func buildEither(second child: Component) -> Component {
        return child
    }
    
    // MARK: - Array overload
    static func buildArray(_ components: [E]) -> Component {
        return components
    }
}