oxc/tasks/ast_codegen/src/markers.rs
rzvxa 1690a57ed3 refactor(ast_codegen): move away from RType in generators. (#4682)
This PR introduces `EarlyCtx` and `LateCtx` in place of the old `CodegenCtx`, Early passes operate at the AST level while generators and other late passes operate on the schema.

It will also replace the confusing `RType` name with something more idiomatic ~~(open for suggestions, I haven't found a good name yet)~~ I've named it `AstType` and dropped the `R` prefix for `REnum` and `RStruct`.

There are some qualities of life improvements too, Things like `to_type_elide` can be used to simplify the code.

Related to #4442 (and can potentially mark it "close as fixed").
2024-08-06 17:14:31 +00:00

199 lines
5.9 KiB
Rust

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<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, Clone)]
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(Default, 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
#[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<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()
})
}