use proc_macro2::TokenStream; use syn::{ ext::IdentExt, parenthesized, parse::{Parse, ParseStream}, parse2, punctuated::Punctuated, spanned::Spanned, token, Attribute, Expr, Ident, Meta, MetaNameValue, Token, }; use crate::util::NormalizeError; /// A single visit argument passed via `#[visit(args(...))]` #[derive(Debug, Clone)] pub struct VisitArg { pub ident: Ident, pub value: Expr, } impl Parse for VisitArg { fn parse(input: ParseStream) -> Result { let nv: MetaNameValue = input.parse()?; Ok(Self { ident: nv.path.get_ident().map_or_else( || Err(syn::Error::new(nv.span(), "Invalid `visit_args` input!")), |it| Ok(it.clone()), )?, value: nv.value, }) } } /// A struct containing `#[visit(args(...))]` items /// ^^^^^^^^^ #[derive(Debug, Default, Clone)] pub struct VisitArgs(Punctuated); impl IntoIterator for VisitArgs { type Item = VisitArg; type IntoIter = syn::punctuated::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl Parse for VisitArgs { fn parse(input: ParseStream) -> Result { input.parse_terminated(VisitArg::parse, Token![,]).map(Self) } } /// A struct representing `#[visit(...)]` markers #[derive(Default, Debug)] pub struct VisitMarkers { pub visit_as: Option, pub visit_args: Option, pub enter_before: bool, pub ignore: bool, } /// A struct representing `#[scope(...)]` markers #[derive(Debug)] pub struct ScopeMarkers { pub enter_before: bool, } /// A struct representing the `#[scope(...)]` attribute. #[derive(Debug, Default)] pub struct ScopeAttr { pub r#if: Option, pub flags: Option, pub strict_if: Option, } impl Parse for ScopeAttr { fn parse(input: ParseStream) -> Result { let parsed = input.parse_terminated(CommonAttribute::parse, Token![,])?; Ok(parsed.into_iter().fold(Self::default(), |mut acc, CommonAttribute { ident, args }| { let expr = parse2(args).expect("Invalid `#[scope]` input."); match ident.to_string().as_str() { "if" => acc.r#if = Some(expr), "flags" => acc.flags = Some(expr), "strict_if" => acc.strict_if = Some(expr), _ => {} } acc })) } } #[derive(Debug)] struct CommonAttribute { ident: Ident, args: TokenStream, } impl Parse for CommonAttribute { fn parse(input: ParseStream) -> Result { let ident = input.call(Ident::parse_any).unwrap(); let args = if input.peek(token::Paren) || input.peek(token::Bracket) || input.peek(token::Brace) { let content; parenthesized!(content in input); content.parse()? } else { TokenStream::default() }; Ok(CommonAttribute { ident, args }) } } pub fn get_visit_markers<'a, I>(attrs: I) -> Option> where I: IntoIterator, { #[allow(clippy::trivially_copy_pass_by_ref)] fn predicate(it: &&Attribute) -> bool { it.path().is_ident("visit") } let mut iter = attrs.into_iter(); let attr = iter.find(predicate); debug_assert_eq!( iter.find(predicate), None, "For now we only accept one `#[visit]` marker per field/variant, Please merge them together!" ); attr.map(|attr| { let mut visit_as = None; let mut visit_args = None; let mut enter_before = false; let mut ignore = false; let nested = attr.parse_args_with(Punctuated::::parse_terminated); nested .map(|nested| { for com in nested { if com.ident == "args" { visit_args = Some(parse2(com.args).unwrap()); } else if com.ident == "as" { visit_as = Some(parse2(com.args).expect("Invalid `#[visit[as(...)]]` input!")); } else if com.ident == "enter_before" { enter_before = true; } else if com.ident == "ignore" { ignore = true; } else { panic!("Invalid `#[visit(...)]` input!") } } }) .map(|()| VisitMarkers { visit_as, visit_args, enter_before, ignore }) .normalize() }) } pub fn get_scope_markers<'a, I>(attrs: I) -> Option> where I: IntoIterator, { #[allow(clippy::trivially_copy_pass_by_ref)] fn predicate(it: &&Attribute) -> bool { it.path().is_ident("scope") } let mut iter = attrs.into_iter(); let attr = iter.find(predicate); debug_assert_eq!( iter.find(predicate), None, "For now we only accept one `#[scope]` marker per field/variant, Please merge them together!" ); attr.map(|attr| { attr.parse_args_with(Ident::parse) .map(|id| ScopeMarkers { enter_before: id == "enter_before" }) .normalize() }) } pub fn get_scope_attr<'a, I>(attrs: I) -> Option> where I: IntoIterator, { let attr = attrs.into_iter().find(|it| it.path().is_ident("scope")); attr.map(|attr| { debug_assert!(attr.path().is_ident("scope")); let result = if matches!(attr.meta, Meta::Path(_)) { // empty `#[scope]`. Ok(ScopeAttr::default()) } else { attr.parse_args_with(ScopeAttr::parse) }; result.normalize() }) }