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.
@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