Streamline raw identifier support in codegen.

This commit is contained in:
Sergio Benitez 2020-11-03 00:44:28 -08:00
parent 97f6bc5dc0
commit 86ff66a69c
8 changed files with 47 additions and 45 deletions

View File

@ -45,9 +45,9 @@ struct Route {
function: syn::ItemFn,
/// The non-static parameters declared in the route segments.
segments: IndexSet<Segment>,
/// The parsed inputs to the user's function. The first ident is the ident
/// as the user wrote it, while the second ident is the identifier that
/// should be used during code generation, the `rocket_ident`.
/// The parsed inputs to the user's function. The name is the param as the
/// user wrote it, while the ident is the identifier that should be used
/// during code generation, the `rocket_ident`.
inputs: Vec<(NameSource, syn::Ident, syn::Type)>,
}
@ -74,7 +74,7 @@ fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
for segment in iter.filter(|s| s.is_dynamic()) {
let span = segment.span;
if let Some(previous) = set.replace(segment.clone()) {
diags.push(span.error(format!("duplicate parameter: `{}`", previous.name.name()))
diags.push(span.error(format!("duplicate parameter: `{}`", previous.name))
.span_note(previous.span, "previous parameter with the same name here"))
}
}
@ -118,7 +118,7 @@ fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
let span = function.sig.paren_token.span;
for missing in segments.difference(&fn_segments) {
diags.push(missing.span.error("unused dynamic parameter")
.span_note(span, format!("expected argument named `{}` here", missing.name.name())))
.span_note(span, format!("expected argument named `{}` here", missing.name)))
}
diags.head_err_or(Route { attribute: attr, function, inputs, segments })
@ -207,7 +207,6 @@ fn query_exprs(route: &Route) -> Option<TokenStream> {
let query_segments = route.attribute.path.query.as_ref()?;
let (mut decls, mut matchers, mut builders) = (vec![], vec![], vec![]);
for segment in query_segments {
let name = segment.name.name();
let (ident, ty, span) = if segment.kind != Kind::Static {
let (ident, ty) = route.inputs.iter()
.find(|(name, _, _)| name == &segment.name)
@ -232,6 +231,7 @@ fn query_exprs(route: &Route) -> Option<TokenStream> {
Kind::Static => quote!()
};
let name = segment.name.name();
let matcher = match segment.kind {
Kind::Single => quote_spanned! { span =>
(_, #name, __v) => {
@ -327,8 +327,9 @@ fn generate_internal_uri_macro(route: &Route) -> TokenStream {
.filter(|seg| seg.source == Source::Path || seg.source == Source::Query)
.filter(|seg| seg.kind != Kind::Static)
.map(|seg| &seg.name)
.map(|name| route.inputs.iter().find(|(name2, ..)| name2 == name).unwrap())
.map(|(name, _, ty)| { let id = name.ident().unwrap(); quote!(#id: #ty) });
.map(|seg_name| route.inputs.iter().find(|(in_name, ..)| in_name == seg_name).unwrap())
.map(|(name, _, ty)| (name.ident(), ty))
.map(|(ident, ty)| quote!(#ident: #ty));
let mut hasher = DefaultHasher::new();
route.function.sig.ident.hash(&mut hasher);
@ -386,7 +387,7 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
let mut req_guard_definitions = vec![];
let mut parameter_definitions = vec![];
for (name, rocket_ident, ty) in &route.inputs {
let fn_segment: Segment = name.ident().unwrap().into();
let fn_segment: Segment = name.ident().into();
match route.segments.get(&fn_segment) {
Some(seg) if seg.source == Source::Path => {
parameter_definitions.push(param_expr(seg, rocket_ident, &ty));

View File

@ -36,7 +36,7 @@ impl Segment {
};
let (kind, index) = (segment.kind, segment.index);
Segment { span, kind, source, index, name: NameSource::from(segment.name.to_string()) }
Segment { span, kind, source, index, name: NameSource::new(&segment.name, span) }
}
pub fn is_wild(&self) -> bool {

View File

@ -231,9 +231,9 @@ impl InternalUriParams {
let (mut extra, mut dup) = (vec![], vec![]);
for (name, expr) in args.named().unwrap() {
match params.get_mut(name) {
Some(ref entry) if entry.is_some() => dup.push(name.ident().unwrap()),
Some(ref entry) if entry.is_some() => dup.push(name.ident()),
Some(entry) => *entry = Some(expr),
None => extra.push(name.ident().unwrap()),
None => extra.push(name.ident()),
}
}
@ -339,7 +339,10 @@ impl ToTokens for Arg {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Arg::Unnamed(e) => e.to_tokens(tokens),
Arg::Named(name, eq, expr) => { let id = name.ident().unwrap(); tokens.extend(quote!(#id #eq #expr)) },
Arg::Named(name, eq, expr) => {
let ident = name.ident();
tokens.extend(quote!(#ident #eq #expr))
}
}
}
}

View File

@ -87,10 +87,10 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
define_vars_and_mods!(_None, _Some, _Ok, _Err);
let (constructors, matchers, builders) = fields.iter().map(|field| {
let (ident, span) = (&field.ident, field.span());
let default_name_source = NameSource::from(ident.clone().expect("named"));
let name_source = Form::from_attrs("form", &field.attrs)
let default_name = NameSource::from(ident.clone().expect("named"));
let name = Form::from_attrs("form", &field.attrs)
.map(|result| result.map(|form| form.field.name))
.unwrap_or_else(|| Ok(default_name_source))?;
.unwrap_or_else(|| Ok(default_name))?;
let ty = field.ty.with_stripped_lifetimes();
let ty = quote_spanned! {
@ -99,7 +99,7 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
let constructor = quote_spanned!(span => let mut #ident = #_None;);
let name = name_source.name();
let name = name.name();
let matcher = quote_spanned! { span =>
#name => { #ident = #_Some(#ty::from_form_value(__v)
.map_err(|_| #form_error::BadValue(__k, __v))?); },

View File

@ -48,7 +48,7 @@ pub fn derive_from_form_value(input: proc_macro::TokenStream) -> TokenStream {
let variant_str = variant_name_source.name();
let builder = variant.builder(|_| unreachable!());
let builder = variant.builder(|_| unreachable!("no fields"));
Ok(quote! {
if uncased == #variant_str {
return #_Ok(#builder);

View File

@ -62,7 +62,6 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
.unwrap_or_else(|| Ok(ident.clone().into()))?;
let name = name_source.name();
quote_spanned!(span => f.write_named_value(#name, &#accessor)?;)
} else {
quote_spanned!(span => f.write_value(&#accessor)?;)

View File

@ -44,9 +44,9 @@ impl TokenStreamExt for crate::proc_macro2::TokenStream {
}
}
/// Represents the source of a name; usually either a string or an Ident. It is
/// normally constructed using FromMeta, From<String>, or From<Ident> depending
/// on the source.
/// Represents the source of a name read by codegen, which may or may not be a
/// valid identifier. A `NameSource` is typically constructed indirectly via
/// FromMeta, or From<Ident> or directly from a string via `NameSource::new()`.
///
/// NameSource implements Hash, PartialEq, and Eq, and additionally PartialEq<S>
/// for all types `S: AsStr<str>`. These implementations all compare the value
@ -58,24 +58,34 @@ pub struct NameSource {
}
impl NameSource {
/// Returns the name as a string. Notably, if this NameSource was
/// constructed from an Ident this method returns a name *without* an `r#`
/// prefix.
/// Creates a new `NameSource` from the string `name` and span `span`. If
/// `name` is a valid ident, the ident is stored as well.
pub fn new<S: AsRef<str>>(name: S, span: crate::proc_macro2::Span) -> Self {
let name = name.as_ref();
syn::parse_str::<Ident>(name)
.map(|mut ident| { ident.set_span(span); ident })
.map(|ident| NameSource::from(ident))
.unwrap_or_else(|_| NameSource { name: name.into(), ident: None })
}
/// Returns the name as a string. Notably, if `self` was constructed from an
/// Ident this method returns a name *without* an `r#` prefix.
pub fn name(&self) -> &str {
&self.name
}
/// Returns the Ident this NameSource was originally constructed from,
/// if applicable.
pub fn ident(&self) -> Option<&Ident> {
self.ident.as_ref()
/// Returns the Ident corresponding to `self`, if any, otherwise panics. If
/// `self` was constructed from an `Ident`, this never panics. Otherwise,
/// panics if the string `self` was constructed from was not a valid ident.
pub fn ident(&self) -> &Ident {
self.ident.as_ref().expect("ident from namesource")
}
}
impl devise::FromMeta for NameSource {
fn from_meta(meta: devise::MetaItem<'_>) -> devise::Result<Self> {
if let syn::Lit::Str(s) = meta.lit()? {
return Ok(Self { name: s.value(), ident: None });
return Ok(NameSource::new(s.value(), s.span()));
}
Err(meta.value_span().error("invalid value: expected string literal"))
@ -84,25 +94,13 @@ impl devise::FromMeta for NameSource {
impl From<Ident> for NameSource {
fn from(ident: Ident) -> Self {
Self {
name: ident.unraw().to_string(),
ident: Some(ident),
}
}
}
impl From<String> for NameSource {
fn from(string: String) -> Self {
Self {
name: string,
ident: None,
}
Self { name: ident.unraw().to_string(), ident: Some(ident), }
}
}
impl std::hash::Hash for NameSource {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
self.name.hash(hasher)
self.name().hash(hasher)
}
}

View File

@ -24,7 +24,8 @@ fn test_raw_ident() {
let rocket = rocket::ignite()
.mount("/", routes![get, swap])
.register(catchers![catch]);
let client = Client::new(rocket).unwrap();
let client = Client::untracked(rocket).unwrap();
let response = client.get("/example?type=1").dispatch();
assert_eq!(response.into_string().unwrap(), "example is 1");