Swift @resultBuilder with enum problem

I think i am having a problem using resultBuild with enums, is that normal or compile improve need?

@resultBuilder
struct ResultBuilder<T>
{
    static func buildBlock(_ block: [T]...) -> [T]
    {
        block.flatMap { $0 }
    }

    ....
}
enum PathModifier
{
    case move(to: CGPoint)
    case addLine(to: CGPoint)
    case stroke
    
    func modify(_ path: NSBezierPath)
    {
        switch self {
        case .move(let point):
            path.move(to: point)
            
        case .addLine(let point):
            path.line(to: point)
            
        case .stroke:
            path.stroke()
        }
    }
}
extension NSBezierPath
{
    @discardableResult
    convenience init(@ResultBuilder<PathModifier> _ modifiers: () -> [PathModifier])
    {
        self.init()
        
        modifiers().forEach {
            $0.modify(self)
        }
    }
    
    @discardableResult
    convenience init(modifiers: [PathModifier])
    {
        self.init()
        
        modifiers.forEach {
            $0.modify(self)
        }
    }
}

As you see, i hava above code, then if i try to write this, it would not work:

    NSBezierPath {
        .move(to: .zero)
        .addLine(to: CGPoint(1, 1))
        .stroke
    }

the compile says: Enum case 'addLine' cannot be used as an instance member

But if i write like this, it works:

    NSBezierPath {
        PathModifier.move(to: .zero)
        PathModifier.addLine(to: CGPoint(x: 1, y: 1))
        PathModifier.stroke
    }

and this works to:

    NSBezierPath(modifiers: [
        .move(to: .zero),
        .addLine(to: CGPoint(x: 1, y: 1)),
        .stroke
    ])

the problem is i like to write code more like DLS and if I have to write PathModifier each time, this would be little be bothering don't you think? It there anyway i can improve this?

The reason the compiler gives you that message is that your code is being interpreted like this (with addLine being a chained method call on move):

NSBezierPath {
    .move(to: .zero).addLine(to: ...).stroke
}

This sort of problem is impossible to avoid with enum cases unless you were to put semicolons after each case (though that is disfavored, style-wise), since this syntax allows modifier methods like those of SwiftUI. Usually, I end up writing result builders, not with enum cases, but with structs, making your example look like this:

NSBezierPath {
    Move(to: .zero)
    Line(to: ...)
    Stroke()
}

Though this doesn't read as well in your case specifically, this works better whenever you have to allow modifier methods. (Other note: I think this question would probably fit better in the "Using Swift" category)

3 Likes

Thanks, I see the point, the reason why I use enum instead strut is because this is much simpler, I don’t need to write multiple struts in this way, and namespace is another problem. I think i just keep writing PathModifier each time for now.

I suppose you can make global functions (move etc) that return the corresponding enum cases. Then you get the same syntax but without the points.

There was a pitch about improving lookup in result builders to allow this sort of thing.

1 Like