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 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<TokenStream> {
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<TokenStream> {
#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()
}

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 {
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<TokenStream2> {
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<TokenStream2> {
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<TokenStream2> {
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<TokenStream2> {
(#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<TokenStream2> {
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<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();
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<TokenStream> {
}
// 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<TokenStream> {
/// 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<TokenStream> {
/// 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,

View File

@ -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;

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!("({}, {}, {}, {}, {}, {}) ({})",
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");
}