Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/generated/ast_changes_watch_list.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ src:
- 'crates/oxc_ast/src/serialize/mod.rs'
- 'crates/oxc_ast/src/serialize/ts.rs'
- 'crates/oxc_ast_macros/src/generated/derived_traits.rs'
- 'crates/oxc_ast_macros/src/generated/structs.rs'
- 'crates/oxc_ast_macros/src/lib.rs'
- 'crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs'
- 'crates/oxc_ast_visit/src/generated/visit.rs'
Expand Down
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ num-traits = "0.2.19"
papaya = "0.2.1"
petgraph = { version = "0.8.1", default-features = false }
phf = "0.11.3"
phf_codegen = "0.11.3"
pico-args = "0.5.0"
prettyplease = "0.2.32"
project-root = "0.2.2"
Expand Down
664 changes: 332 additions & 332 deletions crates/oxc_ast/src/generated/assert_layouts.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/oxc_ast/src/generated/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

//! AST node factories

#![expect(clippy::default_trait_access)]
#![expect(clippy::default_trait_access, clippy::inconsistent_struct_constructor)]

use std::cell::Cell;

Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_ast/src/generated/derive_dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ impl<'a> Dummy<'a> for BlockStatement<'a> {
impl<'a> Dummy<'a> for Declaration<'a> {
/// Create a dummy [`Declaration`].
///
/// Has cost of making 1 allocation (48 bytes).
/// Has cost of making 1 allocation (40 bytes).
fn dummy(allocator: &'a Allocator) -> Self {
Self::VariableDeclaration(Dummy::dummy(allocator))
}
Expand Down Expand Up @@ -1210,7 +1210,7 @@ impl<'a> Dummy<'a> for ClassElement<'a> {
impl<'a> Dummy<'a> for MethodDefinition<'a> {
/// Create a dummy [`MethodDefinition`].
///
/// Has cost of making 3 allocations (160 bytes).
/// Has cost of making 3 allocations (152 bytes).
fn dummy(allocator: &'a Allocator) -> Self {
Self {
span: Dummy::dummy(allocator),
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_ast_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ proc-macro = true
doctest = false

[dependencies]
phf = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true, features = ["full", "parsing", "printing", "proc-macro"] }
107 changes: 91 additions & 16 deletions crates/oxc_ast_macros/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,106 @@
use proc_macro2::TokenStream;
use std::mem;

use proc_macro2::{TokenStream, TokenTree};
use quote::quote;
use syn::{Attribute, Fields, Ident, Item, ItemEnum, punctuated::Punctuated, token::Comma};
use syn::{
Attribute, Fields, FieldsNamed, Ident, Item, ItemEnum, ItemStruct, parse_quote,
punctuated::Punctuated, token::Comma,
};

use crate::generated::derived_traits::get_trait_crate_and_generics;
use crate::generated::{derived_traits::get_trait_crate_and_generics, structs::STRUCTS};

pub fn ast(input: &Item) -> TokenStream {
let (head, tail) = match input {
Item::Enum(enum_) => (enum_repr(enum_), assert_generated_derives(&enum_.attrs)),
Item::Struct(struct_) => (quote!(#[repr(C)]), assert_generated_derives(&struct_.attrs)),
/// `#[ast]` macro.
pub fn ast(item: &mut Item, args: TokenStream) -> TokenStream {
match item {
Item::Enum(item) => modify_enum(item),
Item::Struct(item) => modify_struct(item, args),
_ => unreachable!(),
}
}

/// 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)])
};

let assertions = assert_generated_derives(&item.attrs);

quote! {
#repr
#[derive(::oxc_ast_macros::Ast)]
#head
#input
#tail
#item
#assertions
}
}

/// If `enum_` has any non-unit variant, returns `#[repr(C, u8)]`, otherwise returns `#[repr(u8)]`.
fn enum_repr(enum_: &ItemEnum) -> TokenStream {
if enum_.variants.iter().any(|var| !matches!(var.fields, Fields::Unit)) {
quote!(#[repr(C, u8)])
} else {
quote!(#[repr(u8)])
/// Details of how `#[ast]` macro should modify a struct.
pub struct StructDetails {
pub field_order: Option<&'static [u8]>,
}

/// Add `#[repr(C)]` and `#[derive(::oxc_ast_macros::Ast)]` to struct,
/// and static assertions for `#[generate_derive]`.
/// Re-order struct fields if instructed by `STRUCTS` data.
fn modify_struct(item: &mut ItemStruct, args: TokenStream) -> TokenStream {
let assertions = assert_generated_derives(&item.attrs);

let item = reorder_struct_fields(item, args).unwrap_or_else(|| quote!(#item));

quote! {
#[repr(C)]
#[derive(::oxc_ast_macros::Ast)]
#item
#assertions
}
}

/// Re-order struct fields, depending on instructions in `STRUCTS` (which is codegen-ed).
fn reorder_struct_fields(item: &mut ItemStruct, args: TokenStream) -> Option<TokenStream> {
// Skip foreign types
if let Some(TokenTree::Ident(ident)) = args.into_iter().next() {
if ident == "foreign" {
return None;
}
}

// Get struct data. Exit if no fields need re-ordering.
let struct_name = item.ident.to_string();
let field_order = STRUCTS[&struct_name].field_order?;

// Re-order fields.
// `field_order` contains indexes of fields in the order they should be.
let fields = mem::replace(&mut item.fields, Fields::Unit);
let Fields::Named(FieldsNamed { brace_token, mut named }) = fields else { unreachable!() };

assert!(
named.len() == field_order.len(),
"Wrong number of fields for `{struct_name}` in `STRUCTS`"
);

// Create 2 sets of fields.
// 1st set are the fields in original order, each prefixed with `#[cfg(doc)]`.
// 2nd set are the fields in new order, each prefixed with `#[cfg(not(doc))]`.
// This is necessary so that fields are listed in original source order in docs.
let mut fields = named.clone().into_pairs().zip(field_order).collect::<Vec<_>>();
fields.sort_unstable_by_key(|(_, index)| **index);

for field in &mut named {
field.attrs.insert(0, parse_quote!( #[cfg(doc)]));
}

named.extend(fields.into_iter().map(|(mut pair, _)| {
pair.value_mut().attrs.insert(0, parse_quote!( #[cfg(not(doc))]));
pair
}));

item.fields = Fields::Named(FieldsNamed { brace_token, named });

Some(quote!( #item ))
}

/// Generate assertions that traits used in `#[generate_derive]` are in scope.
Expand Down
Loading
Loading