feat(ast): add AstBuilder::*_with_scope_id etc methods (#6760)

Add methods to `AstBuilder` to create AST nodes with `ScopeId`, `SymbolId`, `ReferenceId`, for use in transformer.

e.g. `identifier_reference_with_reference_id`, `binding_identifier_with_symbol_id`, `block_statement_with_scope_id `.
This commit is contained in:
overlookmotel 2024-10-22 03:40:02 +00:00
parent dbe1972283
commit 78fee6ee24
2 changed files with 1501 additions and 35 deletions

File diff suppressed because it is too large Load diff

View file

@ -47,8 +47,12 @@ impl Generator for AstBuilderGenerator {
)] )]
#![warn(missing_docs)] #![warn(missing_docs)]
///@@line_break
use std::cell::Cell;
///@@line_break ///@@line_break
use oxc_allocator::{Allocator, Box, IntoIn, Vec}; use oxc_allocator::{Allocator, Box, IntoIn, Vec};
use oxc_syntax::{scope::ScopeId, symbol::SymbolId, reference::ReferenceId};
///@@line_break ///@@line_break
#[allow(clippy::wildcard_imports)] #[allow(clippy::wildcard_imports)]
@ -251,53 +255,104 @@ fn default_init_field(field: &FieldDef) -> bool {
} }
} }
/// Generate builder function for struct.
///
/// Generates functions:
/// 1. to create owned object.
/// 2. to create boxed object.
///
/// If type has default fields (`scope_id`, `symbol_id`, `reference_id`), also generates functions:
///
/// 3. to create owned object with provided `ScopeId` / `SymbolId` / `ReferenceId`.
/// 4. to create boxed object with provided `ScopeId` / `SymbolId` / `ReferenceId`.
fn generate_struct_builder_fn(ty: &StructDef, ctx: &LateCtx) -> TokenStream { fn generate_struct_builder_fn(ty: &StructDef, ctx: &LateCtx) -> TokenStream {
fn default_field(param: &Param) -> TokenStream {
debug_assert!(param.is_default);
let ident = &param.ident;
quote!(#ident: Default::default())
}
let ident = ty.ident(); let ident = ty.ident();
let as_type = ty.to_type(); let as_type = ty.to_type();
let fn_name = struct_builder_name(ty); let fn_name = struct_builder_name(ty);
let params = get_struct_params(ty, ctx);
let (generic_params, where_clause) = get_generic_params(&params);
let fields = params
.iter()
.map(|param| {
if param.is_default {
default_field(param)
} else if param.into_in {
let ident = &param.ident;
quote!(#ident: #ident.into_in(self.allocator))
} else {
param.ident.to_token_stream()
}
})
.collect_vec();
let params = params.into_iter().filter(Param::not_default).collect_vec();
let args = params.iter().map(|it| it.ident.clone());
let alloc_fn_name = format_ident!("alloc_{fn_name}"); let alloc_fn_name = format_ident!("alloc_{fn_name}");
let params_incl_defaults = get_struct_params(ty, ctx);
let (generic_params, where_clause) = get_generic_params(&params_incl_defaults);
let mut has_default_fields = false;
let mut params = vec![];
let mut fn_params_incl_defaults = vec![];
let mut default_field_names = vec![];
let mut default_field_type_names = vec![];
let mut fields = vec![];
let mut fields_incl_defaults = vec![];
let mut args = vec![];
let mut args_incl_defaults = vec![];
for param in &params_incl_defaults {
let mut field = if param.into_in {
let ident = &param.ident;
quote!(#ident: #ident.into_in(self.allocator))
} else {
param.ident.to_token_stream()
};
if param.is_default && !has_default_fields {
has_default_fields = true;
fn_params_incl_defaults = params.iter().map(Param::to_token_stream).collect();
fields_incl_defaults.clone_from(&fields);
args_incl_defaults.clone_from(&args);
}
if param.is_default {
let field_ident = &param.ident;
field = quote!(#field_ident: Default::default());
let field_name = field_ident.to_string();
let field_type_name = match field_name.as_str() {
"scope_id" => "ScopeId",
"symbol_id" => "SymbolId",
"reference_id" => "ReferenceId",
_ => unreachable!(),
};
let field_type_ident = format_ident!("{field_type_name}");
fn_params_incl_defaults.push(quote!(#field_ident: #field_type_ident));
fields_incl_defaults.push(quote!( #field_ident: Cell::new(Some(#field_ident)) ));
default_field_names.push(field_name);
default_field_type_names.push(field_type_name);
} else {
params.push(param.clone());
args.push(param.ident.clone());
if has_default_fields {
fn_params_incl_defaults.push(param.to_token_stream());
fields_incl_defaults.push(field.clone());
}
}
if has_default_fields {
args_incl_defaults.push(param.ident.clone());
}
fields.push(field);
}
let article = article_for(ident.to_string()); let article = article_for(ident.to_string());
let fn_docs = DocComment::new(format!("Build {article} [`{ident}`].")) let create_docs = |fn_name, alloc_fn_name, params, extra| {
.with_description(format!("If you want the built node to be allocated in the memory arena, use [`AstBuilder::{alloc_fn_name}`] instead.")) let fn_docs = DocComment::new(format!("Build {article} [`{ident}`]{extra}."))
.with_params(&params); .with_description(format!("If you want the built node to be allocated in the memory arena, use [`AstBuilder::{alloc_fn_name}`] instead."))
.with_params(params);
let alloc_docs = let alloc_docs = DocComment::new(format!("Build {article} [`{ident}`]{extra}, and store it in the memory arena."))
DocComment::new(format!("Build {article} [`{ident}`], and store it in the memory arena."))
.with_description(format!("Returns a [`Box`] containing the newly-allocated node. If you want a stack-allocated node, use [`AstBuilder::{fn_name}`] instead.")) .with_description(format!("Returns a [`Box`] containing the newly-allocated node. If you want a stack-allocated node, use [`AstBuilder::{fn_name}`] instead."))
.with_params(&params); .with_params(params);
quote! { (fn_docs, alloc_docs)
};
let (fn_docs, alloc_docs) = create_docs(&fn_name, &alloc_fn_name, &params, "");
let mut output = quote! {
///@@line_break ///@@line_break
#fn_docs #fn_docs
#[inline] #[inline]
pub fn #fn_name #generic_params (self, #(#params),*) -> #as_type #where_clause { pub fn #fn_name #generic_params (self, #(#params),*) -> #as_type #where_clause {
#ident { #(#fields),* } #ident { #(#fields),* }
} }
@ -307,12 +362,41 @@ fn generate_struct_builder_fn(ty: &StructDef, ctx: &LateCtx) -> TokenStream {
pub fn #alloc_fn_name #generic_params (self, #(#params),*) -> Box<'a, #as_type> #where_clause { pub fn #alloc_fn_name #generic_params (self, #(#params),*) -> Box<'a, #as_type> #where_clause {
Box::new_in(self.#fn_name(#(#args),*), self.allocator) Box::new_in(self.#fn_name(#(#args),*), self.allocator)
} }
};
if has_default_fields {
let fn_name = format_ident!("{fn_name}_with_{}", default_field_names.join("_and_"));
let alloc_fn_name = format_ident!("alloc_{fn_name}");
let with = format!(" with `{}`", default_field_type_names.iter().join("` and `"));
let (fn_docs, alloc_docs) =
create_docs(&fn_name, &alloc_fn_name, &params_incl_defaults, &with);
output = quote! {
#output
///@@line_break
#fn_docs
#[inline]
pub fn #fn_name #generic_params (self, #(#fn_params_incl_defaults),*) -> #as_type #where_clause {
#ident { #(#fields_incl_defaults),* }
}
///@@line_break
#alloc_docs
#[inline]
pub fn #alloc_fn_name #generic_params (self, #(#fn_params_incl_defaults),*) -> Box<'a, #as_type> #where_clause {
Box::new_in(self.#fn_name(#(#args_incl_defaults),*), self.allocator)
}
};
} }
output
} }
// TODO: remove me // TODO: remove me
#[expect(dead_code)] #[expect(dead_code)]
#[derive(Debug)] #[derive(Clone, Debug)]
struct Param { struct Param {
is_default: bool, is_default: bool,
analysis: TypeAnalysis, analysis: TypeAnalysis,