mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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:
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'
|
||||
|
||||
@@ -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 }),
|
||||
],
|
||||
};
|
||||
@@ -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| {
|
||||
|
||||
Reference in new issue
Block a user