mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
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:
parent
dbe1972283
commit
78fee6ee24
2 changed files with 1501 additions and 35 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -47,8 +47,12 @@ impl Generator for AstBuilderGenerator {
|
|||
)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
///@@line_break
|
||||
use std::cell::Cell;
|
||||
|
||||
///@@line_break
|
||||
use oxc_allocator::{Allocator, Box, IntoIn, Vec};
|
||||
use oxc_syntax::{scope::ScopeId, symbol::SymbolId, reference::ReferenceId};
|
||||
|
||||
///@@line_break
|
||||
#[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 default_field(param: &Param) -> TokenStream {
|
||||
debug_assert!(param.is_default);
|
||||
let ident = ¶m.ident;
|
||||
quote!(#ident: Default::default())
|
||||
}
|
||||
let ident = ty.ident();
|
||||
let as_type = ty.to_type();
|
||||
let fn_name = struct_builder_name(ty);
|
||||
|
||||
let params = get_struct_params(ty, ctx);
|
||||
let (generic_params, where_clause) = get_generic_params(¶ms);
|
||||
|
||||
let fields = params
|
||||
.iter()
|
||||
.map(|param| {
|
||||
if param.is_default {
|
||||
default_field(param)
|
||||
} else if param.into_in {
|
||||
let ident = ¶m.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 params_incl_defaults = get_struct_params(ty, ctx);
|
||||
let (generic_params, where_clause) = get_generic_params(¶ms_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 ¶ms_incl_defaults {
|
||||
let mut field = if param.into_in {
|
||||
let ident = ¶m.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 = ¶m.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 fn_docs = DocComment::new(format!("Build {article} [`{ident}`]."))
|
||||
.with_description(format!("If you want the built node to be allocated in the memory arena, use [`AstBuilder::{alloc_fn_name}`] instead."))
|
||||
.with_params(¶ms);
|
||||
let create_docs = |fn_name, alloc_fn_name, params, extra| {
|
||||
let fn_docs = DocComment::new(format!("Build {article} [`{ident}`]{extra}."))
|
||||
.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 =
|
||||
DocComment::new(format!("Build {article} [`{ident}`], and store it in the memory arena."))
|
||||
let alloc_docs = DocComment::new(format!("Build {article} [`{ident}`]{extra}, 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_params(¶ms);
|
||||
.with_params(params);
|
||||
|
||||
quote! {
|
||||
(fn_docs, alloc_docs)
|
||||
};
|
||||
|
||||
let (fn_docs, alloc_docs) = create_docs(&fn_name, &alloc_fn_name, ¶ms, "");
|
||||
|
||||
let mut output = quote! {
|
||||
///@@line_break
|
||||
#fn_docs
|
||||
#[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),* }
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
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, ¶ms_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
|
||||
#[expect(dead_code)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct Param {
|
||||
is_default: bool,
|
||||
analysis: TypeAnalysis,
|
||||
|
|
|
|||
Loading…
Reference in a new issue