diff --git a/Cargo.lock b/Cargo.lock index b2c517e01547b2..dff09b9f4c9d00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2062,6 +2062,7 @@ name = "oxc_transformer" version = "0.36.0" dependencies = [ "base64", + "compact_str", "cow-utils", "dashmap 6.1.0", "indexmap", diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index ce23b97be84b93..6ed2e2cfcd6878 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -35,6 +35,7 @@ oxc_syntax = { workspace = true, features = ["to_js_string"] } oxc_traverse = { workspace = true } base64 = { workspace = true } +compact_str = { workspace = true } cow-utils = { workspace = true } dashmap = { workspace = true } indexmap = { workspace = true } diff --git a/crates/oxc_transformer/src/common/arrow_function_converter.rs b/crates/oxc_transformer/src/common/arrow_function_converter.rs index abf7ac32ac4f9c..7c6a90a70f6a41 100644 --- a/crates/oxc_transformer/src/common/arrow_function_converter.rs +++ b/crates/oxc_transformer/src/common/arrow_function_converter.rs @@ -87,9 +87,10 @@ //! The Implementation based on //! +use compact_str::CompactString; use rustc_hash::{FxHashMap, FxHashSet}; -use oxc_allocator::{Box as ArenaBox, String as ArenaString, Vec as ArenaVec}; +use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec}; use oxc_ast::{ast::*, NONE}; use oxc_data_structures::stack::SparseStack; use oxc_semantic::{ReferenceFlags, SymbolId}; @@ -120,8 +121,12 @@ struct SuperMethodInfo<'a> { super_expr: Expression<'a>, /// If it is true, the method should accept a prop parameter. is_computed: bool, - /// If it is true, the method should accept a value parameter. - is_assignment: bool, +} + +#[derive(Default)] +struct SuperMethods<'a> { + getters: FxHashMap<&'a str, SuperMethodInfo<'a>>, + setters: FxHashMap<&'a str, SuperMethodInfo<'a>>, } pub struct ArrowFunctionConverter<'a> { @@ -129,7 +134,7 @@ pub struct ArrowFunctionConverter<'a> { this_var_stack: SparseStack>, arguments_var_stack: SparseStack>, renamed_arguments_symbol_ids: FxHashSet, - super_methods: Option, SuperMethodInfo<'a>>>, + super_methods: Option>, } impl<'a> ArrowFunctionConverter<'a> { @@ -187,7 +192,7 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> { self.arguments_var_stack.push(None); if self.is_async_only() && func.r#async && Self::is_class_method_like_ancestor(ctx.parent()) { - self.super_methods = Some(FxHashMap::default()); + self.super_methods = Some(SuperMethods::default()); } } @@ -594,16 +599,14 @@ impl<'a> ArrowFunctionConverter<'a> { } }; - let binding_name = Self::generate_super_binding_name(assign_value.is_some(), property, ctx); - let super_info = super_methods.entry(binding_name.clone()).or_insert_with(|| { + let is_assignment = assign_value.is_some(); + let methods = + if is_assignment { &mut super_methods.setters } else { &mut super_methods.getters }; + let super_info = methods.entry(property).or_insert_with(|| { + let binding_name = Self::generate_super_binding_name(is_assignment, property); let binding = ctx .generate_uid_in_current_scope(&binding_name, SymbolFlags::FunctionScopedVariable); - SuperMethodInfo { - binding, - super_expr: init, - is_computed: argument.is_some(), - is_assignment: assign_value.is_some(), - } + SuperMethodInfo { binding, super_expr: init, is_computed: argument.is_some() } }); let callee = super_info.binding.create_read_expression(ctx); @@ -722,10 +725,10 @@ impl<'a> ArrowFunctionConverter<'a> { fn generate_super_method( target_scope_id: ScopeId, super_method: SuperMethodInfo<'a>, + is_assignment: bool, ctx: &mut TraverseCtx<'a>, ) -> VariableDeclarator<'a> { - let SuperMethodInfo { binding, super_expr: mut init, is_computed, is_assignment } = - super_method; + let SuperMethodInfo { binding, super_expr: mut init, is_computed } = super_method; Self::adjust_binding_scope(target_scope_id, &binding, ctx); let scope_id = @@ -798,22 +801,17 @@ impl<'a> ArrowFunctionConverter<'a> { } /// Generate a binding name for the super method, like `superprop_getXXX`. - fn generate_super_binding_name( - is_assignment: bool, - property: &str, - ctx: &TraverseCtx<'a>, - ) -> Atom<'a> { - let start = - if is_assignment { Atom::from("superprop_set") } else { Atom::from("superprop_get") }; + fn generate_super_binding_name(is_assignment: bool, property: &str) -> CompactString { + let mut name = if is_assignment { + CompactString::const_new("superprop_set") + } else { + CompactString::const_new("superprop_get") + }; let Some(&first_byte) = property.as_bytes().first() else { - return start; + return name; }; - let mut name = - ArenaString::with_capacity_in(start.len() + property.len(), ctx.ast.allocator); - name.push_str(start.as_str()); - // Capitalize the first letter of the property name. // Fast path for ASCII (very common case). // TODO(improve-on-babel): We could just use format `superprop_get_prop` and avoid capitalizing. @@ -836,7 +834,7 @@ impl<'a> ArrowFunctionConverter<'a> { } else { #[cold] #[inline(never)] - fn push_unicode(property: &str, name: &mut ArenaString) { + fn push_unicode(property: &str, name: &mut CompactString) { let mut chars = property.chars(); let first_char = chars.next().unwrap(); name.extend(first_char.to_uppercase()); @@ -845,7 +843,7 @@ impl<'a> ArrowFunctionConverter<'a> { push_unicode(property, &mut name); } - ctx.ast.atom(name.into_bump_str()) + name } /// Whether to transform the `arguments` identifier. @@ -1013,7 +1011,9 @@ impl<'a> ArrowFunctionConverter<'a> { let is_class_method_like = Self::is_class_method_like_ancestor(ctx.parent()); let declarations_count = usize::from(arguments.is_some()) + if is_class_method_like { - self.super_methods.as_ref().map_or(0, FxHashMap::len) + self.super_methods.as_ref().map_or(0, |super_methods| { + super_methods.getters.len() + super_methods.setters.len() + }) } else { 0 } @@ -1030,11 +1030,19 @@ impl<'a> ArrowFunctionConverter<'a> { declarations.push(arguments); } - // `_superprop_getSomething = () => super.getSomething;` + // `_superprop_getSomething = () => super.something;` + // `_superprop_setSomething = (_prop, _value) => super[_prop] = _value;` if is_class_method_like { if let Some(super_methods) = self.super_methods.as_mut() { - declarations.extend(super_methods.drain().map(|(_, super_method)| { - Self::generate_super_method(target_scope_id, super_method, ctx) + let methods = super_methods + .getters + .drain() + .map(|(_, super_method)| (super_method, false)) + .chain( + super_methods.setters.drain().map(|(_, super_method)| (super_method, true)), + ); + declarations.extend(methods.map(|(super_method, is_assignment)| { + Self::generate_super_method(target_scope_id, super_method, is_assignment, ctx) })); } }