Hi there... let's say I define a very simple macro like this:
@freestanding(expression)
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?
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?
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.
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).
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.