Function builders

I run into exact same problem here:

I pinged @Douglas_Gregor on twitter in that regard, but have not received any answer yet.

As I replied on Twitter, this is a bug that has been fixed on master but did not make it into Xcode 11.4. It's also specifically called out in the thread on function builders implementation progress.

Doug

1 Like

Argh, dang it, twitter bugged out on me, I didn't get any notifications that you replied. Sorry about that. :frowning:

@Douglas_Gregor one question.

Does buildExpression have to return the same type as buildBlock?

For some reason I couldn't make my function builder work otherwise. Ideally I want buildExpression to only wrap the expression into some type eraser / container ((buildExpression(_: Expression) -> AnyLayout) and then buildBlock to pick it up and return an array buildBlock(_: AnyLayout...) -> [AnyLayout]). Right now I used the HTML example from the original draft to workaround the problem.

@_functionBuilder
public enum LayoutBuilder {
  typealias Component<Content> = [AnyLayout<Content>]
  
  // FIXME: Can we just return `AnyLayout<T.Content>` here?
  static func buildExpression<T>(
    _ expression: T
  ) -> [AnyLayout<T.Content>] where T: Layout {
    [expression.wrapIntoAnyLayout()]
  }

  // FIXME: Can this just be `(AnyLayout<Content>) -> AnyLayout<Content>`? 
  static func buildIf<Content>(
    _ children: Component<Content>?
  ) -> Component<Content> {
    children ?? []
  }

  // FIXME: Can we remove this? Why is this one even needed? 
  static func buildBlock<Content>(
    _ component: Component<Content>
  ) -> Component<Content> {
    component
  }

  // FIXME: Ideally we want the parameter to be `AnyLayout<Content>...`
  // and the return type `[AnyLayout<Content>]`
  static func buildBlock<Content>(
    _ children: Component<Content>...
  ) -> Component<Content> {
    // FIXME: Use `.flatMap(\.self)` when compiler bug is resolved.
    children.flatMap { $0 }
  }
}

Ideally something like should be enough:

@_functionBuilder
public enum LayoutBuilder {
  static func buildExpression<T>(
    _ expression: T
  ) -> AnyLayout<T.Content> where T: Layout {
    expression.wrapIntoAnyLayout()
  }

  static func buildIf<T>(
    _ child: T?
  ) -> AnyLayout<Content> where T: Layout {
    child.wrapIntoAnyLayout()
  }

  static func buildBlock<Content>(
    _ children: AnyLayout<Content>...
  ) -> [AnyLayout<Content>] {
    children
  }
}

No, but the definitions you have for buildBlock require that every buildExpression call must produce the same type, because Component<Content>... takes a homogeneously-typed set of arguments. Hence, you need to type-erase somewhere or you need to have N overloads of buildBlock to account for 0..<N input expressions.

Doug

1 Like