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_watch_list.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ src:
- 'crates/oxc_ast/src/generated/get_id.rs'
- 'crates/oxc_ast/src/generated/visit.rs'
- 'crates/oxc_ast/src/generated/visit_mut.rs'
- 'crates/oxc_ast_macros/src/generated/mod.rs'
- 'crates/oxc_ast_macros/src/lib.rs'
- 'crates/oxc_regular_expression/src/ast.rs'
- 'crates/oxc_regular_expression/src/generated/derive_clone_in.rs'
Expand Down
46 changes: 9 additions & 37 deletions crates/oxc_ast_macros/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::{punctuated::Punctuated, token::Comma, Attribute, Fields, Ident, Item, ItemEnum};

use crate::generated::get_trait_crate_and_generics;

pub fn ast(input: &Item) -> TokenStream {
let (head, tail) = match input {
Item::Enum(enum_) => (enum_repr(enum_), assert_generated_derives(&enum_.attrs)),
Expand Down Expand Up @@ -44,19 +46,20 @@ fn enum_repr(enum_: &ItemEnum) -> TokenStream {
fn assert_generated_derives(attrs: &[Attribute]) -> TokenStream {
// NOTE: At this level we don't care if a trait is derived multiple times, It is the
// responsibility of the `ast_tools` to raise errors for those.
let assertion = attrs
let assertions = attrs
.iter()
.filter(|attr| attr.path().is_ident("generate_derive"))
.flat_map(parse_attr)
.map(|derive| {
let (abs_derive, generics) = abs_trait(&derive);
.map(|trait_ident| {
let trait_name = trait_ident.to_string();
let (trait_path, generics) = get_trait_crate_and_generics(&trait_name);
quote! {{
// NOTE: these are wrapped in a scope to avoid the need for unique identifiers.
trait AssertionTrait: #abs_derive #generics {}
impl<T: #derive #generics> AssertionTrait for T {}
trait AssertionTrait: #trait_path #generics {}
impl<T: #trait_ident #generics> AssertionTrait for T {}
}}
});
quote!(const _: () = { #(#assertion)* };)
quote!( const _: () = { #(#assertions)* }; )
}

#[inline]
Expand All @@ -65,34 +68,3 @@ fn parse_attr(attr: &Attribute) -> impl Iterator<Item = Ident> {
.expect("`#[generate_derive]` only accepts traits as single segment paths. Found an invalid argument.")
.into_iter()
}

// TODO: benchmark this to see if a lazy static cell containing `HashMap` would perform better.
#[inline]
fn abs_trait(
ident: &Ident,
) -> (/* absolute type path */ TokenStream, /* possible generics */ TokenStream) {
if ident == "CloneIn" {
(quote!(::oxc_allocator::CloneIn), quote!(<'static>))
} else if ident == "GetSpan" {
(quote!(::oxc_span::GetSpan), TokenStream::default())
} else if ident == "GetSpanMut" {
(quote!(::oxc_span::GetSpanMut), TokenStream::default())
} else if ident == "GetAddress" {
(quote!(::oxc_allocator::GetAddress), TokenStream::default())
} else if ident == "ContentEq" {
(quote!(::oxc_span::ContentEq), TokenStream::default())
} else if ident == "ESTree" {
(quote!(::oxc_estree::ESTree), TokenStream::default())
} else {
invalid_derive(ident)
}
}

#[cold]
fn invalid_derive(ident: &Ident) -> ! {
panic!(
"Invalid derive trait(generate_derive): {ident}.\n\
Help: If you are trying to implement a new `generate_derive` trait, \
make sure to add it to the list in `abs_trait` function."
)
}
17 changes: 17 additions & 0 deletions crates/oxc_ast_macros/src/generated/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Auto-generated code, DO NOT EDIT DIRECTLY!
// To edit this generated file you have to edit `tasks/ast_tools/src/main.rs`

use proc_macro2::TokenStream;
use quote::quote;

pub fn get_trait_crate_and_generics(trait_name: &str) -> (TokenStream, TokenStream) {
match trait_name {
"CloneIn" => (quote!(::oxc_allocator::CloneIn), quote!(< 'static >)),
"GetAddress" => (quote!(::oxc_allocator::GetAddress), TokenStream::new()),
"GetSpan" => (quote!(::oxc_span::GetSpan), TokenStream::new()),
"GetSpanMut" => (quote!(::oxc_span::GetSpanMut), TokenStream::new()),
"ContentEq" => (quote!(::oxc_span::ContentEq), TokenStream::new()),
"ESTree" => (quote!(::oxc_estree::ESTree), TokenStream::new()),
_ => panic!("Invalid derive trait(generate_derive): {trait_name}"),
}
}
1 change: 1 addition & 0 deletions crates/oxc_ast_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use proc_macro::TokenStream;
use syn::{parse_macro_input, Item};

mod ast;
mod generated;

/// This attribute serves two purposes:
///
Expand Down
8 changes: 8 additions & 0 deletions tasks/ast_tools/src/derives/clone_in.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ impl Derive for DeriveCloneIn {
"CloneIn"
}

fn trait_has_lifetime(&self) -> bool {
true
}

fn crate_name(&self) -> &'static str {
"oxc_allocator"
}

/// Register that accept `#[clone_in]` attr on struct fields.
fn attrs(&self) -> &[(&'static str, AttrPositions)] {
&[("clone_in", AttrPositions::StructField)]
Expand Down
4 changes: 4 additions & 0 deletions tasks/ast_tools/src/derives/content_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ impl Derive for DeriveContentEq {
"ContentEq"
}

fn crate_name(&self) -> &'static str {
"oxc_span"
}

fn prelude(&self) -> TokenStream {
quote! {
#![allow(clippy::match_same_arms)]
Expand Down
4 changes: 4 additions & 0 deletions tasks/ast_tools/src/derives/estree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ impl Derive for DeriveESTree {
"ESTree"
}

fn crate_name(&self) -> &'static str {
"oxc_estree"
}

fn snake_name(&self) -> String {
"estree".to_string()
}
Expand Down
4 changes: 4 additions & 0 deletions tasks/ast_tools/src/derives/get_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ impl Derive for DeriveGetAddress {
"GetAddress"
}

fn crate_name(&self) -> &'static str {
"oxc_allocator"
}

fn prelude(&self) -> TokenStream {
quote! {
#![allow(clippy::match_same_arms)]
Expand Down
9 changes: 9 additions & 0 deletions tasks/ast_tools/src/derives/get_span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ impl Derive for DeriveGetSpan {
"GetSpan"
}

fn crate_name(&self) -> &'static str {
"oxc_span"
}

/// Register that accept `#[span]` attr on struct fields.
fn attrs(&self) -> &[(&'static str, AttrPositions)] {
&[("span", AttrPositions::StructField)]
Expand Down Expand Up @@ -79,6 +83,11 @@ impl Derive for DeriveGetSpanMut {
"GetSpanMut"
}

/// Get crate trait is defined in.
fn crate_name(&self) -> &'static str {
"oxc_span"
}

fn prelude(&self) -> TokenStream {
quote! {
#![allow(clippy::match_same_arms)]
Expand Down
10 changes: 10 additions & 0 deletions tasks/ast_tools/src/derives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ pub trait Derive: Runner {
/// Get trait name.
fn trait_name(&self) -> &'static str;

/// Get if trait has lifetime.
///
/// Default to `false`, but can be overridden.
fn trait_has_lifetime(&self) -> bool {
false
}

/// Get crate trait is defined in.
fn crate_name(&self) -> &'static str;

/// Get snake case trait name.
///
/// Defaults to `trait_name()` converted to snake case.
Expand Down
37 changes: 36 additions & 1 deletion tasks/ast_tools/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
use std::{fmt::Write, fs};

use bpaf::{Bpaf, Parser};
use quote::quote;
use rayon::prelude::*;

mod codegen;
Expand All @@ -187,9 +188,10 @@ use codegen::{get_runners, Codegen, Runner};
use derives::Derive;
use generators::Generator;
use logger::{log, log_failed, log_result, log_success, logln};
use output::{Output, RawOutput};
use output::{output_path, Output, RawOutput};
use parse::parse_files;
use schema::Schema;
use utils::create_ident;

/// Paths to source files containing AST types
static SOURCE_PATHS: &[&str] = &[
Expand Down Expand Up @@ -295,6 +297,7 @@ fn main() {
logln!("All Derives and Generators... Done!");

// Edit `lib.rs` in `oxc_ast_macros` crate
outputs.push(generate_proc_macro());
outputs.push(generate_updated_proc_macro(&codegen));

// Add CI filter file to outputs
Expand Down Expand Up @@ -336,6 +339,38 @@ fn generate_ci_filter(outputs: &[RawOutput]) -> RawOutput {
Output::Yaml { path: GITHUB_WATCH_LIST_PATH.to_string(), code }.into_raw(file!())
}

/// Generate function for proc macro in `oxc_ast_macros` crate.
///
/// This function translates trait name to path to the trait and any generic params.
fn generate_proc_macro() -> RawOutput {
let match_arms = DERIVES.iter().map(|derive| {
let trait_name = derive.trait_name();
let trait_ident = create_ident(trait_name);
let crate_ident = create_ident(derive.crate_name());
if derive.trait_has_lifetime() {
quote!( #trait_name => (quote!(::#crate_ident::#trait_ident), quote!( <'static> )) )
} else {
quote!( #trait_name => (quote!(::#crate_ident::#trait_ident), TokenStream::new()) )
}
});

let output = quote! {
use proc_macro2::TokenStream;
use quote::quote;

///@@line_break
pub fn get_trait_crate_and_generics(trait_name: &str) -> (TokenStream, TokenStream) {
match trait_name {
#(#match_arms,)*
_ => panic!("Invalid derive trait(generate_derive): {trait_name}"),
}
}
};

Output::Rust { path: output_path(AST_MACROS_CRATE_PATH, "mod.rs"), tokens: output }
.into_raw(file!())
}

/// Update the list of helper attributes for `Ast` derive proc macro in `oxc_ast_macros` crate
/// to include all attrs which generators/derives utilize.
///
Expand Down
Loading