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

closes #4281
This commit is contained in:
rzvxa 2024-07-20 17:07:24 +00:00
parent d213773e38
commit 7a3e92591f
9 changed files with 270 additions and 191 deletions

View file

@ -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,

View file

@ -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)]

View file

@ -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)]

View file

@ -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)]

View file

@ -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!(),

View file

@ -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::<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
@ -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<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)
}
}

View file

@ -4,6 +4,7 @@ mod defs;
mod fmt;
mod generators;
mod linker;
mod markers;
mod schema;
mod util;

View file

@ -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()
})
}

View file

@ -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