Systematically name and span codegen variables.

Fixes #839.
This commit is contained in:
Sergio Benitez 2018-11-30 08:43:31 -08:00
parent b7db74144f
commit ac823861c8
6 changed files with 141 additions and 213 deletions

View File

@ -50,10 +50,13 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
let (vis, status) = (&catch.function.vis, &catch.status); let (vis, status) = (&catch.function.vis, &catch.status);
let status_code = status.0.code; 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. // Determine the number of parameters that will be passed in.
let (fn_sig, inputs) = match catch.function.decl.inputs.len() { let (fn_sig, inputs) = match catch.function.decl.inputs.len() {
0 => (quote!(fn() -> _), quote!()), 0 => (quote!(fn() -> _), quote!()),
1 => (quote!(fn(&::rocket::Request) -> _), quote!(__req)), 1 => (quote!(fn(&#Request) -> _), quote!(#req)),
_ => return Err(catch.function.decl.inputs.span() _ => return Err(catch.function.decl.inputs.span()
.error("invalid number of arguments: must be zero or one") .error("invalid number of arguments: must be zero or one")
.help("catchers may optionally take an argument of type `&Request`")) .help("catchers may optionally take an argument of type `&Request`"))
@ -70,8 +73,8 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
let catcher_response = quote_spanned!(return_type_span => { let catcher_response = quote_spanned!(return_type_span => {
// Emit this to force a type signature check. // Emit this to force a type signature check.
let __catcher: #fn_sig = #user_catcher_fn_name; let #catcher: #fn_sig = #user_catcher_fn_name;
::rocket::response::Responder::respond_to(__catcher(#inputs), __req)? ::rocket::response::Responder::respond_to(#catcher(#inputs), #req)?
}); });
// Generate the catcher, keeping the user's input around. // Generate the catcher, keeping the user's input around.
@ -79,13 +82,11 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
#user_catcher_fn #user_catcher_fn
/// Rocket code generated wrapping catch function. /// Rocket code generated wrapping catch function.
#vis fn #generated_fn_name<'_b>( #vis fn #generated_fn_name<'_b>(#req: &'_b #Request) -> #response::Result<'_b> {
__req: &'_b ::rocket::Request let __response = #catcher_response;
) -> ::rocket::response::Result<'_b> { #Response::build()
let response = #catcher_response;
::rocket::response::Response::build()
.status(#status) .status(#status)
.merge(response) .merge(__response)
.ok() .ok()
} }

View File

@ -124,6 +124,7 @@ fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
} }
fn param_expr(seg: &Segment, ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 { 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 i = seg.index.expect("dynamic parameters must be indexed");
let span = ident.span().unstable().join(ty.span()).unwrap().into(); let span = ident.span().unstable().join(ty.span()).unwrap().into();
let name = ident.to_string(); 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; // All dynamic parameter should be found if this function is being called;
// that's the point of statically checking the URI parameters. // that's the point of statically checking the URI parameters.
let internal_error = quote!({ let internal_error = quote!({
___l::error("Internal invariant error: expected dynamic parameter not found."); #log::error("Internal invariant error: expected dynamic parameter not found.");
___l::error("Please report this error to the Rocket issue tracker."); #log::error("Please report this error to the Rocket issue tracker.");
___Outcome::Forward(__data) #Outcome::Forward(#data)
}); });
// Returned when a dynamic parameter fails to parse. // Returned when a dynamic parameter fails to parse.
let parse_error = quote!({ let parse_error = quote!({
___l::warn_(&format!("Failed to parse '{}': {:?}", #name, __e)); #log::warn_(&format!("Failed to parse '{}': {:?}", #name, #error));
___Outcome::Forward(__data) #Outcome::Forward(#data)
}); });
let expr = match seg.kind { let expr = match seg.kind {
Kind::Single => quote_spanned! { span => Kind::Single => quote_spanned! { span =>
match __req.raw_segment_str(#i) { match #req.raw_segment_str(#i) {
Some(__s) => match <#ty as ___r::FromParam>::from_param(__s) { Some(__s) => match <#ty as #request::FromParam>::from_param(__s) {
Ok(__v) => __v, Ok(__v) => __v,
Err(__e) => return #parse_error, Err(#error) => return #parse_error,
}, },
None => return #internal_error None => return #internal_error
} }
}, },
Kind::Multi => quote_spanned! { span => Kind::Multi => quote_spanned! { span =>
match __req.raw_segments(#i) { match #req.raw_segments(#i) {
Some(__s) => match <#ty as ___r::FromSegments>::from_segments(__s) { Some(__s) => match <#ty as #request::FromSegments>::from_segments(__s) {
Ok(__v) => __v, Ok(__v) => __v,
Err(__e) => return #parse_error, Err(#error) => return #parse_error,
}, },
None => return #internal_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 { 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(); let span = ident.span().unstable().join(ty.span()).unwrap().into();
quote_spanned! { span => quote_spanned! { span =>
let __transform = <#ty as ___FromData>::transform(__req, __data); let __transform = <#ty as #FromData>::transform(#req, #data);
#[allow(unreachable_patterns, unreachable_code)] #[allow(unreachable_patterns, unreachable_code)]
let __outcome = match __transform { let __outcome = match __transform {
___T::Owned(___Outcome::Success(__v)) => ___T::Owned(___Outcome::Success(__v)), #Transform::Owned(#Outcome::Success(__v)) => {
___T::Borrowed(___Outcome::Success(ref __v)) => { #Transform::Owned(#Outcome::Success(__v))
___T::Borrowed(___Outcome::Success(::std::borrow::Borrow::borrow(__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") 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)] #[allow(non_snake_case, unreachable_patterns, unreachable_code)]
let #ident: #ty = match <#ty as ___FromData>::from_data(__req, __outcome) { let #ident: #ty = match <#ty as #FromData>::from_data(#req, __outcome) {
___Outcome::Success(__d) => __d, #Outcome::Success(__d) => __d,
___Outcome::Forward(__d) => return ___Outcome::Forward(__d), #Outcome::Forward(__d) => return #Outcome::Forward(__d),
___Outcome::Failure((__c, _)) => return ___Outcome::Failure(__c), #Outcome::Failure((__c, _)) => return #Outcome::Failure(__c),
}; };
} }
} }
fn query_exprs(route: &Route) -> Option<TokenStream2> { fn query_exprs(route: &Route) -> Option<TokenStream2> {
define_vars_and_mods!(data, trail, log, request, req, Outcome, SmallVec, Query);
let query_segments = route.attribute.path.query.as_ref()?; let query_segments = route.attribute.path.query.as_ref()?;
let (mut decls, mut matchers, mut builders) = (vec![], vec![], vec![]); let (mut decls, mut matchers, mut builders) = (vec![], vec![], vec![]);
for segment in query_segments { for segment in query_segments {
@ -218,8 +223,7 @@ fn query_exprs(route: &Route) -> Option<TokenStream2> {
let mut #ident: Option<#ty> = None; let mut #ident: Option<#ty> = None;
}, },
Kind::Multi => quote_spanned! { span => Kind::Multi => quote_spanned! { span =>
let mut __trail = let mut #trail = #SmallVec::<[#request::FormItem; 8]>::new();
::rocket::http::private::SmallVec::<[___r::FormItem; 8]>::new();
}, },
Kind::Static => quote!() Kind::Static => quote!()
}; };
@ -228,11 +232,11 @@ fn query_exprs(route: &Route) -> Option<TokenStream2> {
Kind::Single => quote_spanned! { span => Kind::Single => quote_spanned! { span =>
(_, #name, __v) => { (_, #name, __v) => {
#[allow(unreachable_patterns, unreachable_code)] #[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, Ok(__v) => __v,
Err(__e) => { Err(__e) => {
___l::warn_(&format!("Failed to parse '{}': {:?}", #name, __e)); #log::warn_(&format!("Failed to parse '{}': {:?}", #name, __e));
return ___Outcome::Forward(__data); return #Outcome::Forward(#data);
} }
}; };
@ -243,26 +247,26 @@ fn query_exprs(route: &Route) -> Option<TokenStream2> {
(#name, _, _) => continue, (#name, _, _) => continue,
}, },
Kind::Multi => quote! { Kind::Multi => quote! {
_ => __trail.push(__i), _ => #trail.push(__i),
} }
}; };
let builder = match segment.kind { let builder = match segment.kind {
Kind::Single => quote_spanned! { span => 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, Some(__v) => __v,
None => { None => {
___l::warn_(&format!("Missing required query parameter '{}'.", #name)); #log::warn_(&format!("Missing required query parameter '{}'.", #name));
return ___Outcome::Forward(__data); return #Outcome::Forward(#data);
} }
}; };
}, },
Kind::Multi => quote_spanned! { span => 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, Ok(__v) => __v,
Err(__e) => { Err(__e) => {
___l::warn_(&format!("Failed to parse '{}': {:?}", #name, __e)); #log::warn_(&format!("Failed to parse '{}': {:?}", #name, __e));
return ___Outcome::Forward(__data); return #Outcome::Forward(#data);
} }
}; };
}, },
@ -278,7 +282,7 @@ fn query_exprs(route: &Route) -> Option<TokenStream2> {
Some(quote! { Some(quote! {
#(#decls)* #(#decls)*
if let Some(__items) = __req.raw_query_items() { if let Some(__items) = #req.raw_query_items() {
for __i in __items { for __i in __items {
match (__i.raw.as_str(), __i.key.as_str(), __i.value) { match (__i.raw.as_str(), __i.key.as_str(), __i.value) {
#( #(
@ -297,13 +301,14 @@ fn query_exprs(route: &Route) -> Option<TokenStream2> {
} }
fn request_guard_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 { 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(); let span = ident.span().unstable().join(ty.span()).unwrap().into();
quote_spanned! { span => quote_spanned! { span =>
#[allow(non_snake_case, unreachable_patterns, unreachable_code)] #[allow(non_snake_case, unreachable_patterns, unreachable_code)]
let #ident: #ty = match <#ty as ___r::FromRequest>::from_request(__req) { let #ident: #ty = match <#ty as #request::FromRequest>::from_request(#req) {
___Outcome::Success(__v) => __v, #Outcome::Success(__v) => __v,
___Outcome::Forward(_) => return ___Outcome::Forward(__data), #Outcome::Forward(_) => return #Outcome::Forward(#data),
___Outcome::Failure((__c, _)) => return ___Outcome::Failure(__c), #Outcome::Failure((__c, _)) => return #Outcome::Failure(__c),
}; };
} }
} }
@ -356,6 +361,7 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
} }
// Gather everything we need. // 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 (vis, user_handler_fn) = (&route.function.vis, &route.function);
let user_handler_fn_name = &user_handler_fn.ident; let user_handler_fn_name = &user_handler_fn.ident;
let generated_fn_name = user_handler_fn_name.prepend(ROUTE_FN_PREFIX); let generated_fn_name = user_handler_fn_name.prepend(ROUTE_FN_PREFIX);
@ -372,21 +378,15 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
/// Rocket code generated wrapping route function. /// Rocket code generated wrapping route function.
#vis fn #generated_fn_name<'_b>( #vis fn #generated_fn_name<'_b>(
__req: &'_b ::rocket::Request, #req: &'_b #Request,
__data: ::rocket::Data #data: #Data
) -> ::rocket::handler::Outcome<'_b> { ) -> #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_guard_definitions)* #(#req_guard_definitions)*
#(#parameter_definitions)* #(#parameter_definitions)*
#data_stmt #data_stmt
let ___responder = #user_handler_fn_name(#(#parameter_names),*); 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. /// Rocket code generated wrapping URI macro.
@ -394,8 +394,8 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
/// Rocket code generated static route info. /// Rocket code generated static route info.
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
#vis static #generated_struct_name: ::rocket::StaticRouteInfo = #vis static #generated_struct_name: #StaticRouteInfo =
::rocket::StaticRouteInfo { #StaticRouteInfo {
name: stringify!(#user_handler_fn_name), name: stringify!(#user_handler_fn_name),
method: #method, method: #method,
path: #path, path: #path,

View File

@ -64,7 +64,34 @@ extern crate proc_macro;
extern crate rocket_http as http; extern crate rocket_http as http;
extern crate indexmap; 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 derive;
mod attribute; mod attribute;
mod bang; mod bang;

View File

@ -0,0 +1,53 @@
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate rocket;
use rocket::local::Client;
#[get("/easy/<id>")]
fn easy(id: i32) -> String {
format!("easy id: {}", id)
}
macro_rules! make_handler {
() => {
#[get("/hard/<id>")]
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");
}

View File

@ -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 = "<user>", rank = 2)]
// fn get(
// _name: &RawStr,
// _query: User,
// user: Form<User>,
// _cookies: Cookies
// ) -> String {
// format!("{}:{}", user.name, user.nickname)
// }
// #[post("/", format = "application/x-custom")]
// fn get() -> &'static str { "hi" }
// #[get("/test/<one>/<two>/<three>")]
// 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("/<todo>")]
// fn todo(todo: String) -> String {
// todo
// }
// #[post("/<a>/<b..>")]
// fn get(a: String, b: PathBuf) -> String {
// format!("{}/{}", a, b.to_string_lossy())
// }
// #[post("/<a>/<b..>")]
// fn get2(a: String, b: Result<PathBuf, SegmentError>) -> String {
// format!("{}/{}", a, b.unwrap().to_string_lossy())
// }
// #[test]
// fn main() {
// let _ = routes![todo];
// }

View File

@ -112,33 +112,3 @@ fn test_full_route() {
assert_eq!(response.body_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})", assert_eq!(response.body_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
sky, name, "A A", "inside", path, simple, expected_uri)); sky, name, "A A", "inside", path, simple, expected_uri));
} }
// Check that we propogate span information correctly to allow re-expansion.
#[get("/easy/<id>")]
fn easy(id: i32) -> String {
format!("easy id: {}", id)
}
macro_rules! make_handler {
() => {
#[get("/hard/<id>")]
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");
}