Help to create a macro

Sooo. I want to create a macro that I can use together with my resultbuilder. I've got this constraint-build, which is a @resultBuilder:

@resultBuilder
public struct ConstraintBuilder {

    public static func buildBlock(_ components: NSLayoutConstraint...) -> [NSLayoutConstraint] {
        components
    }

    ...

}

And I would like to create a macro that can be used with this resultbuilder and x number of views.

For example, I would like something like the code below.

#constraint(view1, view2, view3) { arg in
    arg.view1.leadingAnchor.constraint(equalTo: arg.view2.trailingAnchor)
}

The important part is that it is failsafe regarding the variables, so for example this is a no-no, since that would work incorrectly if I switched place of view1 and view2

#constraint(view1, view2, view3) { view1, view2, view3 in
    arg.view1.leadingAnchor.constraint(equalTo: arg.view2.trailingAnchor)
}

Any idea how to build this?

I've tried stuff in the line of this (just playing around):

public struct ConstraintMacro: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) -> ExprSyntax {
        guard let argument = node.argumentList.first?.expression else {
            fatalError("compiler bug: the macro does not have any arguments")
        }

        return """
        struct \(context.makeUniqueName("ConstraintBuilderMacroStruct")) {
            struct Argument {
                let v1: UIView
                let v2: UIView
            }

            let argument: Argument

            func constraint(@ConstraintBuilder _ content: (Argument) -> [NSLayoutConstraint]) -> [NSLayoutConstraint] {
                content(argument)
            }
        }
        """
    }
}

Is there really any difference between the two forms? In both forms view1 represents the view instance in the first position. Introducing an internal Argument struct doesn't change it.

The difference is that if I write

#constraint(view1, view2, view3) { view3, view2, view1 in
    arg.view1.leadingAnchor.constraint(equalTo: arg.view2.trailingAnchor)
}

It still compiles but the arguments doesn't point to the correct view and the constraints will be incorrect? Which is what I want to prevent

I understand your requirement. What I meant was that the approach you described doesn't solve the issue, does it? In your both approaches, the only difference between view1, view2, view3 is their position in the argument list. How do you expect compiler to detect they are passed in wrong order?

This is where I was hoping for macro to solve it for me. Because there I can know the name of the arguments, right? And if I can access them via name instead of position, I've solved the issue. Perhaps as named parts of a tuple, or something.

I don't think so. the names "view1", "view2" contains only position information. So they can't be used to detect wrong argument position.

Maybe it wasnt' clear from my example. But I wasn't planning on calling the members of "Argument" v1 v2 etc, I was planning on naming them base on the input variable and create a new "Argument" struct for each case.

A better example could be for example:

#constraints(titleLabel, subtitleLabel) { arg in
    arg.titleLabel.leadingAnchor.constraint(equalTo: arg.subtitleLabel.trailingAnchor)
}

No, I don't think that would help. No matter what you call it, it represents the argument in that fixed position. I'd suggest you to implement the macro and see the result.

PS: using meaningful names does help human, not compiler, to detect the error.

Yes, it represents the argument at that fixed position, but if I provide an "arg" I have control over the member of "Argument". If I send it as individual arguments to the closure the caller can name the arguments themselves, which makes it error-prone, that's the difference. Anyway, this discussion doesn't really focus on the issue, the "Argument" solution is just one way of solving the issue.

The real problem I've got is I can't make a macro where I can write:

#constraints(titleLabel, subtitleLabel) { arg in
    arg.titleLabel.leadingAnchor.constraint(equalTo: arg.subtitleLabel.trailingAnchor)
}

Because then it's interpreted as the closure is a trailing closure and argument to #constraints while it's supposed perhaps invoke a closure returned by #constraints