From 5d9ec0ab2c682907486f091a90ed16a054e22514 Mon Sep 17 00:00:00 2001 From: overlookmotel Date: Tue, 18 Feb 2025 10:57:22 +0000 Subject: [PATCH] feat(ast_tools): add `#[builder(default)]` attribute for structs and enums --- crates/oxc_ast_macros/src/lib.rs | 13 +++- crates/oxc_syntax/src/reference.rs | 1 + crates/oxc_syntax/src/scope.rs | 1 + crates/oxc_syntax/src/symbol.rs | 1 + tasks/ast_tools/src/generators/ast_builder.rs | 74 ++++++++++++++++--- tasks/ast_tools/src/schema/defs/enum.rs | 3 + tasks/ast_tools/src/schema/defs/struct.rs | 3 + .../src/schema/extensions/ast_builder.rs | 6 ++ tasks/ast_tools/src/schema/mod.rs | 1 + 9 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 tasks/ast_tools/src/schema/extensions/ast_builder.rs diff --git a/crates/oxc_ast_macros/src/lib.rs b/crates/oxc_ast_macros/src/lib.rs index cfef3cef4d13bb..b1a813a1674f59 100644 --- a/crates/oxc_ast_macros/src/lib.rs +++ b/crates/oxc_ast_macros/src/lib.rs @@ -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() diff --git a/crates/oxc_syntax/src/reference.rs b/crates/oxc_syntax/src/reference.rs index 7c10ea40afebc3..3d431bf020cdbd 100644 --- a/crates/oxc_syntax/src/reference.rs +++ b/crates/oxc_syntax/src/reference.rs @@ -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)] diff --git a/crates/oxc_syntax/src/scope.rs b/crates/oxc_syntax/src/scope.rs index cce8f781ae0adc..f43c613d725510 100644 --- a/crates/oxc_syntax/src/scope.rs +++ b/crates/oxc_syntax/src/scope.rs @@ -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)] diff --git a/crates/oxc_syntax/src/symbol.rs b/crates/oxc_syntax/src/symbol.rs index 1d8be5d2ce37fb..e09c91ca080e7b 100644 --- a/crates/oxc_syntax/src/symbol.rs +++ b/crates/oxc_syntax/src/symbol.rs @@ -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)] diff --git a/tasks/ast_tools/src/generators/ast_builder.rs b/tasks/ast_tools/src/generators/ast_builder.rs index 3bb4d65a7e594e..7290164a9b5d62 100644 --- a/tasks/ast_tools/src/generators/ast_builder.rs +++ b/tasks/ast_tools/src/generators/ast_builder.rs @@ -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 @@ -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(¶ms, true); + let (fn_params, fields) = get_struct_fn_params_and_fields(¶ms, true, schema); let (fn_name_postfix, doc_postfix) = if has_default_fields { let default_params = params.iter().filter(|param| param.is_default); @@ -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(¶ms, false); + let (fn_params, fields) = get_struct_fn_params_and_fields(¶ms, false, schema); params.retain(|param| !param.is_default); let mut output2 = generate_builder_methods_for_struct_impl( struct_def, @@ -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; }; @@ -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 ); @@ -342,6 +366,7 @@ 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| { @@ -349,7 +374,12 @@ fn get_struct_fn_params_and_fields( 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(¶m.fn_param); } @@ -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() { diff --git a/tasks/ast_tools/src/schema/defs/enum.rs b/tasks/ast_tools/src/schema/defs/enum.rs index 1d895167149041..83fb83a10e8cca 100644 --- a/tasks/ast_tools/src/schema/defs/enum.rs +++ b/tasks/ast_tools/src/schema/defs/enum.rs @@ -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}, @@ -33,6 +34,7 @@ pub struct EnumDef { pub variants: Vec, /// For `@inherits` inherited enum variants pub inherits: Vec, + pub builder: AstBuilderType, pub visit: VisitEnum, pub kind: Kind, pub layout: Layout, @@ -63,6 +65,7 @@ impl EnumDef { generated_derives, variants, inherits, + builder: AstBuilderType::default(), visit: VisitEnum::default(), kind: Kind::default(), layout: Layout::default(), diff --git a/tasks/ast_tools/src/schema/defs/struct.rs b/tasks/ast_tools/src/schema/defs/struct.rs index e132723958a9f0..6b3e4d5b6e8f1c 100644 --- a/tasks/ast_tools/src/schema/defs/struct.rs +++ b/tasks/ast_tools/src/schema/defs/struct.rs @@ -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}, @@ -29,6 +30,7 @@ pub struct StructDef { pub file_id: FileId, pub generated_derives: Derives, pub fields: Vec, + pub builder: AstBuilderType, pub visit: VisitStruct, pub kind: Kind, pub layout: Layout, @@ -57,6 +59,7 @@ impl StructDef { file_id, generated_derives, fields, + builder: AstBuilderType::default(), visit: VisitStruct::default(), kind: Kind::default(), layout: Layout::default(), diff --git a/tasks/ast_tools/src/schema/extensions/ast_builder.rs b/tasks/ast_tools/src/schema/extensions/ast_builder.rs new file mode 100644 index 00000000000000..6c611611ae0400 --- /dev/null +++ b/tasks/ast_tools/src/schema/extensions/ast_builder.rs @@ -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, +} diff --git a/tasks/ast_tools/src/schema/mod.rs b/tasks/ast_tools/src/schema/mod.rs index 9304bf2b589c02..db12fb14bb3792 100644 --- a/tasks/ast_tools/src/schema/mod.rs +++ b/tasks/ast_tools/src/schema/mod.rs @@ -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;