refactor(ast_macros): calculate which enums are fieldless in ast_tools (#23957)

Move work from `#[ast]` macro into `ast_tools`. Calculate whether an enum is fieldless or not in `ast_tools` and pass that info to `#[ast]` macro via an `EnumDetails` struct. This avoids having to iterate over all variants of each enum in the macro.

This is preferable because `ast_tools` runs only after changes to AST, whereas the macro code runs every time `oxc_ast` crate is compiled.
This commit is contained in:
overlookmotel authored and overlookmotel committed 2026-06-30 15:45:38 +01:00
1 parent 287a0c3819
commit e94b6cde71
5 files changed
+214 -34

No files matched your search

@@ -20,6 +20,7 @@ src:
- 'crates/oxc_allocator/src/**/*.rs'
- 'crates/oxc_ast/src/**/*.rs'
- 'crates/oxc_ast_macros/src/generated/derived_traits.rs'
- 'crates/oxc_ast_macros/src/generated/enums.rs'
- 'crates/oxc_ast_macros/src/generated/structs.rs'
- 'crates/oxc_ast_macros/src/lib.rs'
- 'crates/oxc_ast_visit/src/**/*.rs'
+32 -8
View File
@@ -5,7 +5,9 @@ use syn::{
punctuated::Punctuated, spanned::Spanned, token::Comma,
};
use crate::generated::{derived_traits::get_trait_crate_and_generics, structs::STRUCTS};
use crate::generated::{
derived_traits::get_trait_crate_and_generics, enums::ENUMS, structs::STRUCTS,
};
/// `#[ast]` macro.
pub fn ast(item: &mut Item, args: TokenStream) -> TokenStream {
@@ -16,24 +18,46 @@ pub fn ast(item: &mut Item, args: TokenStream) -> TokenStream {
}
}
/// Details of how `#[ast]` macro should modify an enum.
pub struct EnumDetails {
/// `true` if all variants are fieldless (unit) - including any inherited variants.
/// Determines whether the enum is `#[repr(u8)]` (fieldless) or `#[repr(C, u8)]` (has fields).
pub is_fieldless: bool,
}
/// Add `#[repr(...)]` and `#[derive(::oxc_ast_macros::Ast)]` to enum,
/// and static assertions for `#[generate_derive]`.
fn modify_enum(item: &ItemEnum) -> TokenStream {
// If enum has any non-unit variant, `#[repr(C, u8)]`, otherwise `#[repr(u8)]`
let repr = if item.variants.iter().any(|var| !matches!(var.fields, Fields::Unit)) {
quote!(#[repr(C, u8)])
} else {
quote!(#[repr(u8)])
modify_enum_impl(item).unwrap_or_else(|message| {
let error = compile_error(&item.ident, message);
quote! {
#[derive(::oxc_ast_macros::Ast)]
#item
#error
}
})
}
fn modify_enum_impl(item: &ItemEnum) -> Result<TokenStream, &'static str> {
// Get enum data.
// Whether enum has any non-unit variants is calculated by `oxc_ast_tools`,
// rather than calculating it here on every compilation.
let enum_name = item.ident.to_string();
let Some(enum_details) = ENUMS.get(&enum_name) else {
return Err("Enum is unknown. Run `just ast` to re-run the codegen.");
};
// Fieldless enums are `#[repr(u8)]`. Enums with any non-unit variant are `#[repr(C, u8)]`.
let repr = if enum_details.is_fieldless { quote!(#[repr(u8)]) } else { quote!(#[repr(C, u8)]) };
let assertions = assert_generated_derives(&item.attrs);
quote! {
Ok(quote! {
#repr
#[derive(::oxc_ast_macros::Ast)]
#item
#assertions
}
})
}
/// Details of how `#[ast]` macro should modify a struct.
@@ -0,0 +1,128 @@
// Auto-generated code, DO NOT EDIT DIRECTLY!
// To edit this generated file you have to edit `tasks/ast_tools/src/generators/inherit_variants.rs`.
use crate::ast::EnumDetails;
/// Details of how `#[ast]` macro should modify enums.
#[expect(clippy::unreadable_literal)]
pub static ENUMS: phf::Map<&'static str, EnumDetails> = ::phf::Map {
key: 16287231350648472473,
disps: &[
(0, 17),
(0, 3),
(0, 0),
(0, 9),
(0, 12),
(0, 2),
(0, 4),
(0, 1),
(0, 2),
(0, 64),
(0, 30),
(0, 5),
(0, 20),
(0, 3),
(1, 32),
(0, 0),
(1, 81),
(0, 0),
(0, 55),
(0, 9),
(2, 34),
(0, 0),
(0, 2),
(0, 60),
(0, 14),
(0, 45),
(2, 19),
(10, 57),
(0, 0),
],
entries: &[
("JSXAttributeValue", EnumDetails { is_fieldless: false }),
("JSXAttributeName", EnumDetails { is_fieldless: false }),
("ImportOrExportKind", EnumDetails { is_fieldless: true }),
("JSXElementName", EnumDetails { is_fieldless: false }),
("UpdateOperator", EnumDetails { is_fieldless: true }),
("AssignmentTargetProperty", EnumDetails { is_fieldless: false }),
("AssignmentTarget", EnumDetails { is_fieldless: false }),
("ErrorSeverity", EnumDetails { is_fieldless: true }),
("TSEnumMemberName", EnumDetails { is_fieldless: false }),
("TSModuleReference", EnumDetails { is_fieldless: false }),
("Declaration", EnumDetails { is_fieldless: false }),
("Term", EnumDetails { is_fieldless: false }),
("ImportAttributeKey", EnumDetails { is_fieldless: false }),
("FormalParameterKind", EnumDetails { is_fieldless: true }),
("LanguageVariant", EnumDetails { is_fieldless: true }),
("TSLiteral", EnumDetails { is_fieldless: false }),
("WithClauseKeyword", EnumDetails { is_fieldless: true }),
("FileExtension", EnumDetails { is_fieldless: true }),
("TSType", EnumDetails { is_fieldless: false }),
("CharacterClassEscapeKind", EnumDetails { is_fieldless: true }),
("Language", EnumDetails { is_fieldless: true }),
("ModuleExportName", EnumDetails { is_fieldless: false }),
("ArrayExpressionElement", EnumDetails { is_fieldless: false }),
("ChainElement", EnumDetails { is_fieldless: false }),
("TSMappedTypeModifierOperator", EnumDetails { is_fieldless: true }),
("MethodDefinitionType", EnumDetails { is_fieldless: true }),
("TSModuleDeclarationKind", EnumDetails { is_fieldless: true }),
("JSXExpression", EnumDetails { is_fieldless: false }),
("ObjectPropertyKind", EnumDetails { is_fieldless: false }),
("BindingPattern", EnumDetails { is_fieldless: false }),
("Statement", EnumDetails { is_fieldless: false }),
("AccessorPropertyType", EnumDetails { is_fieldless: true }),
("CommentKind", EnumDetails { is_fieldless: true }),
("TSTupleElement", EnumDetails { is_fieldless: false }),
("ForStatementInit", EnumDetails { is_fieldless: false }),
("FunctionType", EnumDetails { is_fieldless: true }),
("Argument", EnumDetails { is_fieldless: false }),
("Expression", EnumDetails { is_fieldless: false }),
("ImportDeclarationSpecifier", EnumDetails { is_fieldless: false }),
("CharacterClassContents", EnumDetails { is_fieldless: false }),
("LogicalOperator", EnumDetails { is_fieldless: true }),
("CommentPosition", EnumDetails { is_fieldless: true }),
("PropertyKey", EnumDetails { is_fieldless: false }),
("ModuleDeclaration", EnumDetails { is_fieldless: false }),
("TSMethodSignatureKind", EnumDetails { is_fieldless: true }),
("ExportLocalName", EnumDetails { is_fieldless: false }),
("PropertyKind", EnumDetails { is_fieldless: true }),
("LookAroundAssertionKind", EnumDetails { is_fieldless: true }),
("TSTypeQueryExprName", EnumDetails { is_fieldless: false }),
("ImportPhase", EnumDetails { is_fieldless: true }),
("ClassType", EnumDetails { is_fieldless: true }),
("CharacterKind", EnumDetails { is_fieldless: true }),
("BinaryOperator", EnumDetails { is_fieldless: true }),
("TSImportTypeQualifier", EnumDetails { is_fieldless: false }),
("JSXAttributeItem", EnumDetails { is_fieldless: false }),
("PropertyDefinitionType", EnumDetails { is_fieldless: true }),
("MethodDefinitionKind", EnumDetails { is_fieldless: true }),
("JSXMemberExpressionObject", EnumDetails { is_fieldless: false }),
("TSModuleDeclarationName", EnumDetails { is_fieldless: false }),
("ExportExportName", EnumDetails { is_fieldless: false }),
("VariableDeclarationKind", EnumDetails { is_fieldless: true }),
("UnaryOperator", EnumDetails { is_fieldless: true }),
("JSXChild", EnumDetails { is_fieldless: false }),
("TSSignature", EnumDetails { is_fieldless: false }),
("TSTypePredicateName", EnumDetails { is_fieldless: false }),
("ExportDefaultDeclarationKind", EnumDetails { is_fieldless: false }),
("ExportImportName", EnumDetails { is_fieldless: false }),
("ForStatementLeft", EnumDetails { is_fieldless: false }),
("BigintBase", EnumDetails { is_fieldless: true }),
("BoundaryAssertionKind", EnumDetails { is_fieldless: true }),
("CommentContent", EnumDetails { is_fieldless: true }),
("TSTypeOperatorOperator", EnumDetails { is_fieldless: true }),
("AssignmentOperator", EnumDetails { is_fieldless: true }),
("TSModuleDeclarationBody", EnumDetails { is_fieldless: false }),
("ClassElement", EnumDetails { is_fieldless: false }),
("AssignmentTargetPattern", EnumDetails { is_fieldless: false }),
("TSTypeName", EnumDetails { is_fieldless: false }),
("SimpleAssignmentTarget", EnumDetails { is_fieldless: false }),
("ModuleKind", EnumDetails { is_fieldless: true }),
("AssignmentTargetMaybeDefault", EnumDetails { is_fieldless: false }),
("TSAccessibility", EnumDetails { is_fieldless: true }),
("MemberExpression", EnumDetails { is_fieldless: false }),
("NumberBase", EnumDetails { is_fieldless: true }),
("ImportImportName", EnumDetails { is_fieldless: false }),
("CharacterClassContentsKind", EnumDetails { is_fieldless: true }),
],
};
+1
View File
@@ -6,6 +6,7 @@ use syn::{Item, parse_macro_input};
mod ast;
mod generated {
pub mod derived_traits;
pub mod enums;
pub mod structs;
}
@@ -28,10 +28,10 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::{
AST_CRATE_PATH, Codegen, Generator,
AST_CRATE_PATH, AST_MACROS_CRATE_PATH, Codegen, Generator,
output::{Output, output_path},
schema::{Def, EnumDef, Schema, VariantDef},
utils::article_for,
utils::{article_for, generate_phf_map},
};
use super::define_generator;
@@ -42,33 +42,59 @@ pub struct InheritVariantsGenerator;
define_generator!(InheritVariantsGenerator);
impl Generator for InheritVariantsGenerator {
fn generate(&self, schema: &Schema, _codegen: &Codegen) -> Output {
let impls = generate_impls(schema);
let match_macros = generate_match_macros(schema);
let output = quote! {
//!@ Some `TryFrom` impls have a single non-shared variant left for the catch-all arm
#![expect(clippy::match_wildcard_for_single_variants)]
///@@line_break
use std::{mem::ManuallyDrop, ptr::addr_of};
///@@line_break
use oxc_allocator::ArenaBox;
///@@line_break
use crate::ast::*;
///@@line_break
#impls
#match_macros
};
Output::Rust { path: output_path(AST_CRATE_PATH, "inherit_variants.rs"), tokens: output }
fn generate_many(&self, schema: &Schema, _codegen: &Codegen) -> Vec<Output> {
vec![generate_inherit_variants(schema), generate_enum_details(schema)]
}
}
/// Generate `inherit_variants.rs` in `oxc_ast` - conversion methods, trait impls and `match_*!` macros.
fn generate_inherit_variants(schema: &Schema) -> Output {
let impls = generate_impls(schema);
let match_macros = generate_match_macros(schema);
let output = quote! {
//!@ Some `TryFrom` impls have a single non-shared variant left for the catch-all arm
#![expect(clippy::match_wildcard_for_single_variants)]
///@@line_break
use std::{mem::ManuallyDrop, ptr::addr_of};
///@@line_break
use oxc_allocator::ArenaBox;
///@@line_break
use crate::ast::*;
///@@line_break
#impls
#match_macros
};
Output::Rust { path: output_path(AST_CRATE_PATH, "inherit_variants.rs"), tokens: output }
}
/// Generate `enums.rs` in `oxc_ast_macros` - the `ENUMS` data table the `#[ast]` macro reads
/// to decide how to modify each enum, without having to derive it from the enum on every compilation.
fn generate_enum_details(schema: &Schema) -> Output {
let map = generate_phf_map(schema.enums().map(|enum_def| {
let is_fieldless = enum_def.is_fieldless();
let details = quote!( EnumDetails { is_fieldless: #is_fieldless } );
(enum_def.name(), details)
}));
let code = quote! {
use crate::ast::EnumDetails;
///@@line_break
/// Details of how `#[ast]` macro should modify enums.
#[expect(clippy::unreadable_literal)]
pub static ENUMS: phf::Map<&'static str, EnumDetails> = #map;
};
Output::Rust { path: output_path(AST_MACROS_CRATE_PATH, "enums.rs"), tokens: code }
}
/// Generate conversion methods and trait impls for all enums which inherit from other enums.
fn generate_impls(schema: &Schema) -> TokenStream {
let impls = schema.enums().flat_map(|enum_def| {