From 004c40af4d90c59045e5ec2fcbac07f85c9e4600 Mon Sep 17 00:00:00 2001 From: Jacob O'Toole Date: Wed, 12 Mar 2025 23:44:53 +0000 Subject: [PATCH] Fix inconsistent identifier spans in route codegen Previously, identifiers such as `__req` were output with inconsistent spans. For example, in [`query_decls`](https://github.com/rwf2/Rocket/blob/28891e8072136f4641a33fb8c3f2aafce9d88d5b/core/codegen/src/attribute/route/mod.rs#L43), it was defined with `Span::call_site()`, but in [`request_guard_decl`](https://github.com/rwf2/Rocket/blob/28891e8072136f4641a33fb8c3f2aafce9d88d5b/core/codegen/src/attribute/route/mod.rs#L127) as `guard.ty.span()`. The problem is, when used with macros, these spans can be different and cause the identifiers to fail to resolve. A simple failure example is: ```rust macro_rules! empty_get { ($name:ident) => { #[get("/")] async fn $name() {} } } empty_get!(test); ``` In this case, the inconsistency arises because: - In [`codegen_route`](https://github.com/rwf2/Rocket/blob/28891e8072136f4641a33fb8c3f2aafce9d88d5b/core/codegen/src/attribute/route/mod.rs#L380), no span for `__req` is specified, which I believe means it defaults to `Span::call_site()`, so inside `macro_rules!`, then - in [`responder_outcome_expr`](https://github.com/rwf2/Rocket/blob/28891e8072136f4641a33fb8c3f2aafce9d88d5b/core/codegen/src/attribute/route/mod.rs#L299), the span used is from the identifier of the function, which is outside of `macro_rules!`. This causes the `__req` in `responder_outcome` not to resolve to the `__req` defined in the `monomorphized_function` arguments. The span is now always `Span::call_site()`, which resolves these issues. I believe `call_site` is acceptable, since it was already used as the span for these variables elsewhere. In addition, I didn't spot anywhere in the code where this could cause a problem. --- core/codegen/src/attribute/route/mod.rs | 14 +++++++------- core/codegen/tests/route.rs | 10 ++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/core/codegen/src/attribute/route/mod.rs b/core/codegen/src/attribute/route/mod.rs index 506102499c..4a51c2629c 100644 --- a/core/codegen/src/attribute/route/mod.rs +++ b/core/codegen/src/attribute/route/mod.rs @@ -63,7 +63,7 @@ fn query_decls(route: &Route) -> Option { _ => quote!(#name) }; - define_spanned_export!(ty.span() => FromForm, _form); + define_spanned_export!(Span::call_site() => FromForm, _form); let ty = quote_spanned!(ty.span() => <#ty as #FromForm>); let init = quote_spanned!(ty.span() => #ty::init(#_form::Options::Lenient)); @@ -124,8 +124,8 @@ fn query_decls(route: &Route) -> Option { fn request_guard_decl(guard: &Guard) -> TokenStream { let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty); - define_spanned_export!(ty.span() => - __req, __data, _request, display_hack, FromRequest, Outcome + define_spanned_export!(Span::call_site() => + __req, __data, display_hack, FromRequest, Outcome ); quote_spanned! { ty.span() => @@ -162,7 +162,7 @@ fn request_guard_decl(guard: &Guard) -> TokenStream { fn param_guard_decl(guard: &Guard) -> TokenStream { let (i, name, ty) = (guard.index, &guard.name, &guard.ty); - define_spanned_export!(ty.span() => + define_spanned_export!(Span::call_site() => __req, __data, _None, _Some, _Ok, _Err, Outcome, FromSegments, FromParam, Status, display_hack ); @@ -219,7 +219,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream { fn data_guard_decl(guard: &Guard) -> TokenStream { let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty); - define_spanned_export!(ty.span() => __req, __data, display_hack, FromData, Outcome); + define_spanned_export!(Span::call_site() => __req, __data, display_hack, FromData, Outcome); quote_spanned! { ty.span() => let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await { @@ -307,7 +307,7 @@ fn responder_outcome_expr(route: &Route) -> TokenStream { let _await = route.handler.sig.asyncness .map(|a| quote_spanned!(a.span() => .await)); - define_spanned_export!(ret_span => __req, _route); + define_spanned_export!(Span::call_site() => __req, _route); quote_spanned! { mixed(ret_span) => let ___responder = #user_handler_fn_name(#(#parameter_names),*) #_await; #_route::Outcome::from(#__req, ___responder) @@ -363,7 +363,7 @@ fn sentinels_expr(route: &Route) -> TokenStream { .map(|child| (child.parent, child.ty)); let sentinel = eligible_types.map(|(parent, ty)| { - define_spanned_export!(ty.span() => _sentinel); + define_spanned_export!(Span::call_site() => _sentinel); match parent { Some(p) if p.is_concrete(&generic_idents) => { diff --git a/core/codegen/tests/route.rs b/core/codegen/tests/route.rs index f9a1bf66e9..33c73a8833 100644 --- a/core/codegen/tests/route.rs +++ b/core/codegen/tests/route.rs @@ -347,3 +347,13 @@ fn test_inclusive_segments() { assert_eq!(get("//a//b////c/d/e"), "nonempty+c/d/e"); assert_eq!(get("//a//b////c/d/e/"), "nonempty+c/d/e/"); } + +macro_rules! empty_get { + ($name:ident) => { + #[allow(dead_code)] + #[get("/")] + async fn $name() {} + } +} + +empty_get!(test);