Referencing a macro's generated function inside the macro

I’m building a macro to define routes in Vapor and this works well and provides us type-safe routing among other benefits. However I would like to auto-register these routes and convert something like

@GET("api", "macros", "users")
func getUsers(req: Request) async throws -> String {
    return "Users"
}

into

func getUsers(req: Request) async throws -> String {
    return "Users"
}

@Sendable func _route_getUsers(req: Request) async throws -> Response {
    let result: some ResponseEncodable = try await getUsers(req: req)
    return try await result.encodeResponse(for: req)
}

let _ = app.on(.get, "api", "macros", "users", use: _route_getUsers)

If I write this code, it works fine. If I inline my macro definition it works fine. If I use the macro then I get the error Cannot find '_route_getUsers' in scope, so I’m guessing that the macro code can’t see the function defined in the macro?

I’ve also tried using another macro to do this in 2 steps, with the first macro generating:

@RouteRegistration(routeBuilder: app, method: .get, "api", "macros", "users")
@Sendable func _route_getUsers(req: Request) async throws -> Response {
    let result: some ResponseEncodable = try await getUsers(req: req)
    return try await result.encodeResponse(for: req)
}

Where @RouteRegistration generates the let _ = app.on(.get, "api", "macros", "users", use: _route_getUsers). However, this produces the same error.

So the question is - what am I doing wrong and how can I reference the function generated by the macro inside the macro code? This should work given I can reference variables generated by the macro inside the macro.

You can reproduce this by trying to build GitHub - vapor/vapor at non-controller-registration , which the relevant code defined in vapor/Sources/VaporMacrosPlugin/RouteRegistrationMacro.swift at non-controller-registration · vapor/vapor · GitHub and vapor/Sources/VaporMacrosPlugin/HTTPMethods/HTTPMethodMacroUtilities.swift at non-controller-registration · vapor/vapor · GitHub

(I’ve also tried returning the code as separate DeclSyntax items in the array returned by the macro expansion, returning it as a single code block and a few other workarounds to no avail)

1 Like

You're not doing anything wrong here. It is my understanding that peer macros (and probably others) cannot reference the members they generate. There are some solutions to this and my favorites are using member macros or freestanding declarations/expressions, but each has their own pros and cons.

In my upcoming networking library I use a freestanding declaration macro to generate a highly optimized http server and router taking into account all the provided data (middleware, redirects, routes, route groups, handlers, etc). I intentionally chose to use a freestanding decl macro because of the limitations of the other macros. One flaw in this approach is requiring all the route data to be provided in the macro parameters, but I think it is a worthy tradeoff.

However, I'm pretty sure there is a better solution to your problem out there somewhere. Maybe move the routes function out of global scope and into a struct/class, than you can use a member macro on the decl and reparse the route macros from the member macro expansion to add and reference the custom route declarations?