Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(ast_codegen): better visit marker parsing. #4371

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
@@ -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> {
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas#arrays>
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<Box<'a, CatchClause<'a>>>,
#[visit_as(FinallyClause)]
#[visit(as(FinallyClause))]
pub finalizer: Option<Box<'a, BlockStatement<'a>>>,
}

@@ -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<BindingIdentifier<'a>>,
#[scope(enter_before)]
pub type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
#[visit_as(ClassHeritage)]
#[visit(as(ClassHeritage))]
pub super_class: Option<Expression<'a>>,
pub super_type_parameters: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
pub implements: Option<Vec<'a, TSClassImplements<'a>>>,
@@ -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,

6 changes: 3 additions & 3 deletions crates/oxc_ast/src/ast/jsx.rs
Original file line number Diff line number Diff line change
@@ -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)]
6 changes: 3 additions & 3 deletions crates/oxc_ast/src/ast/literal.rs
Original file line number Diff line number Diff line change
@@ -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)]
6 changes: 3 additions & 3 deletions crates/oxc_ast/src/ast/ts.rs
Original file line number Diff line number Diff line change
@@ -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)]
43 changes: 19 additions & 24 deletions tasks/ast_codegen/src/generators/ast_kind.rs
Original file line number Diff line number Diff line change
@@ -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!(),
174 changes: 29 additions & 145 deletions tasks/ast_codegen/src/generators/visit.rs
Original file line number Diff line number Diff line change
@@ -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,24 +358,17 @@ 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::<Path>()
.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
} else {
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<Ident> =
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<VisitArg, Token![,]>);

impl IntoIterator for VisitArgs {
type Item = VisitArg;
type IntoIter = syn::punctuated::IntoIter<Self::Item>;
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<Expr>,
flags: Option<Expr>,
strict_if: Option<Expr>,
}

impl Parse for VisitArgs {
fn parse(input: ParseStream) -> std::result::Result<Self, syn::Error> {
input.parse_terminated(VisitArg::parse, Token![,]).map(Self)
}
}

impl Parse for VisitArg {
fn parse(input: ParseStream) -> std::result::Result<Self, syn::Error> {
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<Self, syn::Error> {
fn parse(input: ParseStream) -> std::result::Result<(String, Expr), syn::Error> {
let ident = if let Ok(ident) = input.parse::<Ident>() {
ident.to_string()
} else if input.parse::<Token![if]>().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<ScopeArgs, syn::Error> {
debug_assert!(attr.path().is_ident("scope"));
if matches!(attr.meta, Meta::Path(_)) {
// empty!
Ok(ScopeArgs::default())
} else {
attr.parse_args_with(ScopeArgs::parse)
}
}
1 change: 1 addition & 0 deletions tasks/ast_codegen/src/main.rs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ mod defs;
mod fmt;
mod generators;
mod linker;
mod markers;
mod schema;
mod util;

199 changes: 199 additions & 0 deletions tasks/ast_codegen/src/markers.rs
Original file line number Diff line number Diff line change
@@ -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<Self, syn::Error> {
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<VisitArg, Token![,]>);

impl IntoIterator for VisitArgs {
type Item = VisitArg;
type IntoIter = syn::punctuated::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

impl Parse for VisitArgs {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
input.parse_terminated(VisitArg::parse, Token![,]).map(Self)
}
}

/// A struct representing `#[visit(...)]` markers
#[derive(Debug)]
pub struct VisitMarkers {
pub visit_as: Option<Ident>,
pub visit_args: Option<VisitArgs>,
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<Expr>,
pub flags: Option<Expr>,
pub strict_if: Option<Expr>,
}

impl Parse for ScopeAttr {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
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<Self, syn::Error> {
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<crate::Result<VisitMarkers>>
where
I: IntoIterator<Item = &'a Attribute>,
{
#[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::<CommonAttribute, Token![,]>::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<crate::Result<ScopeMarkers>>
where
I: IntoIterator<Item = &'a Attribute>,
{
#[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<crate::Result<ScopeAttr>>
where
I: IntoIterator<Item = &'a Attribute>,
{
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()
})
}
2 changes: 1 addition & 1 deletion tasks/ast_codegen/src/schema.rs
Original file line number Diff line number Diff line change
@@ -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::<Path>().normalize()?;
if args.is_ident("visit") {
AstAttr::Visit