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
13 changes: 12 additions & 1 deletion crates/oxc_ast_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,18 @@ pub fn ast_meta(_args: TokenStream, input: TokenStream) -> TokenStream {
/// See [`macro@ast`] for further details.
#[proc_macro_derive(
Ast,
attributes(clone_in, content_eq, estree, generate_derive, plural, scope, span, ts, visit)
attributes(
builder,
clone_in,
content_eq,
estree,
generate_derive,
plural,
scope,
span,
ts,
visit
)
)]
pub fn ast_derive(_input: TokenStream) -> TokenStream {
TokenStream::new()
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_syntax/src/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use oxc_ast_macros::ast;

#[ast]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[builder(default)]
#[clone_in(default)]
#[content_eq(skip)]
#[estree(skip)]
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_syntax/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use oxc_ast_macros::ast;

#[ast]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[builder(default)]
#[clone_in(default)]
#[content_eq(skip)]
#[estree(skip)]
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_syntax/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use oxc_ast_macros::ast;

#[ast]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[builder(default)]
#[clone_in(default)]
#[content_eq(skip)]
#[estree(skip)]
Expand Down
74 changes: 63 additions & 11 deletions tasks/ast_tools/src/generators/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,42 @@ use crate::{
output::{output_path, Output},
schema::{Def, EnumDef, FieldDef, Schema, StructDef, TypeDef, VariantDef},
utils::{create_safe_ident, is_reserved_name},
Codegen, Generator, AST_CRATE_PATH,
Codegen, Generator, Result, AST_CRATE_PATH,
};

use super::define_generator;
use super::{attr_positions, define_generator, AttrLocation, AttrPart, AttrPositions};

/// Types to omit builder method for.
const BLACK_LIST: [&str; 1] = ["Span"];

/// Semantic ID types.
/// We generate builder methods both with and without these fields for types which include any of them.
const SEMANTIC_ID_TYPES: [&str; 3] = ["ScopeId", "SymbolId", "ReferenceId"];

/// Generator for `AstBuilder`.
pub struct AstBuilderGenerator;

define_generator!(AstBuilderGenerator);

impl Generator for AstBuilderGenerator {
/// Register that accept `#[builder]` attr on structs or enums.
fn attrs(&self) -> &[(&'static str, AttrPositions)] {
&[("builder", attr_positions!(Struct | Enum))]
}

/// Parse `#[builder(default)]` on struct or enum.
fn parse_attr(&self, _attr_name: &str, location: AttrLocation, part: AttrPart) -> Result<()> {
// No need to check attr name is `builder`, because that's the only attribute that
// this generator handles.
if !matches!(part, AttrPart::Tag("default")) {
return Err(());
}

match location {
AttrLocation::Struct(struct_def) => struct_def.builder.is_default = true,
AttrLocation::Enum(enum_def) => enum_def.builder.is_default = true,
_ => return Err(()),
}

Ok(())
}

/// Generate `AstBuilder`.
fn generate(&self, schema: &Schema, _codegen: &Codegen) -> Output {
let fns = schema
Expand Down Expand Up @@ -116,7 +134,7 @@ fn generate_builder_methods(type_def: &TypeDef, schema: &Schema) -> TokenStream
fn generate_builder_methods_for_struct(struct_def: &StructDef, schema: &Schema) -> TokenStream {
let (mut params, generic_params, where_clause, has_default_fields) =
get_struct_params(struct_def, schema);
let (fn_params, fields) = get_struct_fn_params_and_fields(&params, true);
let (fn_params, fields) = get_struct_fn_params_and_fields(&params, true, schema);

let (fn_name_postfix, doc_postfix) = if has_default_fields {
let default_params = params.iter().filter(|param| param.is_default);
Expand Down Expand Up @@ -153,7 +171,7 @@ fn generate_builder_methods_for_struct(struct_def: &StructDef, schema: &Schema)
}

// Generate builder functions excluding default fields
let (fn_params, fields) = get_struct_fn_params_and_fields(&params, false);
let (fn_params, fields) = get_struct_fn_params_and_fields(&params, false, schema);
params.retain(|param| !param.is_default);
let mut output2 = generate_builder_methods_for_struct_impl(
struct_def,
Expand Down Expand Up @@ -267,7 +285,13 @@ fn get_struct_params<'s>(
let type_def = field.type_def(schema);
let ty = type_def.ty(schema);

let is_default = SEMANTIC_ID_TYPES.contains(&type_def.innermost_type(schema).name());
// A field is default if its innermost type is marked `#[builder(default)]`
let innermost_type = type_def.innermost_type(schema);
let is_default = match innermost_type {
TypeDef::Struct(inner_struct) => inner_struct.builder.is_default,
TypeDef::Enum(inner_enum) => inner_enum.builder.is_default,
_ => false,
};
if is_default {
has_default_fields = true;
};
Expand Down Expand Up @@ -298,7 +322,7 @@ fn get_struct_params<'s>(

let fn_param_ty = if is_default {
assert!(!has_generic_param);
type_def.innermost_type(schema).ty(schema)
innermost_type.ty(schema)
} else if let Some(generic_ident) = generic_ident {
let where_clause_part = quote!( #generic_ident: IntoIn<'a, #ty> );
let generic_ty = quote!( #generic_ident );
Expand Down Expand Up @@ -342,14 +366,20 @@ fn get_struct_params<'s>(
fn get_struct_fn_params_and_fields(
params: &[Param],
include_default_fields: bool,
schema: &Schema,
) -> (/* function params */ TokenStream, /* fields */ TokenStream) {
let mut fields = vec![];
let fn_params = params.iter().filter_map(|param| {
let param_ident = &param.ident;

if param.is_default {
if include_default_fields {
fields.push(quote!( #param_ident: Cell::new(Some(#param_ident)) ));
// Builder functions which take default fields receive the innermost type as param.
// So wrap the param's value in `Cell::new(...)`, or `Some(...)` if necessary.
let field_type = param.field.type_def(schema);
let value = wrap_default_field_value(quote!( #param_ident ), field_type, schema);

fields.push(quote!( #param_ident: #value ));
return Some(&param.fn_param);
}

Expand Down Expand Up @@ -476,6 +506,28 @@ fn enum_variant_builder_name(enum_def: &EnumDef, variant: &VariantDef) -> Ident
format_ident!("{enum_name}_{variant_name}")
}

/// Wrap the value of a default field in `Cell::new(...)` or `Some(...)` if necessary.
///
/// Wrap recursively, moving inwards towards the innermost type.
fn wrap_default_field_value(
value: TokenStream,
type_def: &TypeDef,
schema: &Schema,
) -> TokenStream {
match type_def {
TypeDef::Cell(cell_def) => {
let inner_value = wrap_default_field_value(value, cell_def.inner_type(schema), schema);
quote!( Cell::new(#inner_value) )
}
TypeDef::Option(option_def) => {
let inner_value =
wrap_default_field_value(value, option_def.inner_type(schema), schema);
quote!( Some(#inner_value) )
}
_ => value,
}
}

/// Generate doc comment for function params.
fn generate_doc_comment_for_params(params: &[Param]) -> TokenStream {
if params.is_empty() {
Expand Down
3 changes: 3 additions & 0 deletions tasks/ast_tools/src/schema/defs/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::utils::{create_ident, pluralize};

use super::{
extensions::{
ast_builder::AstBuilderType,
clone_in::CloneInType,
content_eq::ContentEqType,
estree::{ESTreeEnum, ESTreeEnumVariant},
Expand All @@ -33,6 +34,7 @@ pub struct EnumDef {
pub variants: Vec<VariantDef>,
/// For `@inherits` inherited enum variants
pub inherits: Vec<TypeId>,
pub builder: AstBuilderType,
pub visit: VisitEnum,
pub kind: Kind,
pub layout: Layout,
Expand Down Expand Up @@ -63,6 +65,7 @@ impl EnumDef {
generated_derives,
variants,
inherits,
builder: AstBuilderType::default(),
visit: VisitEnum::default(),
kind: Kind::default(),
layout: Layout::default(),
Expand Down
3 changes: 3 additions & 0 deletions tasks/ast_tools/src/schema/defs/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::utils::{create_ident_tokens, pluralize};

use super::{
extensions::{
ast_builder::AstBuilderType,
clone_in::{CloneInStructField, CloneInType},
content_eq::{ContentEqStructField, ContentEqType},
estree::{ESTreeStruct, ESTreeStructField},
Expand All @@ -29,6 +30,7 @@ pub struct StructDef {
pub file_id: FileId,
pub generated_derives: Derives,
pub fields: Vec<FieldDef>,
pub builder: AstBuilderType,
pub visit: VisitStruct,
pub kind: Kind,
pub layout: Layout,
Expand Down Expand Up @@ -57,6 +59,7 @@ impl StructDef {
file_id,
generated_derives,
fields,
builder: AstBuilderType::default(),
visit: VisitStruct::default(),
kind: Kind::default(),
layout: Layout::default(),
Expand Down
6 changes: 6 additions & 0 deletions tasks/ast_tools/src/schema/extensions/ast_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Details for `AstBuilder` generator on a struct or enum.
#[derive(Default, Debug)]
pub struct AstBuilderType {
/// `true` if should be replaced with default value in AST builder methods
pub is_default: bool,
}
1 change: 1 addition & 0 deletions tasks/ast_tools/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub use meta::MetaType;

/// Extensions to schema for specific derives / generators
pub mod extensions {
pub mod ast_builder;
pub mod clone_in;
pub mod content_eq;
pub mod estree;
Expand Down
Loading