From 7a3e92591f469b9eca7452e5defda581b4f10cba Mon Sep 17 00:00:00 2001 From: rzvxa <3788964+rzvxa@users.noreply.github.com> Date: Sat, 20 Jul 2024 17:07:24 +0000 Subject: [PATCH] refactor(ast_codegen): better visit marker parsing. (#4371) closes #4281 --- crates/oxc_ast/src/ast/js.rs | 24 +-- crates/oxc_ast/src/ast/jsx.rs | 6 +- crates/oxc_ast/src/ast/literal.rs | 6 +- crates/oxc_ast/src/ast/ts.rs | 6 +- tasks/ast_codegen/src/generators/ast_kind.rs | 43 ++-- tasks/ast_codegen/src/generators/visit.rs | 174 +++------------- tasks/ast_codegen/src/main.rs | 1 + tasks/ast_codegen/src/markers.rs | 199 +++++++++++++++++++ tasks/ast_codegen/src/schema.rs | 2 +- 9 files changed, 270 insertions(+), 191 deletions(-) create mode 100644 tasks/ast_codegen/src/markers.rs diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 7b712f81cff50..85b37fa582f3c 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1,6 +1,6 @@ -// NB: `#[span]`, `#[scope(...)]`, `#[visit(...)]`, `#[visit_as(...)]` and `#[visit_args(...)]` do -// not do anything to the code, They are purely markers for codegen used in -// `tasts/ast_codegen` and `crates/oxc_traverse/scripts`. See docs in that crate. +// NB: `#[span]`, `#[scope(...)]` and `#[visit(...)]` do NOT do anything to the code. +// They are purely markers for codegen used in +// `tasks/ast_codegen` and `crates/oxc_traverse/scripts`. See docs in those crates. // Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]` #![allow(non_snake_case)] @@ -79,7 +79,7 @@ pub enum Expression<'a> { ChainExpression(Box<'a, ChainExpression<'a>>) = 16, ClassExpression(Box<'a, Class<'a>>) = 17, ConditionalExpression(Box<'a, ConditionalExpression<'a>>) = 18, - #[visit_args(flags = ScopeFlags::Function)] + #[visit(args(flags = ScopeFlags::Function))] FunctionExpression(Box<'a, Function<'a>>) = 19, ImportExpression(Box<'a, ImportExpression<'a>>) = 20, LogicalExpression(Box<'a, LogicalExpression<'a>>) = 21, @@ -252,7 +252,7 @@ pub enum ArrayExpressionElement<'a> { /// Elision(Elision) = 65, // `Expression` variants added here by `inherit_variants!` macro - // TODO: support for attributes syntax here so we can use `#[visit_as(ExpressionArrayElement)]` + // TODO: support for attributes syntax here so we can use `#[visit(as(ExpressionArrayElement))]` @inherit Expression } } @@ -966,7 +966,7 @@ pub struct BlockStatement<'a> { #[serde(untagged)] pub enum Declaration<'a> { VariableDeclaration(Box<'a, VariableDeclaration<'a>>) = 32, - #[visit_args(flags = ScopeFlags::Function)] + #[visit(args(flags = ScopeFlags::Function))] FunctionDeclaration(Box<'a, Function<'a>>) = 33, ClassDeclaration(Box<'a, Class<'a>>) = 34, UsingDeclaration(Box<'a, UsingDeclaration<'a>>) = 35, @@ -1292,7 +1292,7 @@ pub struct TryStatement<'a> { pub span: Span, pub block: Box<'a, BlockStatement<'a>>, pub handler: Option>>, - #[visit_as(FinallyClause)] + #[visit(as(FinallyClause))] pub finalizer: Option>>, } @@ -1428,7 +1428,7 @@ pub struct BindingRestElement<'a> { /// Function Definitions #[ast(visit)] #[scope( - // `flags` passed in to visitor via parameter defined by `#[visit_args(flags = ...)]` on parents + // `flags` passed in to visitor via parameter defined by `#[visit(args(flags = ...))]` on parents flags(flags), strict_if(self.is_strict()), )] @@ -1582,7 +1582,7 @@ pub struct Class<'a> { pub id: Option>, #[scope(enter_before)] pub type_parameters: Option>>, - #[visit_as(ClassHeritage)] + #[visit(as(ClassHeritage))] pub super_class: Option>, pub super_type_parameters: Option>>, pub implements: Option>>, @@ -1632,12 +1632,12 @@ pub struct MethodDefinition<'a> { pub span: Span, pub decorators: Vec<'a, Decorator<'a>>, pub key: PropertyKey<'a>, - #[visit_args(flags = match self.kind { + #[visit(args(flags = match self.kind { MethodDefinitionKind::Get => ScopeFlags::Function | ScopeFlags::GetAccessor, MethodDefinitionKind::Set => ScopeFlags::Function | ScopeFlags::SetAccessor, MethodDefinitionKind::Constructor => ScopeFlags::Function | ScopeFlags::Constructor, MethodDefinitionKind::Method => ScopeFlags::Function, - })] + }))] pub value: Box<'a, Function<'a>>, // FunctionExpression pub kind: MethodDefinitionKind, pub computed: bool, @@ -1953,7 +1953,7 @@ inherit_variants! { #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] pub enum ExportDefaultDeclarationKind<'a> { - #[visit_args(flags = ScopeFlags::Function)] + #[visit(args(flags = ScopeFlags::Function))] FunctionDeclaration(Box<'a, Function<'a>>) = 64, ClassDeclaration(Box<'a, Class<'a>>) = 65, diff --git a/crates/oxc_ast/src/ast/jsx.rs b/crates/oxc_ast/src/ast/jsx.rs index 7aa5e3a14d8f7..ba9bbb4228275 100644 --- a/crates/oxc_ast/src/ast/jsx.rs +++ b/crates/oxc_ast/src/ast/jsx.rs @@ -1,8 +1,8 @@ //! [JSX](https://facebook.github.io/jsx) -// NB: `#[span]`, `#[scope(...)]`, `#[visit(...)]`, `#[visit_as(...)]` and `#[visit_args(...)]` do -// not do anything to the code, They are purely markers for codegen used in -// `tasts/ast_codegen` and `crates/oxc_traverse/scripts`. See docs in that crate. +// NB: `#[span]`, `#[scope(...)]` and `#[visit(...)]` do NOT do anything to the code. +// They are purely markers for codegen used in +// `tasks/ast_codegen` and `crates/oxc_traverse/scripts`. See docs in those crates. // Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]` #![allow(non_snake_case)] diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs index 5c4e4ffd59ba3..4dd51ab4b651c 100644 --- a/crates/oxc_ast/src/ast/literal.rs +++ b/crates/oxc_ast/src/ast/literal.rs @@ -1,8 +1,8 @@ //! Literals -// NB: `#[span]`, `#[scope(...)]`, `#[visit(...)]`, `#[visit_as(...)]` and `#[visit_args(...)]` do -// not do anything to the code, They are purely markers for codegen used in -// `tasts/ast_codegen` and `crates/oxc_traverse/scripts`. See docs in that crate. +// NB: `#[span]`, `#[scope(...)]` and `#[visit(...)]` do NOT do anything to the code. +// They are purely markers for codegen used in +// `tasks/ast_codegen` and `crates/oxc_traverse/scripts`. See docs in those crates. // Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]` #![allow(non_snake_case)] diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index 8ea3639613c18..1164906c940b3 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -3,9 +3,9 @@ //! [AST Spec](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/ast-spec) //! [Archived TypeScript spec](https://github.com/microsoft/TypeScript/blob/3c99d50da5a579d9fa92d02664b1b66d4ff55944/doc/spec-ARCHIVED.md) -// NB: `#[span]`, `#[scope(...)]`, `#[visit(...)]`, `#[visit_as(...)]` and `#[visit_args(...)]` do -// not do anything to the code, They are purely markers for codegen used in -// `tasts/ast_codegen` and `crates/oxc_traverse/scripts`. See docs in that crate. +// NB: `#[span]`, `#[scope(...)]` and `#[visit(...)]` do NOT do anything to the code. +// They are purely markers for codegen used in +// `tasks/ast_codegen` and `crates/oxc_traverse/scripts`. See docs in those crates. // Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]` #![allow(non_snake_case)] diff --git a/tasks/ast_codegen/src/generators/ast_kind.rs b/tasks/ast_codegen/src/generators/ast_kind.rs index 4af92ccf2e502..5966dc67735f0 100644 --- a/tasks/ast_codegen/src/generators/ast_kind.rs +++ b/tasks/ast_codegen/src/generators/ast_kind.rs @@ -2,7 +2,10 @@ use itertools::Itertools; use quote::quote; use syn::{parse_quote, Arm, Ident, Type, Variant}; -use crate::{schema::RType, util::TypeExt, CodegenCtx, Generator, GeneratorOutput, TypeRef}; +use crate::{ + markers::get_visit_markers, schema::RType, util::TypeExt, CodegenCtx, Generator, + GeneratorOutput, TypeRef, +}; use super::generated_header; @@ -91,35 +94,27 @@ pub fn process_types(ty: &TypeRef) -> Vec<(Ident, Type)> { .item .variants .iter() - .filter_map(|it| { - it.attrs - .iter() - .find(|it| it.path().is_ident("visit_as")) - .map(|attr| (it, attr)) - .map(|(it, attr)| { - assert!( - it.fields.len() == 1, - "visit_as only supports single argument fields." - ); - let field = it.fields.iter().next().unwrap(); - let type_name = field.ty.get_ident().inner_ident(); - (attr.parse_args().unwrap(), parse_quote!(#type_name<'a>)) - }) + .map(|it| (it, get_visit_markers(&it.attrs).transpose().unwrap())) + .filter(|(_, markers)| markers.as_ref().is_some_and(|mk| mk.visit_as.is_some())) + .filter_map(|(it, markers)| { + markers.map(|markers| { + let field = it.fields.iter().next().unwrap(); + let type_name = field.ty.get_ident().inner_ident(); + (markers.visit_as.expect("Already checked"), parse_quote!(#type_name<'a>)) + }) }) .collect_vec(), RType::Struct(struct_) => struct_ .item .fields .iter() - .filter_map(|it| { - it.attrs - .iter() - .find(|it| it.path().is_ident("visit_as")) - .map(|attr| (it, attr)) - .map(|(field, attr)| { - let type_name = field.ty.get_ident().inner_ident(); - (attr.parse_args().unwrap(), parse_quote!(#type_name<'a>)) - }) + .map(|it| (it, get_visit_markers(&it.attrs).transpose().unwrap())) + .filter(|(_, markers)| markers.as_ref().is_some_and(|mk| mk.visit_as.is_some())) + .filter_map(|(field, markers)| { + markers.map(|markers| { + let type_name = field.ty.get_ident().inner_ident(); + (markers.visit_as.expect("Already checked"), parse_quote!(#type_name<'a>)) + }) }) .collect_vec(), _ => panic!(), diff --git a/tasks/ast_codegen/src/generators/visit.rs b/tasks/ast_codegen/src/generators/visit.rs index a912c5941022c..b0aa57176e0f4 100644 --- a/tasks/ast_codegen/src/generators/visit.rs +++ b/tasks/ast_codegen/src/generators/visit.rs @@ -21,6 +21,10 @@ use syn::{ use crate::{ generators::{ast_kind::BLACK_LIST as KIND_BLACK_LIST, insert}, + markers::{ + get_scope_attr, get_scope_markers, get_visit_markers, ScopeMarkers, VisitArg, VisitArgs, + VisitMarkers, + }, schema::{Inherit, REnum, RStruct, RType}, util::{StrExt, TokenStreamExt, TypeExt, TypeIdentResult, TypeWrapper}, CodegenCtx, Generator, GeneratorOutput, Result, TypeRef, @@ -354,16 +358,9 @@ impl<'a> VisitBuilder<'a> { .variants .iter() .filter(|it| !it.attrs.iter().any(|a| a.path().is_ident("inherit"))) - .filter(|it| { - if it.attrs.iter().any(|a| { - a.path().is_ident("visit") - && a.meta - .require_list() - .unwrap() - .parse_args::() - .unwrap() - .is_ident("ignore") - }) { + .map(|it| (it, get_visit_markers(&it.attrs).transpose().unwrap())) + .filter(|(_, markers)| { + if markers.as_ref().is_some_and(|mk| mk.ignore) { // We are ignoring some variants so the match is no longer exhaustive. non_exhaustive = true; false @@ -371,7 +368,7 @@ impl<'a> VisitBuilder<'a> { true } }) - .filter_map(|it| { + .filter_map(|(it, markers)| { let typ = it .fields .iter() @@ -385,17 +382,11 @@ impl<'a> VisitBuilder<'a> { let visitable = borrowed.visitable(); if visitable { let visit = self.get_visitor(&typ, false, None); - let (args_def, args) = it - .attrs - .iter() - .find(|it| it.path().is_ident("visit_args")) - .map(|it| it.parse_args_with(VisitArgs::parse)) - .map(|it| { - it.into_iter() - .flatten() - .fold((Vec::new(), Vec::new()), Self::visit_args_fold) - }) - .unwrap_or_default(); + let (args_def, args) = markers + .map(|mk| mk.visit_args.unwrap_or_default()) + .into_iter() + .flatten() + .fold((Vec::new(), Vec::new()), Self::visit_args_fold); let body = quote!(visitor.#visit(it #(#args)*)); let body = if args_def.is_empty() { body @@ -471,10 +462,10 @@ impl<'a> VisitBuilder<'a> { visit_as: Option<&Ident>, ) -> (TokenStream, /* inline */ bool) { let ident = visit_as.unwrap_or_else(|| struct_.ident()); - let scope_attr = struct_.item.attrs.iter().find(|it| it.path().is_ident("scope")); - let scope_events = scope_attr.map(parse_as_scope).transpose().unwrap().map_or_else( - Default::default, - |scope_args| { + let scope_events = get_scope_attr(struct_.item.attrs.iter()) + .transpose() + .unwrap() + .map_or_else(Default::default, |scope_args| { let cond = scope_args.r#if.map(|cond| { let cond = cond.to_token_stream().replace_ident("self", &format_ident!("it")); quote!(let scope_events_cond = #cond;) @@ -510,8 +501,7 @@ impl<'a> VisitBuilder<'a> { enter.extend(maybe_conditional(quote!(visitor.enter_scope(#flags, &it.scope_id);))); let leave = maybe_conditional(quote!(visitor.leave_scope();)); (enter, leave) - }, - ); + }); let node_events = if KIND_BLACK_LIST.contains(&ident.to_string().as_str()) { ( @@ -539,40 +529,25 @@ impl<'a> VisitBuilder<'a> { .fields .iter() .enumerate() - .filter_map(|(ix, it)| { + .map(|(ix, it)| (ix, it, get_visit_markers(&it.attrs).transpose())) + .filter_map(|(ix, it, markers)| { let ty_res = it.ty.analyze(self.ctx); let typ = ty_res.type_ref?; if !typ.borrow().visitable() { return None; } let typ_wrapper = ty_res.wrapper; - let visit_as: Option = - it.attrs.iter().find(|it| it.path().is_ident("visit_as")).map(|it| { - match &it.meta { - Meta::List(meta) => { - parse2(meta.tokens.clone()).expect("wrong `visit_as` input!") - } - _ => panic!("wrong use of `visit_as`!"), - } - }); + let markers = markers.unwrap(); + let visit_as = markers.as_ref().and_then(|mk| mk.visit_as.clone()); + let visit_args = markers.and_then(|mk| mk.visit_args); - let have_enter_scope = it.attrs.iter().any(|it| { - it.path().is_ident("scope") - && it.parse_args_with(Ident::parse).is_ok_and(|id| id == "enter_before") - }); - let have_enter_node = it.attrs.iter().any(|it| { - it.path().is_ident("visit") - && it.parse_args_with(Ident::parse).is_ok_and(|id| id == "enter_before") - }); + let have_enter_scope = get_scope_markers(&it.attrs) + .is_some_and(|it| matches!(it, Ok(ScopeMarkers { enter_before: true }))); + let have_enter_node = get_visit_markers(&it.attrs) + .is_some_and(|it| matches!(it, Ok(VisitMarkers { enter_before: true, .. }))); - let args = it.attrs.iter().find(|it| it.meta.path().is_ident("visit_args")); - let (args_def, args) = args - .map(|it| it.parse_args_with(VisitArgs::parse)) - .map(|it| { - it.into_iter() - .flatten() - .fold((Vec::new(), Vec::new()), Self::visit_args_fold) - }) + let (args_def, args) = visit_args + .map(|it| it.into_iter().fold((Vec::new(), Vec::new()), Self::visit_args_fold)) .unwrap_or_default(); let visit = self.get_visitor( &typ, @@ -680,94 +655,3 @@ impl<'a> VisitBuilder<'a> { accumulator } } - -#[derive(Debug)] -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() - } -} - -#[derive(Debug)] -struct VisitArg { - ident: Ident, - value: Expr, -} - -#[derive(Debug, Default)] -struct ScopeArgs { - r#if: Option, - flags: Option, - strict_if: Option, -} - -impl Parse for VisitArgs { - fn parse(input: ParseStream) -> std::result::Result { - input.parse_terminated(VisitArg::parse, Token![,]).map(Self) - } -} - -impl Parse for VisitArg { - fn parse(input: ParseStream) -> std::result::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, - }) - } -} - -impl Parse for ScopeArgs { - fn parse(input: ParseStream) -> std::result::Result { - fn parse(input: ParseStream) -> std::result::Result<(String, Expr), syn::Error> { - let ident = if let Ok(ident) = input.parse::() { - ident.to_string() - } else if input.parse::().is_ok() { - String::from("if") - } else { - return Err(syn::Error::new(input.span(), "Invalid `#[scope]` input.")); - }; - let content; - parenthesized!(content in input); - Ok((ident, content.parse()?)) - } - - let parsed = input.parse_terminated(parse, Token![,])?; - Ok(parsed.into_iter().fold(Self::default(), |mut acc, (ident, expr)| { - match ident.as_str() { - "if" => acc.r#if = Some(expr), - "flags" => acc.flags = Some(expr), - "strict_if" => acc.strict_if = Some(expr), - _ => {} - } - acc - })) - } -} - -fn parse_as_visit_args(attr: &Attribute) -> Vec<(Ident, TokenStream)> { - debug_assert!(attr.path().is_ident("visit_args")); - let mut result = Vec::new(); - let args: MetaNameValue = attr.parse_args().expect("Invalid `visit_args` input!"); - let ident = args.path.get_ident().unwrap().clone(); - let value = args.value.to_token_stream(); - result.push((ident, value)); - result -} - -fn parse_as_scope(attr: &Attribute) -> std::result::Result { - debug_assert!(attr.path().is_ident("scope")); - if matches!(attr.meta, Meta::Path(_)) { - // empty! - Ok(ScopeArgs::default()) - } else { - attr.parse_args_with(ScopeArgs::parse) - } -} diff --git a/tasks/ast_codegen/src/main.rs b/tasks/ast_codegen/src/main.rs index 80109fa16c3bd..1a5919849fe5d 100644 --- a/tasks/ast_codegen/src/main.rs +++ b/tasks/ast_codegen/src/main.rs @@ -4,6 +4,7 @@ mod defs; mod fmt; mod generators; mod linker; +mod markers; mod schema; mod util; diff --git a/tasks/ast_codegen/src/markers.rs b/tasks/ast_codegen/src/markers.rs new file mode 100644 index 0000000000000..2960a908497a5 --- /dev/null +++ b/tasks/ast_codegen/src/markers.rs @@ -0,0 +1,199 @@ +use proc_macro2::{Delimiter, TokenStream, TokenTree}; +use syn::{ + ext::IdentExt, + parenthesized, + parse::{Parse, ParseStream}, + parse2, + punctuated::Punctuated, + spanned::Spanned, + token::{self, Brace, Bracket, Paren}, + Attribute, Expr, Ident, MacroDelimiter, Meta, MetaList, MetaNameValue, Token, +}; + +use crate::util::NormalizeError; + +/// A single visit argument passed via `#[visit(args(...))]` +#[derive(Debug)] +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)] +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(Debug)] +pub struct VisitMarkers { + pub visit_as: Option, + pub visit_args: Option, + pub enter_before: bool, + pub ignore: bool, +} + +/// A struct representing `#[scope(...)]` markers +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() + }) +} diff --git a/tasks/ast_codegen/src/schema.rs b/tasks/ast_codegen/src/schema.rs index c3f3ef54d7190..a03d7c8f73c0e 100644 --- a/tasks/ast_codegen/src/schema.rs +++ b/tasks/ast_codegen/src/schema.rs @@ -360,7 +360,7 @@ pub fn analyze(type_def: &TypeRef) -> Result<()> { let attr = match attr { Some(Attribute { meta: Meta::Path(_), .. }) => AstAttr::Mark, Some(attr @ Attribute { meta: Meta::List(_), .. }) => { - // TODO: support for punctuated list of arguments here! + // TODO: support for punctuated list of arguments here if needed! let args = attr.parse_args::().normalize()?; if args.is_ident("visit") { AstAttr::Visit