From 9072f4cb0e20c5c96dcf4de153da0a77e3192382 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Tue, 4 Feb 2025 02:48:15 +0000 Subject: [PATCH] feat(ast_tools): allow defining foreign types with `#[ast(foreign = ...)]` (#8866) Allow informing `oxc_ast_tools` about foreign types in AST (e.g. `NonMaxU32`) and other types which it otherwise can't parse (e.g. `RegExpFlags`, which is generated by `bitflags!` macro). Mechanism is `#[ast(foreign = ...)]` attr on another type which acts as a "stand-in" for the real one, similar to `serde`'s [remote derive feature](https://serde.rs/remote-derive.html). ```rs bitflags! { pub struct RegExpFlags: u8 { /* ... */ } } /// This dummy type tells `oxc_ast_tools` about `RegExpFlags` #[ast(foreign = RegExpFlags)] struct RegExpFlagsAlias(u8); ``` --- tasks/ast_tools/src/parse/load.rs | 87 ++++++++++++++++++++++-------- tasks/ast_tools/src/parse/parse.rs | 4 ++ 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/tasks/ast_tools/src/parse/load.rs b/tasks/ast_tools/src/parse/load.rs index fe94a879131e0..c1d563638aae0 100644 --- a/tasks/ast_tools/src/parse/load.rs +++ b/tasks/ast_tools/src/parse/load.rs @@ -6,14 +6,15 @@ use syn::{ parse::{Parse, ParseBuffer}, parse_file, punctuated::Punctuated, - Attribute, Generics, Ident, Item, ItemEnum, ItemMacro, ItemStruct, Token, Variant, Visibility, - WhereClause, + Attribute, Generics, Ident, Item, ItemEnum, ItemMacro, ItemStruct, Meta, Token, Variant, + Visibility, WhereClause, }; use crate::schema::FileId; use super::{ ident_name, + parse::convert_expr_to_string, skeleton::{EnumSkeleton, Skeleton, StructSkeleton}, FxIndexMap, }; @@ -59,20 +60,12 @@ pub fn load_file(file_id: FileId, file_path: &str, skeletons: &mut FxIndexMap Option { - if !has_ast_attr(&item.attrs) { - return None; - } - - let name = ident_name(&item.ident); + let name = get_type_name(&item.attrs, &item.ident)?; Some(StructSkeleton { name, file_id, item }) } fn parse_enum(item: ItemEnum, file_id: FileId) -> Option { - if !has_ast_attr(&item.attrs) { - return None; - } - - let name = ident_name(&item.ident); + let name = get_type_name(&item.attrs, &item.ident)?; Some(EnumSkeleton { name, file_id, item, inherits: vec![] }) } @@ -96,15 +89,13 @@ fn parse_macro(item: &ItemMacro, file_id: FileId) -> Option { let ident = input.parse::()?; let generics = input.parse::()?; - let name = ident_name(&ident); - let where_clause = input.parse::>()?; assert!(where_clause.is_none(), "Types with `where` clauses are not supported"); - assert!( - has_ast_attr(&attrs), - "Enum in `inherit_variants!` macro must have `#[ast]` attr: {name}", - ); + let name = get_type_name(&attrs, &ident); + let Some(name) = name else { + panic!("Enum in `inherit_variants!` macro must have `#[ast]` attr: {ident}"); + }; let content; let brace_token = braced!(content in input); @@ -133,7 +124,61 @@ fn parse_macro(item: &ItemMacro, file_id: FileId) -> Option { Some(skeleton) } -/// Returns `true` if type has an `#[ast]` attribute on it. -fn has_ast_attr(attrs: &[Attribute]) -> bool { - attrs.iter().any(|attr| attr.path().is_ident("ast")) +/// Get name of type. +/// +/// Parse attributes and find `#[ast]` attr, or `#[ast(foreign = ForeignType)]`. +/// +/// If no `#[ast]` attr is present, returns `None`. +/// +/// Otherwise, returns foreign name if provided with `#[ast(foreign = ForeignType)]`, +/// or otherwise name of the `ident`. +/// +/// # Panics +/// Panics if cannot parse `#[ast]` attribute. +fn get_type_name(attrs: &[Attribute], ident: &Ident) -> Option { + let mut has_ast_attr = false; + let mut foreign_name = None; + for attr in attrs { + if attr.path().is_ident("ast") { + has_ast_attr = true; + if let Some(this_foreign_name) = parse_ast_attr_foreign_name(attr, ident) { + assert!( + foreign_name.is_none(), + "Multiple `#[ast(foreign)]` attributes on type: `{ident}`" + ); + foreign_name = Some(this_foreign_name); + } + } + } + + if has_ast_attr { + Some(foreign_name.unwrap_or_else(|| ident_name(ident))) + } else { + None + } +} + +fn parse_ast_attr_foreign_name(attr: &Attribute, ident: &Ident) -> Option { + let meta_list = match &attr.meta { + Meta::Path(_) => return None, + Meta::List(meta_list) => meta_list, + Meta::NameValue(_) => panic!("Failed to parse `#[ast]` attribute"), + }; + let metas = meta_list + .parse_args_with(Punctuated::::parse_terminated) + .expect("Unable to parse `#[ast]` attribute"); + + let mut foreign_name = None; + for meta in &metas { + if let Meta::NameValue(name_value) = meta { + if name_value.path.is_ident("foreign") { + assert!( + foreign_name.is_none(), + "Multiple `#[ast(foreign)]` attributes on type: `{ident}`" + ); + foreign_name = Some(convert_expr_to_string(&name_value.value)); + } + } + } + foreign_name } diff --git a/tasks/ast_tools/src/parse/parse.rs b/tasks/ast_tools/src/parse/parse.rs index 0c1a8bb261d7f..ffe14e7ed89e7 100644 --- a/tasks/ast_tools/src/parse/parse.rs +++ b/tasks/ast_tools/src/parse/parse.rs @@ -601,6 +601,10 @@ impl<'c> Parser<'c> { for meta in &parts { let attr_name = meta.path().get_ident().unwrap().to_string(); + if attr_name == "foreign" { + continue; + } + if let Some((processor, positions)) = self.codegen.attr_processor(&attr_name) { // Check attribute is legal in this position // and has the relevant trait `#[generate_derive]`-ed on it