Port other proc-macros to new attribute parsing (#1372)

This commit is contained in:
David Pedersen 2022-09-12 21:26:10 +02:00 committed by GitHub
parent 8da69a98fc
commit 2abda4de88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 61 deletions

View File

@ -1,5 +1,8 @@
use quote::ToTokens; use quote::ToTokens;
use syn::parse::{Parse, ParseStream}; use syn::{
parse::{Parse, ParseStream},
Token,
};
pub(crate) fn parse_parenthesized_attribute<K, T>( pub(crate) fn parse_parenthesized_attribute<K, T>(
input: ParseStream, input: ParseStream,
@ -26,6 +29,29 @@ where
Ok(()) Ok(())
} }
pub(crate) fn parse_assignment_attribute<K, T>(
input: ParseStream,
out: &mut Option<(K, T)>,
) -> syn::Result<()>
where
K: Parse + ToTokens,
T: Parse,
{
let kw = input.parse()?;
input.parse::<Token![=]>()?;
let inner = input.parse()?;
if out.is_some() {
let kw_name = std::any::type_name::<K>().split("::").last().unwrap();
let msg = format!("`{}` specified more than once", kw_name);
return Err(syn::Error::new_spanned(kw, msg));
}
*out = Some((kw, inner));
Ok(())
}
pub(crate) trait Combine: Sized { pub(crate) trait Combine: Sized {
fn combine(self, other: Self) -> syn::Result<Self>; fn combine(self, other: Self) -> syn::Result<Self>;
} }

View File

@ -1,10 +1,21 @@
use crate::with_position::{Position, WithPosition}; use crate::{
attr_parsing::{parse_assignment_attribute, second},
with_position::{Position, WithPosition},
};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned}; use quote::{format_ident, quote, quote_spanned};
use std::collections::HashSet; use std::collections::HashSet;
use syn::{parse::Parse, spanned::Spanned, FnArg, ItemFn, Token, Type}; use syn::{parse::Parse, parse_quote, spanned::Spanned, FnArg, ItemFn, Token, Type};
pub(crate) fn expand(attr: Attrs, item_fn: ItemFn) -> TokenStream {
let Attrs { body_ty, state_ty } = attr;
let body_ty = body_ty
.map(second)
.unwrap_or_else(|| parse_quote!(axum::body::Body));
let mut state_ty = state_ty.map(second);
pub(crate) fn expand(mut attr: Attrs, item_fn: ItemFn) -> TokenStream {
let check_extractor_count = check_extractor_count(&item_fn); let check_extractor_count = check_extractor_count(&item_fn);
let check_path_extractor = check_path_extractor(&item_fn); let check_path_extractor = check_path_extractor(&item_fn);
let check_output_impls_into_response = check_output_impls_into_response(&item_fn); let check_output_impls_into_response = check_output_impls_into_response(&item_fn);
@ -12,14 +23,14 @@ pub(crate) fn expand(mut attr: Attrs, item_fn: ItemFn) -> TokenStream {
// If the function is generic, we can't reliably check its inputs or whether the future it // If the function is generic, we can't reliably check its inputs or whether the future it
// returns is `Send`. Skip those checks to avoid unhelpful additional compiler errors. // returns is `Send`. Skip those checks to avoid unhelpful additional compiler errors.
let check_inputs_and_future_send = if item_fn.sig.generics.params.is_empty() { let check_inputs_and_future_send = if item_fn.sig.generics.params.is_empty() {
if attr.state_ty.is_none() { if state_ty.is_none() {
attr.state_ty = state_type_from_args(&item_fn); state_ty = state_type_from_args(&item_fn);
} }
let state_ty = attr.state_ty.unwrap_or_else(|| syn::parse_quote!(())); let state_ty = state_ty.unwrap_or_else(|| syn::parse_quote!(()));
let check_inputs_impls_from_request = let check_inputs_impls_from_request =
check_inputs_impls_from_request(&item_fn, &attr.body_ty, state_ty); check_inputs_impls_from_request(&item_fn, &body_ty, state_ty);
let check_future_send = check_future_send(&item_fn); let check_future_send = check_future_send(&item_fn);
quote! { quote! {
@ -49,8 +60,8 @@ mod kw {
} }
pub(crate) struct Attrs { pub(crate) struct Attrs {
body_ty: Type, body_ty: Option<(kw::body, Type)>,
state_ty: Option<Type>, state_ty: Option<(kw::state, Type)>,
} }
impl Parse for Attrs { impl Parse for Attrs {
@ -60,27 +71,10 @@ impl Parse for Attrs {
while !input.is_empty() { while !input.is_empty() {
let lh = input.lookahead1(); let lh = input.lookahead1();
if lh.peek(kw::body) { if lh.peek(kw::body) {
let kw = input.parse::<kw::body>()?; parse_assignment_attribute(input, &mut body_ty)?;
if body_ty.is_some() {
return Err(syn::Error::new_spanned(
kw,
"`body` specified more than once",
));
}
input.parse::<Token![=]>()?;
body_ty = Some(input.parse()?);
} else if lh.peek(kw::state) { } else if lh.peek(kw::state) {
let kw = input.parse::<kw::state>()?; parse_assignment_attribute(input, &mut state_ty)?;
if state_ty.is_some() {
return Err(syn::Error::new_spanned(
kw,
"`state` specified more than once",
));
}
input.parse::<Token![=]>()?;
state_ty = Some(input.parse()?);
} else { } else {
return Err(lh.error()); return Err(lh.error());
} }
@ -88,8 +82,6 @@ impl Parse for Attrs {
let _ = input.parse::<Token![,]>(); let _ = input.parse::<Token![,]>();
} }
let body_ty = body_ty.unwrap_or_else(|| syn::parse_quote!(axum::body::Body));
Ok(Self { body_ty, state_ty }) Ok(Self { body_ty, state_ty })
} }
} }

View File

@ -2,6 +2,8 @@ use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned}; use quote::{format_ident, quote, quote_spanned};
use syn::{parse::Parse, ItemStruct, LitStr, Token}; use syn::{parse::Parse, ItemStruct, LitStr, Token};
use crate::attr_parsing::{combine_attribute, parse_parenthesized_attribute, second, Combine};
pub(crate) fn expand(item_struct: ItemStruct) -> syn::Result<TokenStream> { pub(crate) fn expand(item_struct: ItemStruct) -> syn::Result<TokenStream> {
let ItemStruct { let ItemStruct {
attrs, attrs,
@ -18,7 +20,16 @@ pub(crate) fn expand(item_struct: ItemStruct) -> syn::Result<TokenStream> {
)); ));
} }
let Attrs { path, rejection } = parse_attrs(attrs)?; let Attrs { path, rejection } = crate::attr_parsing::parse_attrs("typed_path", attrs)?;
let path = path.ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"Missing path: `#[typed_path(\"/foo/bar\")]`",
)
})?;
let rejection = rejection.map(second);
match fields { match fields {
syn::Fields::Named(_) => { syn::Fields::Named(_) => {
@ -37,52 +48,49 @@ mod kw {
syn::custom_keyword!(rejection); syn::custom_keyword!(rejection);
} }
#[derive(Default)]
struct Attrs { struct Attrs {
path: LitStr, path: Option<LitStr>,
rejection: Option<syn::Path>, rejection: Option<(kw::rejection, syn::Path)>,
} }
impl Parse for Attrs { impl Parse for Attrs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let path = input.parse()?; let mut path = None;
let mut rejection = None;
let rejection = if input.is_empty() { while !input.is_empty() {
None let lh = input.lookahead1();
} else { if lh.peek(LitStr) {
let _: Token![,] = input.parse()?; path = Some(input.parse()?);
let _: kw::rejection = input.parse()?; } else if lh.peek(kw::rejection) {
parse_parenthesized_attribute(input, &mut rejection)?;
} else {
return Err(lh.error());
}
let content; let _ = input.parse::<Token![,]>();
syn::parenthesized!(content in input); }
Some(content.parse()?)
};
Ok(Self { path, rejection }) Ok(Self { path, rejection })
} }
} }
fn parse_attrs(attrs: &[syn::Attribute]) -> syn::Result<Attrs> { impl Combine for Attrs {
let mut out = None; fn combine(mut self, other: Self) -> syn::Result<Self> {
let Self { path, rejection } = other;
for attr in attrs { if let Some(path) = path {
if attr.path.is_ident("typed_path") { if self.path.is_some() {
if out.is_some() {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
attr, path,
"`typed_path` specified more than once", "path specified more than once",
)); ));
} else {
out = Some(attr.parse_args()?);
} }
self.path = Some(path);
} }
combine_attribute(&mut self.rejection, rejection)?;
Ok(self)
} }
out.ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"missing `#[typed_path(\"...\")]` attribute",
)
})
} }
fn expand_named_fields( fn expand_named_fields(