Type validation of macro parameters

Hi there... let's say I define a very simple macro like this:

public macro double(_ value: Int) -> Int = #externalMacro(module: "MacrosShowcaseMacros", type: "DoubleWithPlusMacro")

public struct DoubleWithPlusMacro: 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")
        let result: ExprSyntax = "\(argument) + \(argument)"
        return result

Macro invocation and substitution is "type-validated" using the macro declaration but, what if a user decides to write another macro declaration like this?

public macro doubleString(_ value: String) -> String = #externalMacro(module: "MacrosShowcaseMacros", type: "DoubleWithPlusMacro")

Is there any way I can check that the argument(s) in FreestandingMacroExpansionSyntax are of the desired type so I don't get unexpected/unsupported uses of a given macro? Am I supposed/required to do it?

Thanks in advance

1 Like

You won't be able to retrieve the type from node. In your example, however, you may check the name of the macro call by node.macro and infer its type from that. You can also define different named parameters like value in macro double(value: Int) -> Int = ... for your Int and String variant. A third option would be to require a second argument that defines a type like Int.self or String.self. The latter is not very ergonomic to use, admittedly.


From what I can see the from digging in CompilerPluginMessageHandler.swift only the macro's name, and #externalMacro parameters are passed to your macro handling process (via CompilerPlugin). The parameters such as (_ value: Int) -> Int are not passed.

If you want to play then you can take a look at a slimmed down SwiftSyntax I made: GitHub - jjrscott/SwiftCompilerPlugin: Swift macros without requiring swift-syntax. The actually gets passed is something like this:

  "expandFreestandingMacro" : {
    "macro" : {
      "moduleName" : "MacrosShowcaseMacros",
      "name" : "double",
      "typeName" : "DoubleWithPlusMacro"
    "syntax" : {
      "kind" : "expression",
      "location" : {
        "fileID" : "ExampleClient/main.swift",
        "fileName" : "*****/SwiftCompilerPlugin/Examples/Sources/ExampleClient/main.swift",
        "line" : 9,
        "offset" : 107,
        "column" : 5
      "source" : "#double(10)"
    "discriminator" : "$s13ExampleClient33_C7C48FD1C44CD1E5F2188E7B86F2D462Ll6doublefMf_",
    "macroRole" : "expression"

Maybe the compiler should be sending the macro signature as part of this call.

(It would potentially be more useful than typeName which seems to be used by SwiftSyntax as a "key" to find the right class/struct - it could technically be anything).

1 Like

Sorry, I don't know if I wasn't clear enough. I don't pretend to have different variants of the macro to be applied to different parameter types. My concern was exactly the opposite: how can I be sure that the macro is being called with the correct parameter types if anybody can add new declarations pointing to the macro I developed? Is this checking something I should be doing?

You probably have no way to ensure that other than checking the name and argument labels of the called macro. If it’s the one you know, you continue processing it, otherwise you throw an error.

That’s still not 100% safe, admittedly. There are ideas, however, to provide macro implementations with type information in the future.

1 Like