mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 12:51:57 +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)]
|
#![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,49 +255,100 @@ 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 = ¶m.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 alloc_fn_name = format_ident!("alloc_{fn_name}");
|
||||||
|
|
||||||
let params = get_struct_params(ty, ctx);
|
let params_incl_defaults = get_struct_params(ty, ctx);
|
||||||
let (generic_params, where_clause) = get_generic_params(¶ms);
|
let (generic_params, where_clause) = get_generic_params(¶ms_incl_defaults);
|
||||||
|
|
||||||
let fields = params
|
let mut has_default_fields = false;
|
||||||
.iter()
|
let mut params = vec![];
|
||||||
.map(|param| {
|
let mut fn_params_incl_defaults = vec![];
|
||||||
if param.is_default {
|
let mut default_field_names = vec![];
|
||||||
default_field(param)
|
let mut default_field_type_names = vec![];
|
||||||
} else if param.into_in {
|
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;
|
let ident = ¶m.ident;
|
||||||
quote!(#ident: #ident.into_in(self.allocator))
|
quote!(#ident: #ident.into_in(self.allocator))
|
||||||
} else {
|
} else {
|
||||||
param.ident.to_token_stream()
|
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);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
let params = params.into_iter().filter(Param::not_default).collect_vec();
|
if param.is_default {
|
||||||
let args = params.iter().map(|it| it.ident.clone());
|
let field_ident = ¶m.ident;
|
||||||
|
field = quote!(#field_ident: Default::default());
|
||||||
|
|
||||||
let alloc_fn_name = format_ident!("alloc_{fn_name}");
|
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| {
|
||||||
|
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_description(format!("If you want the built node to be allocated in the memory arena, use [`AstBuilder::{alloc_fn_name}`] instead."))
|
||||||
.with_params(¶ms);
|
.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(¶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
|
///@@line_break
|
||||||
#fn_docs
|
#fn_docs
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -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, ¶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
|
// 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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue