Hey there!
I am currently struggling to find a way to create a result builder for the following.
Lets say I have a type FooBar that consists of a string and an int like so:
struct FooBar {
var foo: String
var bar: Int
}
And I'd like to create a result builder to construct this type. What I'd like to accomplish is the following DSL:
FooBarBuilder {
if fooOrBar {
"Foo"
} else {
"Bar"
}
42
}
This should then construct either FooBar(foo: "Foo", bar: 42) or FooBar(foo: "Bar", bar: 42) depending on the condition. Is there a way to do that or can result builders simply not support that type of DSL?
Would be happy for any type of help here, thanks!
xAlien95
(Stefano De Carolis)
2
To create a result builder accepting if/else statements, you need to add an implementation for both buildEither(first:) and buildEither(second:):
@resultBuilder struct FooBarBuilder {
static func buildBlock<Parameter>(_ component: Parameter) -> Parameter {
component
}
static func buildBlock(_ foo: String, _ bar: Int) -> FooBar {
.init(foo: foo, bar: bar)
}
static func buildEither<Parameter>(first component: Parameter) -> Parameter {
component
}
static func buildEither<Parameter>(second component: Parameter) -> Parameter {
component
}
}
extension FooBar {
init(@FooBarBuilder _ content: () -> Self) {
self = content()
}
}
1 Like
Ah, the generics were the missing part. I always tried to do something like buildEither(first component: String) -> String and the compilers diagnostic wasn't very helpful. Thanks a ton!
xAlien95
(Stefano De Carolis)
4
You can use String without issues if your goal is to enable if/else only for the first parameter
@resultBuilder struct FooBarBuilder {
static func buildBlock(_ foo: String, _ bar: Int) -> FooBar { .init(foo: foo, bar: bar) }
static func buildBlock<Parameter>(_ component: Parameter) -> Parameter { component }
static func buildEither(first component: String) -> String { component }
static func buildEither(second component: String) -> String { component }
}
Could you post your implementation of FooBarBuilder? There could be room for improvement for the compiler diagnostics you faced.
Sure! I've had the following in place:
@resultBuilder
enum FooBarBuilder {
static func buildBlock(_ foo: String, _ bar: Int) -> FooBar {
.init(foo: foo, bar: bar)
}
static func buildExpression(_ component: String) -> String {
component
}
static func buildEither(first component: String) -> String {
component
}
static func buildEither(second component: String) -> String {
component
}
}
And the compilers diagnostic was Cannot convert value of type 'FooBar' to expected argument type 'String' as well as Missing argument for parameter #2 in call.
I thought I have to implement the buildExpression function whereas I would have to implement the same function just as buildBlock and now that I know what was the missing part, it totally makes sense that the compiler was missing the second parameter because it only saw the buildBlock function that takes two arguments.
I think the problem for me was that its not obvious what code the compiler is actually synthesizing and of course that I simply was not knowing that the buildExpression function is the wrong one to implement.
2 Likes