From ac823861c8bc6e6559736159e45937a637745bfe Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 30 Nov 2018 08:43:31 -0800 Subject: [PATCH] Systematically name and span codegen variables. Fixes #839. --- core/codegen/src/attribute/catch.rs | 19 +++-- core/codegen/src/attribute/route.rs | 100 +++++++++++----------- core/codegen/src/lib.rs | 29 ++++++- core/codegen/tests/expansion.rs | 53 ++++++++++++ core/codegen/tests/other-route.rs | 123 ---------------------------- core/codegen/tests/route.rs | 30 ------- 6 files changed, 141 insertions(+), 213 deletions(-) create mode 100644 core/codegen/tests/expansion.rs delete mode 100644 core/codegen/tests/other-route.rs diff --git a/core/codegen/src/attribute/catch.rs b/core/codegen/src/attribute/catch.rs index e0d520f0..9a32ca12 100644 --- a/core/codegen/src/attribute/catch.rs +++ b/core/codegen/src/attribute/catch.rs @@ -50,10 +50,13 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result { let (vis, status) = (&catch.function.vis, &catch.status); let status_code = status.0.code; + // Variables names we'll use and reuse. + define_vars_and_mods!(req, catcher, response, Request, Response); + // Determine the number of parameters that will be passed in. let (fn_sig, inputs) = match catch.function.decl.inputs.len() { 0 => (quote!(fn() -> _), quote!()), - 1 => (quote!(fn(&::rocket::Request) -> _), quote!(__req)), + 1 => (quote!(fn(&#Request) -> _), quote!(#req)), _ => return Err(catch.function.decl.inputs.span() .error("invalid number of arguments: must be zero or one") .help("catchers may optionally take an argument of type `&Request`")) @@ -70,8 +73,8 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result { let catcher_response = quote_spanned!(return_type_span => { // Emit this to force a type signature check. - let __catcher: #fn_sig = #user_catcher_fn_name; - ::rocket::response::Responder::respond_to(__catcher(#inputs), __req)? + let #catcher: #fn_sig = #user_catcher_fn_name; + ::rocket::response::Responder::respond_to(#catcher(#inputs), #req)? }); // Generate the catcher, keeping the user's input around. @@ -79,13 +82,11 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result { #user_catcher_fn /// Rocket code generated wrapping catch function. - #vis fn #generated_fn_name<'_b>( - __req: &'_b ::rocket::Request - ) -> ::rocket::response::Result<'_b> { - let response = #catcher_response; - ::rocket::response::Response::build() + #vis fn #generated_fn_name<'_b>(#req: &'_b #Request) -> #response::Result<'_b> { + let __response = #catcher_response; + #Response::build() .status(#status) - .merge(response) + .merge(__response) .ok() } diff --git a/core/codegen/src/attribute/route.rs b/core/codegen/src/attribute/route.rs index 3dc65e89..e1f63e87 100644 --- a/core/codegen/src/attribute/route.rs +++ b/core/codegen/src/attribute/route.rs @@ -124,6 +124,7 @@ fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result { } fn param_expr(seg: &Segment, ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 { + define_vars_and_mods!(req, data, error, log, request, Outcome); let i = seg.index.expect("dynamic parameters must be indexed"); let span = ident.span().unstable().join(ty.span()).unwrap().into(); let name = ident.to_string(); @@ -131,32 +132,32 @@ fn param_expr(seg: &Segment, ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 // All dynamic parameter should be found if this function is being called; // that's the point of statically checking the URI parameters. let internal_error = quote!({ - ___l::error("Internal invariant error: expected dynamic parameter not found."); - ___l::error("Please report this error to the Rocket issue tracker."); - ___Outcome::Forward(__data) + #log::error("Internal invariant error: expected dynamic parameter not found."); + #log::error("Please report this error to the Rocket issue tracker."); + #Outcome::Forward(#data) }); // Returned when a dynamic parameter fails to parse. let parse_error = quote!({ - ___l::warn_(&format!("Failed to parse '{}': {:?}", #name, __e)); - ___Outcome::Forward(__data) + #log::warn_(&format!("Failed to parse '{}': {:?}", #name, #error)); + #Outcome::Forward(#data) }); let expr = match seg.kind { Kind::Single => quote_spanned! { span => - match __req.raw_segment_str(#i) { - Some(__s) => match <#ty as ___r::FromParam>::from_param(__s) { + match #req.raw_segment_str(#i) { + Some(__s) => match <#ty as #request::FromParam>::from_param(__s) { Ok(__v) => __v, - Err(__e) => return #parse_error, + Err(#error) => return #parse_error, }, None => return #internal_error } }, Kind::Multi => quote_spanned! { span => - match __req.raw_segments(#i) { - Some(__s) => match <#ty as ___r::FromSegments>::from_segments(__s) { + match #req.raw_segments(#i) { + Some(__s) => match <#ty as #request::FromSegments>::from_segments(__s) { Ok(__v) => __v, - Err(__e) => return #parse_error, + Err(#error) => return #parse_error, }, None => return #internal_error } @@ -171,32 +172,36 @@ fn param_expr(seg: &Segment, ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 } fn data_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 { + define_vars_and_mods!(req, data, FromData, Outcome, Transform); let span = ident.span().unstable().join(ty.span()).unwrap().into(); quote_spanned! { span => - let __transform = <#ty as ___FromData>::transform(__req, __data); + let __transform = <#ty as #FromData>::transform(#req, #data); #[allow(unreachable_patterns, unreachable_code)] let __outcome = match __transform { - ___T::Owned(___Outcome::Success(__v)) => ___T::Owned(___Outcome::Success(__v)), - ___T::Borrowed(___Outcome::Success(ref __v)) => { - ___T::Borrowed(___Outcome::Success(::std::borrow::Borrow::borrow(__v))) + #Transform::Owned(#Outcome::Success(__v)) => { + #Transform::Owned(#Outcome::Success(__v)) }, - ___T::Borrowed(__o) => ___T::Borrowed(__o.map(|_| { + #Transform::Borrowed(#Outcome::Success(ref __v)) => { + #Transform::Borrowed(#Outcome::Success(::std::borrow::Borrow::borrow(__v))) + }, + #Transform::Borrowed(__o) => #Transform::Borrowed(__o.map(|_| { unreachable!("Borrowed(Success(..)) case handled in previous block") })), - ___T::Owned(__o) => ___T::Owned(__o), + #Transform::Owned(__o) => #Transform::Owned(__o), }; #[allow(non_snake_case, unreachable_patterns, unreachable_code)] - let #ident: #ty = match <#ty as ___FromData>::from_data(__req, __outcome) { - ___Outcome::Success(__d) => __d, - ___Outcome::Forward(__d) => return ___Outcome::Forward(__d), - ___Outcome::Failure((__c, _)) => return ___Outcome::Failure(__c), + let #ident: #ty = match <#ty as #FromData>::from_data(#req, __outcome) { + #Outcome::Success(__d) => __d, + #Outcome::Forward(__d) => return #Outcome::Forward(__d), + #Outcome::Failure((__c, _)) => return #Outcome::Failure(__c), }; } } fn query_exprs(route: &Route) -> Option { + define_vars_and_mods!(data, trail, log, request, req, Outcome, SmallVec, Query); let query_segments = route.attribute.path.query.as_ref()?; let (mut decls, mut matchers, mut builders) = (vec![], vec![], vec![]); for segment in query_segments { @@ -218,8 +223,7 @@ fn query_exprs(route: &Route) -> Option { let mut #ident: Option<#ty> = None; }, Kind::Multi => quote_spanned! { span => - let mut __trail = - ::rocket::http::private::SmallVec::<[___r::FormItem; 8]>::new(); + let mut #trail = #SmallVec::<[#request::FormItem; 8]>::new(); }, Kind::Static => quote!() }; @@ -228,11 +232,11 @@ fn query_exprs(route: &Route) -> Option { Kind::Single => quote_spanned! { span => (_, #name, __v) => { #[allow(unreachable_patterns, unreachable_code)] - let __v = match <#ty as ___r::FromFormValue>::from_form_value(__v) { + let __v = match <#ty as #request::FromFormValue>::from_form_value(__v) { Ok(__v) => __v, Err(__e) => { - ___l::warn_(&format!("Failed to parse '{}': {:?}", #name, __e)); - return ___Outcome::Forward(__data); + #log::warn_(&format!("Failed to parse '{}': {:?}", #name, __e)); + return #Outcome::Forward(#data); } }; @@ -243,26 +247,26 @@ fn query_exprs(route: &Route) -> Option { (#name, _, _) => continue, }, Kind::Multi => quote! { - _ => __trail.push(__i), + _ => #trail.push(__i), } }; let builder = match segment.kind { Kind::Single => quote_spanned! { span => - let #ident = match #ident.or_else(<#ty as ___r::FromFormValue>::default) { + let #ident = match #ident.or_else(<#ty as #request::FromFormValue>::default) { Some(__v) => __v, None => { - ___l::warn_(&format!("Missing required query parameter '{}'.", #name)); - return ___Outcome::Forward(__data); + #log::warn_(&format!("Missing required query parameter '{}'.", #name)); + return #Outcome::Forward(#data); } }; }, Kind::Multi => quote_spanned! { span => - let #ident = match <#ty as ___r::FromQuery>::from_query(___r::Query(&__trail)) { + let #ident = match <#ty as #request::FromQuery>::from_query(#Query(&#trail)) { Ok(__v) => __v, Err(__e) => { - ___l::warn_(&format!("Failed to parse '{}': {:?}", #name, __e)); - return ___Outcome::Forward(__data); + #log::warn_(&format!("Failed to parse '{}': {:?}", #name, __e)); + return #Outcome::Forward(#data); } }; }, @@ -278,7 +282,7 @@ fn query_exprs(route: &Route) -> Option { Some(quote! { #(#decls)* - if let Some(__items) = __req.raw_query_items() { + if let Some(__items) = #req.raw_query_items() { for __i in __items { match (__i.raw.as_str(), __i.key.as_str(), __i.value) { #( @@ -297,13 +301,14 @@ fn query_exprs(route: &Route) -> Option { } fn request_guard_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 { + define_vars_and_mods!(req, data, request, Outcome); let span = ident.span().unstable().join(ty.span()).unwrap().into(); quote_spanned! { span => #[allow(non_snake_case, unreachable_patterns, unreachable_code)] - let #ident: #ty = match <#ty as ___r::FromRequest>::from_request(__req) { - ___Outcome::Success(__v) => __v, - ___Outcome::Forward(_) => return ___Outcome::Forward(__data), - ___Outcome::Failure((__c, _)) => return ___Outcome::Failure(__c), + let #ident: #ty = match <#ty as #request::FromRequest>::from_request(#req) { + #Outcome::Success(__v) => __v, + #Outcome::Forward(_) => return #Outcome::Forward(#data), + #Outcome::Failure((__c, _)) => return #Outcome::Failure(__c), }; } } @@ -356,6 +361,7 @@ fn codegen_route(route: Route) -> Result { } // Gather everything we need. + define_vars_and_mods!(req, data, handler, Request, Data, StaticRouteInfo); let (vis, user_handler_fn) = (&route.function.vis, &route.function); let user_handler_fn_name = &user_handler_fn.ident; let generated_fn_name = user_handler_fn_name.prepend(ROUTE_FN_PREFIX); @@ -372,21 +378,15 @@ fn codegen_route(route: Route) -> Result { /// Rocket code generated wrapping route function. #vis fn #generated_fn_name<'_b>( - __req: &'_b ::rocket::Request, - __data: ::rocket::Data - ) -> ::rocket::handler::Outcome<'_b> { - #[allow(unused_imports)] - use rocket::{ - Outcome as ___Outcome, logger as ___l, request as ___r, - data::{FromData as ___FromData, Transform as ___T}, - }; - + #req: &'_b #Request, + #data: #Data + ) -> #handler::Outcome<'_b> { #(#req_guard_definitions)* #(#parameter_definitions)* #data_stmt let ___responder = #user_handler_fn_name(#(#parameter_names),*); - ::rocket::handler::Outcome::from(__req, ___responder) + #handler::Outcome::from(#req, ___responder) } /// Rocket code generated wrapping URI macro. @@ -394,8 +394,8 @@ fn codegen_route(route: Route) -> Result { /// Rocket code generated static route info. #[allow(non_upper_case_globals)] - #vis static #generated_struct_name: ::rocket::StaticRouteInfo = - ::rocket::StaticRouteInfo { + #vis static #generated_struct_name: #StaticRouteInfo = + #StaticRouteInfo { name: stringify!(#user_handler_fn_name), method: #method, path: #path, diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 0d5c6fdd..ef8e1c5a 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -64,7 +64,34 @@ extern crate proc_macro; extern crate rocket_http as http; extern crate indexmap; -#[macro_use] mod proc_macro_ext; +macro_rules! define { + ($val:path as $v:ident) => (#[allow(non_snake_case)] let $v = quote!($val);); +} + +macro_rules! define_vars_and_mods { + (@req as $v:ident) => (define!(__req as $v)); + (@catcher as $v:ident) => (define!(__catcher as $v)); + (@data as $v:ident) => (define!(__data as $v)); + (@error as $v:ident) => (define!(__error as $v)); + (@trail as $v:ident) => (define!(__trail as $v)); + (@request as $v:ident) => (define!(::rocket::request as $v)); + (@response as $v:ident) => (define!(::rocket::response as $v)); + (@handler as $v:ident) => (define!(::rocket::handler as $v)); + (@log as $v:ident) => (define!(::rocket::logger as $v)); + (@Outcome as $v:ident) => (define!(::rocket::Outcome as $v)); + (@FromData as $v:ident) => (define!(::rocket::data::FromData as $v)); + (@Transform as $v:ident) => (define!(::rocket::data::Transform as $v)); + (@Query as $v:ident) => (define!(::rocket::request::Query as $v)); + (@Request as $v:ident) => (define!(::rocket::Request as $v)); + (@Response as $v:ident) => (define!(::rocket::response::Response as $v)); + (@Data as $v:ident) => (define!(::rocket::Data as $v)); + (@StaticRouteInfo as $v:ident) => (define!(::rocket::StaticRouteInfo as $v)); + (@SmallVec as $v:ident) => (define!(::rocket::http::private::SmallVec as $v)); + ($($name:ident),*) => ($(define_vars_and_mods!(@$name as $name);)*) +} + +#[macro_use] +mod proc_macro_ext; mod derive; mod attribute; mod bang; diff --git a/core/codegen/tests/expansion.rs b/core/codegen/tests/expansion.rs new file mode 100644 index 00000000..1f19c2db --- /dev/null +++ b/core/codegen/tests/expansion.rs @@ -0,0 +1,53 @@ +#![feature(proc_macro_hygiene, decl_macro)] + +#[macro_use] extern crate rocket; + +use rocket::local::Client; + +#[get("/easy/")] +fn easy(id: i32) -> String { + format!("easy id: {}", id) +} + +macro_rules! make_handler { + () => { + #[get("/hard/")] + fn hard(id: i32) -> String { + format!("hard id: {}", id) + } + } +} + +make_handler!(); + +#[test] +fn test_reexpansion() { + let rocket = rocket::ignite().mount("/", routes![easy, hard]); + let client = Client::new(rocket).unwrap(); + + let mut response = client.get("/easy/327").dispatch(); + assert_eq!(response.body_string().unwrap(), "easy id: 327"); + + let mut response = client.get("/hard/72").dispatch(); + assert_eq!(response.body_string().unwrap(), "hard id: 72"); +} + +macro_rules! index { + ($type:ty) => { + #[get("/")] + fn index(thing: rocket::State<$type>) -> String { + format!("Thing: {}", *thing) + } + } +} + +index!(i32); + +#[test] +fn test_index() { + let rocket = rocket::ignite().mount("/", routes![index]).manage(100i32); + let client = Client::new(rocket).unwrap(); + + let mut response = client.get("/").dispatch(); + assert_eq!(response.body_string().unwrap(), "Thing: 100"); +} diff --git a/core/codegen/tests/other-route.rs b/core/codegen/tests/other-route.rs deleted file mode 100644 index e66acf3d..00000000 --- a/core/codegen/tests/other-route.rs +++ /dev/null @@ -1,123 +0,0 @@ -// #![feature(proc_macro_hygiene, decl_macro)] - -// #[macro_use] extern crate rocket; - -// #[get("/", rank = 1)] -// fn get1() -> &'static str { "hi" } - -// #[get("/", rank = 2)] -// fn get2() -> &'static str { "hi" } - -// #[get("/", rank = 3)] -// fn get3() -> &'static str { "hi" } - -// #[get("/")] fn get() { } -// #[route(GET, "/")] fn get_r() { } - -// #[put("/")] fn put() { } -// #[route(PUT, "/")] fn put_r() { } - -// #[post("/")] fn post() { } -// #[route(POST, "/")] fn post_r() { } - -// #[delete("/")] fn delete() { } -// #[route(DELETE, "/")] fn delete_r() { } - -// #[head("/")] fn head() { } -// #[route(HEAD, "/")] fn head_r() { } - -// #[patch("/")] fn patch() { } -// #[route(PATCH, "/")] fn patch_r() { } - -// #[options("/")] fn options() { } -// #[route(OPTIONS, "/")] fn options_r() { } - -// use rocket::http::{Cookies, RawStr}; -// use rocket::request::Form; - -// #[derive(FromForm)] -// struct User<'a> { -// name: &'a RawStr, -// nickname: String, -// } - -// #[post("/<_name>?<_query>", format = "application/json", data = "", rank = 2)] -// fn get( -// _name: &RawStr, -// _query: User, -// user: Form, -// _cookies: Cookies -// ) -> String { -// format!("{}:{}", user.name, user.nickname) -// } - -// #[post("/", format = "application/x-custom")] -// fn get() -> &'static str { "hi" } - -// #[get("/test///")] -// fn get(one: String, two: usize, three: isize) -> &'static str { "hi" } - -// #[get("/test/<_one>/<_two>/<__three>")] -// fn ignored(_one: String, _two: usize, __three: isize) -> &'static str { "hi" } - -// #[get("/")] -// fn get() -> &'static str { "hi" } - -// #[get("/")] -// fn get_empty() { } - -// #[get("/one")] -// fn one() { } - -// #[get("/two")] -// fn two() { } - -// #[get("/three")] -// fn three() { } - -// #[get("/four")] -// fn four() { } - -// #[test] -// fn main() { -// let instance = rocket::ignite() -// .mount("/", routes![one]); - -// let other = instance.mount("/", routes![two]); -// other.mount("/", routes![three]) -// .mount("/", routes![four]); - -// rocket::ignite() -// .mount("/", routes![one]) -// .mount("/", routes![two]) -// .mount("/", routes![three]) -// .mount("/", routes![four]); - -// let a = rocket::ignite() -// .mount("/", routes![one]) -// .mount("/", routes![two]); - -// let b = a.mount("/", routes![three]) -// .mount("/", routes![four]); -// } - -// #[get("/")] -// fn todo(todo: String) -> String { -// todo -// } - -// #[post("//")] -// fn get(a: String, b: PathBuf) -> String { -// format!("{}/{}", a, b.to_string_lossy()) -// } - -// #[post("//")] -// fn get2(a: String, b: Result) -> String { -// format!("{}/{}", a, b.unwrap().to_string_lossy()) -// } - - -// #[test] -// fn main() { -// let _ = routes![todo]; -// } diff --git a/core/codegen/tests/route.rs b/core/codegen/tests/route.rs index 9cbdcf24..90c86c09 100644 --- a/core/codegen/tests/route.rs +++ b/core/codegen/tests/route.rs @@ -112,33 +112,3 @@ fn test_full_route() { assert_eq!(response.body_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})", sky, name, "A A", "inside", path, simple, expected_uri)); } - -// Check that we propogate span information correctly to allow re-expansion. - -#[get("/easy/")] -fn easy(id: i32) -> String { - format!("easy id: {}", id) -} - -macro_rules! make_handler { - () => { - #[get("/hard/")] - fn hard(id: i32) -> String { - format!("hard id: {}", id) - } - } -} - -make_handler!(); - -#[test] -fn test_reexpansion() { - let rocket = rocket::ignite().mount("/", routes![easy, hard]); - let client = Client::new(rocket).unwrap(); - - let mut response = client.get("/easy/327").dispatch(); - assert_eq!(response.body_string().unwrap(), "easy id: 327"); - - let mut response = client.get("/hard/72").dispatch(); - assert_eq!(response.body_string().unwrap(), "hard id: 72"); -}