Result Builder Optionals

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!

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!

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