Help me implement this simple Macro

I want to implement a simple freestanding macro but having difficulty implementing it.

I have a macro that accepts an instance of a struct

@freestanding(expression)
public macro rawValue(_ value: MyStruct) -> String = #externalMacro(module: "MyMacros", type: "RawValueMacro")

struct MyStruct: RawRepresentable {
    let rawValue: String
}

extension MyStruct {
    static let foo: Self = .init(rawValue: "bar")
}

I want simply in the client to expand #rawValue(.foo) to be "bar".

Now in the MyMacros module, I have

public struct RawValueMacro: ExpressionMacro {
    
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) -> ExprSyntax {

       // ??
    }
}

I hope that my goal is possible with Swift Macros

The problem I see is how to access the rawValue property from inside the macro itself.

A macro only has access to the syntax it's invoked with, so it can't see syntax "in some other place".

I presume this is a simplified problem, but you could have your macro expand #rawValue(.foo) to MyStruct.foo.rawValue, which will have the desired string value.

Alternatively, you could embed the table of raw values into the macro, so it already knows to map .foo to "bar".

1 Like

Unfortunately, I don't think this would work because MyStruct is part of the macro module, not the client. so it wouldn't compile.

MyStruct and MyStruct.foo have to be visible for #rawValue(.foo) to compile, so I don't see why MyStruct.foo.rawValue wouldn't?

1 Like

Here is my attempt as something sort of similar… passing a value from an app as a macro parameter and then "unboxing" that parameter at runtime in the macro expansion. For my example… it was a system type (SortOrder) that did not conform to RawRepresentable (which made things a little more difficult).

Since you have a parameter that does confirm to RawRepresentable… I think you have an extra option here. You could define a "shadow" declaration of MyStruct that lives along RawValueMacro (and then use that logic in your macro expansion)… or you could try to factor MyStruct out to some kind of "macro defs" module that can be imported by both the macro library and the client library (assuming this does not lead to some kind of dep cycle).

1 Like

Thanks for the info. I guess I had some wrong assumptions. I assumed that MyStruct would not be included in the app binary. but I think that is the case (?) since MyStruct is perfectly accessible in the client app, which defeat my original intent.

My struct have 5000+ static instances (with their string rawValue). I wanted to decrease that code size by making it a macro instead and removing the strings from the app clients.

I guess one option I have is to make MyStruct member-less and remove the rawRepresentable conformance then i would have

extension MyStruct {
    static let foo: Self = .init()
}

then along side RawValueMacro, I would have

let nameToValueMappings = [
"foo": "bar"
]

I think this way the actual strings (which is causing the large code size) would not be included in the app clients