From eae401c90bf1ba44ca3fdaf4dfc4d0465557e1d1 Mon Sep 17 00:00:00 2001 From: rzvxa <3788964+rzvxa@users.noreply.github.com> Date: Fri, 2 Aug 2024 00:07:46 +0000 Subject: [PATCH] feat(ast, ast_macros): apply stable repr to all `#[ast]` enums (#4373) closes #4296 --- Cargo.lock | 5 +++ crates/oxc_ast/src/ast/js.rs | 16 ---------- crates/oxc_ast/src/ast/jsx.rs | 1 - crates/oxc_ast/src/ast/macros.rs | 4 +-- crates/oxc_ast/src/ast/ts.rs | 6 ---- crates/oxc_ast_macros/Cargo.toml | 5 +++ crates/oxc_ast_macros/src/lib.rs | 52 +++++++++++++++++++++++--------- 7 files changed, 50 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ea9e73b586b4..74b20a859b933 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1355,6 +1355,11 @@ dependencies = [ [[package]] name = "oxc_ast_macros" version = "0.23.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "oxc_benchmark" diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index e7841cc6cfdc5..a9c047861c7f3 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -52,7 +52,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -269,7 +268,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[serde(untagged)] @@ -359,7 +357,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -436,7 +433,6 @@ pub struct TemplateElementValue<'a> { /// #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -597,7 +593,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -706,7 +701,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -725,7 +719,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -780,7 +773,6 @@ macro_rules! match_simple_assignment_target { pub use match_simple_assignment_target; #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -846,7 +838,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -949,7 +940,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -979,7 +969,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -1050,7 +1039,6 @@ pub struct BlockStatement<'a> { /// Declarations and the Variable Statement #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -1217,7 +1205,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -1251,7 +1238,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -2000,7 +1986,6 @@ pub struct StaticBlock<'a> { /// export as namespace d; // TSNamespaceExportDeclaration /// ``` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -2303,7 +2288,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] diff --git a/crates/oxc_ast/src/ast/jsx.rs b/crates/oxc_ast/src/ast/jsx.rs index a94e98bf5177f..62c5758565a9d 100644 --- a/crates/oxc_ast/src/ast/jsx.rs +++ b/crates/oxc_ast/src/ast/jsx.rs @@ -240,7 +240,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] diff --git a/crates/oxc_ast/src/ast/macros.rs b/crates/oxc_ast/src/ast/macros.rs index 8c85996416757..4febf1c820e87 100644 --- a/crates/oxc_ast/src/ast/macros.rs +++ b/crates/oxc_ast/src/ast/macros.rs @@ -20,7 +20,7 @@ /// /// ``` /// inherit_variants! { -/// #[repr(C, u8)] +/// #[ast] /// enum Statement<'a> { /// pub enum Statement<'a> { /// BlockStatement(Box<'a, BlockStatement<'a>>) = 0, @@ -35,7 +35,7 @@ /// expands to: /// /// ``` -/// #[repr(C, u8)] +/// #[ast] /// enum Statement<'a> { /// pub enum Statement<'a> { /// BlockStatement(Box<'a, BlockStatement<'a>>) = 0, diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index fdbc39551daa3..b8c206947b3a1 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -108,7 +108,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -191,7 +190,6 @@ pub enum TSLiteral<'a> { /// This is the root-level type for TypeScript types, kind of like [`Expression`] is for /// expressions. #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged, rename_all = "camelCase")] @@ -483,7 +481,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged, rename_all = "camelCase")] @@ -642,7 +639,6 @@ pub struct TSTypeReference<'a> { /// IdentifierReference /// NamespaceNameā€ƒ.ā€ƒIdentifierReference #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -1035,7 +1031,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged)] @@ -1208,7 +1203,6 @@ inherit_variants! { /// /// [`ast` module docs]: `super` #[ast(visit)] -#[repr(C, u8)] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[serde(untagged, rename_all = "camelCase")] diff --git a/crates/oxc_ast_macros/Cargo.toml b/crates/oxc_ast_macros/Cargo.toml index a3e385db998b8..5286f5812c24a 100644 --- a/crates/oxc_ast_macros/Cargo.toml +++ b/crates/oxc_ast_macros/Cargo.toml @@ -19,3 +19,8 @@ workspace = true [lib] proc-macro = true doctest = false + +[dependencies] +quote = { workspace = true } +syn = { workspace = true, features = ["full"] } +proc-macro2 = { workspace = true } diff --git a/crates/oxc_ast_macros/src/lib.rs b/crates/oxc_ast_macros/src/lib.rs index d35379d8e9a2a..75cd7e505580b 100644 --- a/crates/oxc_ast_macros/src/lib.rs +++ b/crates/oxc_ast_macros/src/lib.rs @@ -1,30 +1,54 @@ use proc_macro::TokenStream; -use std::str::FromStr; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; -/// Attach to AST node type (struct or enum), to signal to codegen to create visitor for this type. -/// -/// Macro's role is not to generate code - it's purely a means to communicate information to the codegen. -/// -/// Only thing macro does is add `#[derive(Ast)]` to the item. -/// Deriving `Ast` does nothing, but supports `#[scope]`, `#[visit]`, and other attrs on struct fields. -/// These "helper" attributes are also signals to the codegen, and do nothing in themselves. +/// returns `#[repr(C, u8)]` if `enum_` has any non-unit variant, +/// Otherwise it would return `#[repr(u8)]`. +fn enum_repr(enum_: &syn::ItemEnum) -> TokenStream2 { + if enum_.variants.iter().any(|var| !matches!(var.fields, syn::Fields::Unit)) { + quote!(#[repr(C, u8)]) + } else { + quote!(#[repr(u8)]) + } +} + +/// This attribute serves two purposes, +/// First, it is a marker for our codegen to detect AST types. Furthermore. +/// It is also a lightweight macro; All of its computation is cached and +/// it only applies the following changes without any complex operation: /// -/// This is a workaround for Rust not supporting helper attributes for `proc_macro_attribute` macros, -/// so we need to use a derive macro to get that support. +/// * Prepend `#[repr(C, u8)]` to fieldful enums e.g. `enum E { X: u32, Y: u8 }` +/// * Prepend `#[repr(u8)]` to unit (fieldless) enums e.g. `enum E { X, Y, Z, }` +/// * Prepend `#[derive(oxc_ast_macros::Ast)]` /// -/// Use native Rust `TokenStream`, to avoid dependency on slow-compiling crates like `syn` and `quote`. #[proc_macro_attribute] #[allow(clippy::missing_panics_doc)] pub fn ast(_args: TokenStream, input: TokenStream) -> TokenStream { - let mut stream = TokenStream::from_str("#[derive(::oxc_ast_macros::Ast)]").unwrap(); - stream.extend(input); - stream + let input = syn::parse_macro_input!(input as syn::Item); + + let repr = match input { + syn::Item::Enum(ref enum_) => enum_repr(enum_), + // In future, we'll add `#[repr(C)]` to structs, but at present this is disabled + syn::Item::Struct(_) => TokenStream2::default(), + + _ => { + unreachable!() + } + }; + + let expanded = quote! { + #[derive(::oxc_ast_macros::Ast)] + #repr + #input + }; + TokenStream::from(expanded) } /// Dummy derive macro for a non-existent trait `Ast`. /// /// Does not generate any code. /// Only purpose is to allow using `#[scope]`, `#[visit]`, and other attrs in the AST node type defs. +/// These "marker" attributes are used in codegen. #[proc_macro_derive(Ast, attributes(scope, visit, span, serde, tsify))] pub fn ast_derive(_item: TokenStream) -> TokenStream { TokenStream::new()