Function Builder and Type Inference

Is there any way to make type interference work there? If I use and Array of Columns the type of Item is inferred but not when I use a ColumnBuilder.


struct ContentView: View {
    
    @State var people = (1...100).map { Person(name: "Person \($0)", age: 20) }
    
    var body: some View { 
        Table(items: people) { // Cannot convert value of type '() -> Array<Column<_>>' to expected argument type '() -> [Column<Person>]'
            Column(title: "C1") {
                Text("\($0.name)")
            }
            Column(title: "C2") {
                Text("\($0.age)")
            }
        }
    }
    
}

struct Table<Item: Identifiable> {
    
    let columns: [Column<Item>]
    let items: [Item]
    
    init(items: [Item], @ColumnBuilder columns: () -> [Column<Item>]) {
        self.items = items
        self.columns = columns()
    }
}
 
@_functionBuilder
struct ColumnBuilder {
    static func buildBlock<Item>(_ columns: Column<Item>...) -> [Column<Item>] {
        return columns
    }
}

struct Column<Item> {
    
    let title: String
    let content: (Item) -> (AnyView)
    
    init<Content: View>(title: String, content: @escaping (Item) -> Content) {
        self.title = title
        self.content = { AnyView(content($0)) }
    }
    
}

You can make ColumnBuilder generic over the Item type and use buildExpression:

@resultBuilder
struct ColumnBuilder<Item> {
  static func buildBlock(_ columns: Column<Item>...) -> [Column<Item>] {
    return columns
  }
  
  static func buildExpression(_ column: Column<Item>) -> Column<Item> {
    return column
  }
}
struct Table<Item: Identifiable>: View {
  let columns: [Column<Item>]
  let items: [Item]
  
  init(items: [Item], @ColumnBuilder<Item> columns: () -> [Column<Item>]) {
    self.items = items
    self.columns = columns()
  }
  
  var body: some View { ... }
}

As for why buildExpression helps the type checker, it is mentioned in SE-0289:


Side note: it would be nice to have a ready-to-be-executed code snippet. In this case the Person struct is missing and both Column and Table aren't conforming to View, so in order to check if there were other errors I needed to "fill the gaps" prior doing anything else :slight_smile:

1 Like

Thank you so much! I tried this approach but I missed buildExpression.

Sorry about that :sweat_smile: