mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
refactor(ast_codegen): better visit marker parsing. (#4371)
closes #4281
This commit is contained in:
parent
d213773e38
commit
7a3e92591f
9 changed files with 270 additions and 191 deletions
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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!(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ mod defs;
|
|||
mod fmt;
|
||||
mod generators;
|
||||
mod linker;
|
||||
mod markers;
|
||||
mod schema;
|
||||
mod util;
|
||||
|
||||
|
|
|
|||
199
tasks/ast_codegen/src/markers.rs
Normal file
199
tasks/ast_codegen/src/markers.rs
Normal 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()
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue