diff --git a/crates/oxc_transformer/src/es2022/class_properties/class.rs b/crates/oxc_transformer/src/es2022/class_properties/class.rs index df9724c28f420..242f7cad3d0cb 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/class.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/class.rs @@ -12,13 +12,12 @@ use oxc_syntax::{ }; use oxc_traverse::{BoundIdentifier, TraverseCtx}; -use crate::common::helper_loader::Helper; +use crate::{common::helper_loader::Helper, TransformCtx}; use super::{ constructor::InstanceInitsInsertLocation, - private_props::{PrivateProp, PrivateProps}, utils::{create_assignment, create_variable_declaration, exprs_into_stmts}, - ClassBindings, ClassProperties, FxIndexMap, + ClassBindings, ClassDetails, ClassProperties, FxIndexMap, PrivateProp, }; impl<'a, 'ctx> ClassProperties<'a, 'ctx> { @@ -63,10 +62,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Return number of expressions to be inserted before/after the class let mut expr_count = self.insert_before.len() + self.insert_after_exprs.len(); - - let private_props = self.private_props_stack.last(); - if let Some(private_props) = private_props { - expr_count += private_props.props.len(); + if let Some(private_props) = &self.current_class().private_props { + expr_count += private_props.len(); } if expr_count > 0 { @@ -99,7 +96,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // TODO: Deduct static private props from `expr_count`. // Or maybe should store count and increment it when create private static props? // They're probably pretty rare, so it'll be rarely used. - expr_count += 1 + usize::from(self.class_bindings.temp.is_some()); + let class_details = self.classes_stack.last(); + expr_count += 1 + usize::from(class_details.bindings.temp.is_some()); let mut exprs = ctx.ast.vec_with_capacity(expr_count); @@ -107,24 +105,23 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // (or `_prop = _classPrivateFieldLooseKey("prop")` if loose mode). // Babel has these always go first, regardless of order of class elements. // Also insert `var _prop;` temp var declarations for private static props. - let private_props = self.private_props_stack.last(); - if let Some(private_props) = private_props { + if let Some(private_props) = &class_details.private_props { // Insert `var _prop;` declarations here rather than when binding was created to maintain // same order of `var` declarations as Babel. // `c = class C { #x = 1; static y = 2; }` -> `var _C, _x;` // TODO(improve-on-babel): Simplify this. if self.private_fields_as_properties { - exprs.extend(private_props.props.iter().map(|(name, prop)| { + exprs.extend(private_props.iter().map(|(name, prop)| { // Insert `var _prop;` declaration self.ctx.var_declarations.insert_var(&prop.binding, ctx); // `_prop = _classPrivateFieldLooseKey("prop")` - let value = self.create_private_prop_key_loose(name, ctx); + let value = Self::create_private_prop_key_loose(name, self.ctx, ctx); create_assignment(&prop.binding, value, ctx) })); } else { let mut weakmap_symbol_id = None; - exprs.extend(private_props.props.values().filter_map(|prop| { + exprs.extend(private_props.values().filter_map(|prop| { // Insert `var _prop;` declaration self.ctx.var_declarations.insert_var(&prop.binding, ctx); @@ -144,7 +141,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Insert class + static property assignments + static blocks let class_expr = ctx.ast.move_expression(expr); - if let Some(binding) = &self.class_bindings.temp { + if let Some(binding) = &class_details.bindings.temp { // Insert `var _Class` statement, if it wasn't already in `transform_class` if !self.temp_var_is_created { self.ctx.var_declarations.insert_var(binding, ctx); @@ -193,7 +190,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // TODO: Run other transforms on inserted statements. How? - if let Some(temp_binding) = &self.class_bindings.temp { + let class_details = self.classes_stack.last_mut(); + if let Some(temp_binding) = &class_details.bindings.temp { // Binding for class name is required if let Some(ident) = &class.id { // Insert `var _Class` statement, if it wasn't already in `transform_class` @@ -225,13 +223,13 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { ); } - if let Some(private_props) = self.private_props_stack.last() { + if let Some(private_props) = &class_details.private_props { if self.private_fields_as_properties { self.ctx.statement_injector.insert_many_before( &stmt_address, - private_props.props.iter().map(|(name, prop)| { + private_props.iter().map(|(name, prop)| { // `var _prop = _classPrivateFieldLooseKey("prop");` - let value = self.create_private_prop_key_loose(name, ctx); + let value = Self::create_private_prop_key_loose(name, self.ctx, ctx); create_variable_declaration(&prop.binding, value, ctx) }), ); @@ -240,7 +238,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { let mut weakmap_symbol_id = None; self.ctx.statement_injector.insert_many_before( &stmt_address, - private_props.props.values().filter_map(|prop| { + private_props.values().filter_map(|prop| { if prop.is_static { return None; } @@ -258,15 +256,27 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { .statement_injector .insert_many_after(&stmt_address, self.insert_after_stmts.drain(..)); } + + // Update class bindings prior to traversing class body and insertion of statements/expressions + // before/after the class. See comments on `ClassBindings`. + // Static private fields reference class name (not temp var) in class declarations. + // `class Class { static #prop; method() { return obj.#prop; } }` + // -> `method() { return _assertClassBrand(Class, obj, _prop)._; }` + // (note `Class` in `_assertClassBrand(Class, ...)`, not `_Class`) + // So set "temp" binding to actual class name while visiting class body. + // Note: If declaration is `export default class {}` with no name, and class has static props, + // then class has had name binding created already in `transform_class`. + // So name binding is always `Some`. + class_details.bindings.temp = class_details.bindings.name.clone(); } /// `_classPrivateFieldLooseKey("prop")` fn create_private_prop_key_loose( - &self, name: &Atom<'a>, + transform_ctx: &TransformCtx<'a>, ctx: &mut TraverseCtx<'a>, ) -> Expression<'a> { - self.ctx.helper_call_expr( + transform_ctx.helper_call_expr( Helper::ClassPrivateFieldLooseKey, SPAN, ctx.ast.vec1(Argument::from(ctx.ast.expression_string_literal( @@ -349,7 +359,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Exit if nothing to transform if instance_prop_count == 0 && !has_static_prop && !has_static_block { - self.private_props_stack.push(None); + self.classes_stack.push(ClassDetails::default()); return; } @@ -379,25 +389,14 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { None }; - self.class_bindings = ClassBindings::new(class_name_binding, class_temp_binding); + let class_bindings = ClassBindings::new(class_name_binding, class_temp_binding); - // Add entry to `private_props_stack` - if private_props.is_empty() { - self.private_props_stack.push(None); - } else { - // `class_bindings.temp` in the `PrivateProps` entry is the temp var (if one has been created). - // Private fields in static prop initializers use the temp var in the transpiled output - // e.g. `_assertClassBrand(_Class, obj, _prop)`. - // At end of this function, if it's a class declaration, we set `class_bindings.temp` to be - // the binding for the class name, for when the class body is visited, because in the class - // body, private fields use the class name - // e.g. `_assertClassBrand(Class, obj, _prop)` (note `Class` not `_Class`). - self.private_props_stack.push(Some(PrivateProps { - props: private_props, - class_bindings: self.class_bindings.clone(), - is_declaration: self.is_declaration, - })); - } + // Add entry to `classes_stack` + self.classes_stack.push(ClassDetails { + is_declaration: self.is_declaration, + private_props: if private_props.is_empty() { None } else { Some(private_props) }, + bindings: class_bindings, + }); // Determine where to insert instance property initializers in constructor let instance_inits_insert_location = if instance_prop_count == 0 { @@ -488,29 +487,6 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { ctx, ); } - - // Update class bindings prior to traversing class body and insertion of statements/expressions - // before/after the class. See comments on `ClassBindings`. - if let Some(private_props) = self.private_props_stack.last_mut() { - // Transfer state of `temp` binding from `private_props_stack` to `self`. - // A temp binding may have been created while transpiling private fields in - // static prop initializers. - // TODO: Do this where `class_bindings.temp` is consumed instead? - if let Some(temp_binding) = &private_props.class_bindings.temp { - self.class_bindings.temp = Some(temp_binding.clone()); - } - - // Static private fields reference class name (not temp var) in class declarations. - // `class Class { static #prop; method() { return obj.#prop; } }` - // -> `method() { return _assertClassBrand(Class, obj, _prop)._; }` - // (note `Class` in `_assertClassBrand(Class, ...)`, not `_Class`) - // So set "temp" binding to actual class name while visiting class body. - // Note: If declaration is `export default class {}` with no name, and class has static props, - // then class has had name binding created already above. So name binding is always `Some`. - if self.is_declaration { - private_props.class_bindings.temp = private_props.class_bindings.name.clone(); - } - } } /// Pop from private props stack. @@ -523,7 +499,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { return; } - self.private_props_stack.pop(); + self.classes_stack.pop(); } /// Insert an expression after the class. diff --git a/crates/oxc_transformer/src/es2022/class_properties/class_bindings.rs b/crates/oxc_transformer/src/es2022/class_properties/class_bindings.rs index c8403569157b2..efaca62c690ad 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/class_bindings.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/class_bindings.rs @@ -7,6 +7,7 @@ use oxc_traverse::{BoundIdentifier, TraverseCtx}; /// 2. Temp var `_Class`, which may or may not be required. /// /// Temp var is required in the following circumstances: +/// /// * Class expression has static properties. /// e.g. `C = class { x = 1; }` /// * Class declaration has static properties and one of the static prop's initializers contains: @@ -17,11 +18,6 @@ use oxc_traverse::{BoundIdentifier, TraverseCtx}; /// c. A private field referring to one of the class's static private props. /// e.g. `class C { static #x; static y = obj.#x; }` /// -/// An instance of `ClassBindings` is stored in main `ClassProperties` transform, and a 2nd is stored -/// in `PrivateProps` for the class, if the class has any private properties. -/// If the class has private props, the instance of `ClassBindings` in `PrivateProps` is the source -/// of truth. -/// /// The logic for when transpiled private fields use a reference to class name or class temp var /// is unfortunately rather complicated. /// @@ -34,12 +30,12 @@ use oxc_traverse::{BoundIdentifier, TraverseCtx}; /// e.g. `class C { static #x; y = obj.#x; }` /// /// To cover all these cases, the meaning of `temp` binding here changes while traversing the class body. -/// [`ClassProperties::transform_class`] sets `temp` binding to be a copy of the `name` binding before -/// that traversal begins. So the name `temp` is misleading at that point. +/// [`ClassProperties::transform_class_declaration`] sets `temp` binding to be a copy of the +/// `name` binding before that traversal begins. So the name `temp` is misleading at that point. /// /// Debug assertions are used to make sure this complex logic is correct. /// -/// [`ClassProperties::transform_class`]: super::ClassProperties::transform_class +/// [`ClassProperties::transform_class_declaration`]: super::ClassProperties::transform_class_declaration #[derive(Default, Clone)] pub(super) struct ClassBindings<'a> { /// Binding for class name, if class has name diff --git a/crates/oxc_transformer/src/es2022/class_properties/class_details.rs b/crates/oxc_transformer/src/es2022/class_properties/class_details.rs new file mode 100644 index 0000000000000..e77f85f031277 --- /dev/null +++ b/crates/oxc_transformer/src/es2022/class_properties/class_details.rs @@ -0,0 +1,120 @@ +use oxc_ast::ast::*; +use oxc_data_structures::stack::NonEmptyStack; +use oxc_span::Atom; +use oxc_traverse::BoundIdentifier; + +use super::{ClassBindings, ClassProperties, FxIndexMap}; + +/// Details of a class. +/// +/// These are stored in `ClassesStack`. +#[derive(Default)] +pub(super) struct ClassDetails<'a> { + /// `true` for class declaration, `false` for class expression + pub is_declaration: bool, + /// Private properties. + /// Mapping private prop name to binding for temp var. + /// This is then used as lookup when transforming e.g. `this.#x`. + /// `None` if class has no private properties. + pub private_props: Option, PrivateProp<'a>>>, + /// Bindings for class name and temp var for class + pub bindings: ClassBindings<'a>, +} + +/// Details of a private property. +pub(super) struct PrivateProp<'a> { + pub binding: BoundIdentifier<'a>, + pub is_static: bool, +} + +/// Stack of `ClassDetails`. +/// Pushed to when entering a class, popped when exiting. +/// +/// We use a `NonEmptyStack` to make `last` and `last_mut` cheap (these are used a lot). +/// The first entry is a dummy. +/// +/// This is a separate structure, rather than just storing stack as a property of `ClassProperties` +/// to work around borrow-checker. You can call `find_private_prop` and retain the return value +/// without holding a mut borrow of the whole of `&mut ClassProperties`. This allows accessing other +/// properties of `ClassProperties` while that borrow is held. +#[derive(Default)] +pub(super) struct ClassesStack<'a> { + stack: NonEmptyStack>, +} + +impl<'a> ClassesStack<'a> { + /// Push an entry to stack. + #[inline] + pub fn push(&mut self, class: ClassDetails<'a>) { + self.stack.push(class); + } + + /// Push last entry from stack. + #[inline] + pub fn pop(&mut self) -> ClassDetails<'a> { + self.stack.pop() + } + + /// Get details of current class. + #[inline] + pub fn last(&self) -> &ClassDetails<'a> { + self.stack.last() + } + + /// Get details of current class as `&mut` reference. + #[inline] + pub fn last_mut(&mut self) -> &mut ClassDetails<'a> { + self.stack.last_mut() + } + + /// Lookup details of private property referred to by `ident`. + pub fn find_private_prop<'b>( + &'b mut self, + ident: &PrivateIdentifier<'a>, + ) -> Option> { + // Check for binding in closest class first, then enclosing classes. + // We skip the first, because this is a `NonEmptyStack` with dummy first entry. + // TODO: Check there are tests for bindings in enclosing classes. + for class in self.stack[1..].iter_mut().rev() { + if let Some(private_props) = &mut class.private_props { + if let Some(prop) = private_props.get(&ident.name) { + return Some(ResolvedPrivateProp { + prop_binding: &prop.binding, + class_bindings: &mut class.bindings, + is_static: prop.is_static, + is_declaration: class.is_declaration, + }); + } + } + } + // TODO: This should be unreachable. Only returning `None` because implementation is incomplete. + None + } +} + +/// Details of a private property resolved for a private field. +/// +/// This is the return value of [`ClassesStack::find_private_prop`]. +pub(super) struct ResolvedPrivateProp<'a, 'b> { + /// Binding for temp var representing the property + pub prop_binding: &'b BoundIdentifier<'a>, + /// Bindings for class name and temp var for class + pub class_bindings: &'b mut ClassBindings<'a>, + /// `true` if is a static property + pub is_static: bool, + /// `true` if class which defines this property is a class declaration + pub is_declaration: bool, +} + +// Shortcut methods to get current class +impl<'a, 'ctx> ClassProperties<'a, 'ctx> { + /// Get details of current class. + pub(super) fn current_class(&self) -> &ClassDetails<'a> { + self.classes_stack.last() + } + + /// Get details of current class as `&mut` reference. + pub(super) fn current_class_mut(&mut self) -> &mut ClassDetails<'a> { + self.classes_stack.last_mut() + } +} diff --git a/crates/oxc_transformer/src/es2022/class_properties/mod.rs b/crates/oxc_transformer/src/es2022/class_properties/mod.rs index 5dc3c7e52e99d..cf50b9c1ed57b 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/mod.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/mod.rs @@ -135,8 +135,8 @@ //! * `computed_key.rs`: Transform of property/method computed keys. //! * `private_field.rs`: Transform of private fields (`this.#prop`). //! * `super.rs`: Transform `super` expressions. +//! * `class_details.rs`: Structures containing details of classes and private properties. //! * `class_bindings.rs`: Structure containing bindings for class name and temp var. -//! * `private_props.rs`: Structures storing details of private properties. //! * `utils.rs`: Utility functions. //! //! ## References @@ -162,18 +162,18 @@ use crate::TransformCtx; mod class; mod class_bindings; +mod class_details; mod computed_key; mod constructor; mod instance_prop_init; mod private_field; -mod private_props; mod prop_decl; mod static_block; mod static_prop_init; mod supers; mod utils; use class_bindings::ClassBindings; -use private_props::PrivatePropsStack; +use class_details::{ClassDetails, ClassesStack, PrivateProp, ResolvedPrivateProp}; type FxIndexMap = IndexMap; @@ -202,16 +202,15 @@ pub struct ClassProperties<'a, 'ctx> { // State during whole AST // - /// Stack of private props. - /// Pushed to when entering a class (`None` if class has no private props, `Some` if it does). - /// Entries are a mapping from private prop name to binding for temp var. - /// This is then used as lookup when transforming e.g. `this.#x`. + /// Stack of classes. + /// Pushed to when entering a class, popped when exiting. // TODO: The way stack is used is not perfect, because pushing to/popping from it in // `enter_expression` / `exit_expression`. If another transform replaces the class, // then stack will get out of sync. // TODO: Should push to the stack only when entering class body, because `#x` in class `extends` // clause resolves to `#x` in *outer* class, not the current class. - private_props_stack: PrivatePropsStack<'a>, + classes_stack: ClassesStack<'a>, + /// Addresses of class expressions being processed, to prevent same class being visited twice. /// Have to use a stack because the revisit doesn't necessarily happen straight after the first visit. /// e.g. `c = class C { [class D {}] = 1; }` -> `c = (_D = class D {}, class C { ... })` @@ -221,8 +220,6 @@ pub struct ClassProperties<'a, 'ctx> { // /// `true` for class declaration, `false` for class expression is_declaration: bool, - /// Bindings for class name and temp var for class - class_bindings: ClassBindings<'a>, /// `true` if temp var for class has been inserted temp_var_is_created: bool, /// Scope that instance init initializers will be inserted into @@ -258,11 +255,10 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { private_fields_as_properties, transform_static_blocks, ctx, - private_props_stack: PrivatePropsStack::default(), + classes_stack: ClassesStack::default(), class_expression_addresses_stack: NonEmptyStack::new(Address::DUMMY), // Temporary values - overwritten when entering class is_declaration: false, - class_bindings: ClassBindings::default(), temp_var_is_created: false, instance_inits_scope_id: ScopeId::new(0), instance_inits_constructor_scope_id: None, diff --git a/crates/oxc_transformer/src/es2022/class_properties/private_field.rs b/crates/oxc_transformer/src/es2022/class_properties/private_field.rs index 6bab35d7bd3ea..4783f82f02563 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/private_field.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/private_field.rs @@ -14,12 +14,11 @@ use oxc_traverse::{ use crate::{common::helper_loader::Helper, TransformCtx}; use super::{ - private_props::ResolvedPrivateProp, utils::{ create_assignment, create_underscore_ident_name, debug_assert_expr_is_not_parenthesis_or_typescript_syntax, }, - ClassProperties, + ClassProperties, ResolvedPrivateProp, }; impl<'a, 'ctx> ClassProperties<'a, 'ctx> { @@ -49,7 +48,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { is_assignment: bool, ctx: &mut TraverseCtx<'a>, ) -> Expression<'a> { - let prop_details = self.private_props_stack.find(&field_expr.field); + let prop_details = self.classes_stack.find_private_prop(&field_expr.field); // TODO: Should never be `None` - only because implementation is incomplete. let Some(prop_details) = prop_details else { return Expression::PrivateFieldExpression(field_expr); @@ -179,7 +178,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { if self.private_fields_as_properties { // `object.#prop(arg)` -> `_classPrivateFieldLooseBase(object, _prop)[_prop](arg)` - let prop_details = self.private_props_stack.find(&field_expr.field); + let prop_details = self.classes_stack.find_private_prop(&field_expr.field); // TODO: Should never be `None` - only because implementation is incomplete. let Some(ResolvedPrivateProp { prop_binding, .. }) = prop_details else { return }; @@ -252,7 +251,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { ) -> Option<(Expression<'a>, Expression<'a>)> { // TODO: Should never be `None` - only because implementation is incomplete. let ResolvedPrivateProp { prop_binding, class_bindings, is_static, is_declaration } = - self.private_props_stack.find(&field_expr.field)?; + self.classes_stack.find_private_prop(&field_expr.field)?; let prop_ident = prop_binding.create_read_expression(ctx); // `(object.#method)()` @@ -346,7 +345,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { unreachable!() }; - let prop_details = self.private_props_stack.find(&field_expr.field); + let prop_details = self.classes_stack.find_private_prop(&field_expr.field); // TODO: Should never be `None` - only because implementation is incomplete. let Some(prop_details) = prop_details else { return }; let ResolvedPrivateProp { prop_binding, class_bindings, is_static, is_declaration } = @@ -726,7 +725,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { _ => unreachable!(), }; - let prop_details = self.private_props_stack.find(&field_expr.field); + let prop_details = self.classes_stack.find_private_prop(&field_expr.field); // TODO: Should never be `None` - only because implementation is incomplete. let Some(prop_details) = prop_details else { return }; let ResolvedPrivateProp { prop_binding, class_bindings, is_static, is_declaration } = @@ -1520,7 +1519,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // // TODO: Should never be `None` - only because implementation is incomplete. let ResolvedPrivateProp { prop_binding, .. } = - self.private_props_stack.find(&field_expr.field)?; + self.classes_stack.find_private_prop(&field_expr.field)?; let object = ctx.ast.move_expression(&mut field_expr.object); let replacement = Self::create_private_field_member_expr_loose( diff --git a/crates/oxc_transformer/src/es2022/class_properties/private_props.rs b/crates/oxc_transformer/src/es2022/class_properties/private_props.rs deleted file mode 100644 index 27548bc7703e6..0000000000000 --- a/crates/oxc_transformer/src/es2022/class_properties/private_props.rs +++ /dev/null @@ -1,94 +0,0 @@ -use oxc_ast::ast::PrivateIdentifier; -use oxc_data_structures::stack::SparseStack; -use oxc_span::Atom; -use oxc_traverse::BoundIdentifier; - -use super::{class_bindings::ClassBindings, FxIndexMap}; - -/// Stack of private props defined by classes. -/// -/// Pushed to when entering a class (`None` if class has no private props, `Some` if it does). -/// Entries contain a mapping from private prop name to binding for temp var for that property, -/// and details of the class that defines the private prop. -/// -/// This is used as lookup when transforming e.g. `this.#x`. -#[derive(Default)] -pub(super) struct PrivatePropsStack<'a> { - stack: SparseStack>, -} - -impl<'a> PrivatePropsStack<'a> { - // Forward methods to underlying `SparseStack` - - #[inline] - pub fn push(&mut self, props: Option>) { - self.stack.push(props); - } - - #[inline] - pub fn pop(&mut self) -> Option> { - self.stack.pop() - } - - #[inline] - pub fn last(&self) -> Option<&PrivateProps<'a>> { - self.stack.last() - } - - #[inline] - pub fn last_mut(&mut self) -> Option<&mut PrivateProps<'a>> { - self.stack.last_mut() - } - - /// Lookup details of private property referred to by `ident`. - pub fn find<'b>( - &'b mut self, - ident: &PrivateIdentifier<'a>, - ) -> Option> { - // Check for binding in closest class first, then enclosing classes - // TODO: Check there are tests for bindings in enclosing classes. - for private_props in self.stack.as_mut_slice().iter_mut().rev() { - if let Some(prop) = private_props.props.get(&ident.name) { - return Some(ResolvedPrivateProp { - prop_binding: &prop.binding, - class_bindings: &mut private_props.class_bindings, - is_static: prop.is_static, - is_declaration: private_props.is_declaration, - }); - } - } - // TODO: This should be unreachable. Only returning `None` because implementation is incomplete. - None - } -} - -/// Details of private properties for a class. -pub(super) struct PrivateProps<'a> { - /// Private properties for class. Indexed by property name. - // TODO(improve-on-babel): Order that temp vars are created in is not important. Use `FxHashMap` instead. - pub props: FxIndexMap, PrivateProp<'a>>, - /// Bindings for class name and temp var for class - pub class_bindings: ClassBindings<'a>, - /// `true` for class declaration, `false` for class expression - pub is_declaration: bool, -} - -/// Details of a private property. -pub(super) struct PrivateProp<'a> { - pub binding: BoundIdentifier<'a>, - pub is_static: bool, -} - -/// Details of a private property resolved for a private field. -/// -/// This is the return value of [`PrivatePropsStack::find`]. -pub(super) struct ResolvedPrivateProp<'a, 'b> { - /// Binding for temp var representing the property - pub prop_binding: &'b BoundIdentifier<'a>, - /// Bindings for class name and temp var for class - pub class_bindings: &'b mut ClassBindings<'a>, - /// `true` if is a static property - pub is_static: bool, - /// `true` if class which defines this property is a class declaration - pub is_declaration: bool, -} diff --git a/crates/oxc_transformer/src/es2022/class_properties/prop_decl.rs b/crates/oxc_transformer/src/es2022/class_properties/prop_decl.rs index bf359315d6d12..1e98aeaa2c1a3 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/prop_decl.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/prop_decl.rs @@ -67,8 +67,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { value: Expression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Expression<'a> { - let private_props = self.private_props_stack.last().unwrap(); - let prop = &private_props.props[&ident.name]; + let private_props = self.current_class().private_props.as_ref().unwrap(); + let prop = &private_props[&ident.name]; let arguments = ctx.ast.vec_from_array([ Argument::from(ctx.ast.expression_this(SPAN)), Argument::from(prop.binding.create_read_expression(ctx)), @@ -102,13 +102,14 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { self.insert_private_static_init_assignment(ident, value, ctx); } else { // Convert to assignment or `_defineProperty` call, depending on `loose` option - let class_binding = if self.is_declaration { + let class_details = self.current_class(); + let class_binding = if class_details.is_declaration { // Class declarations always have a name except `export default class {}`. // For default export, binding is created when static prop found in 1st pass. - self.class_bindings.name.as_ref().unwrap() + class_details.bindings.name.as_ref().unwrap() } else { // Binding is created when static prop found in 1st pass. - self.class_bindings.temp.as_ref().unwrap() + class_details.bindings.temp.as_ref().unwrap() }; let assignee = class_binding.create_read_expression(ctx); @@ -147,13 +148,14 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { ctx: &mut TraverseCtx<'a>, ) { // TODO: This logic appears elsewhere. De-duplicate it. - let class_binding = if self.is_declaration { + let class_details = self.current_class(); + let class_binding = if class_details.is_declaration { // Class declarations always have a name except `export default class {}`. // For default export, binding is created when static prop found in 1st pass. - self.class_bindings.name.as_ref().unwrap() + class_details.bindings.name.as_ref().unwrap() } else { // Binding is created when static prop found in 1st pass. - self.class_bindings.temp.as_ref().unwrap() + class_details.bindings.temp.as_ref().unwrap() }; let assignee = class_binding.create_read_expression(ctx); @@ -184,16 +186,17 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { let obj = ctx.ast.expression_object(SPAN, ctx.ast.vec1(property), None); // Insert after class - let private_props = self.private_props_stack.last().unwrap(); - let prop = &private_props.props[&ident.name]; + let class_details = self.current_class(); + let private_props = class_details.private_props.as_ref().unwrap(); + let prop_binding = &private_props[&ident.name].binding; - if self.is_declaration { + if class_details.is_declaration { // `var _prop = {_: value};` - let var_decl = create_variable_declaration(&prop.binding, obj, ctx); + let var_decl = create_variable_declaration(prop_binding, obj, ctx); self.insert_after_stmts.push(var_decl); } else { // `_prop = {_: value}` - let assignment = create_assignment(&prop.binding, obj, ctx); + let assignment = create_assignment(prop_binding, obj, ctx); self.insert_after_exprs.push(assignment); } } @@ -346,11 +349,11 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { None, ); - let private_props = self.private_props_stack.last().unwrap(); - let prop = &private_props.props[&ident.name]; + let private_props = self.current_class().private_props.as_ref().unwrap(); + let prop_binding = &private_props[&ident.name].binding; let arguments = ctx.ast.vec_from_array([ Argument::from(assignee), - Argument::from(prop.binding.create_read_expression(ctx)), + Argument::from(prop_binding.create_read_expression(ctx)), Argument::from(prop_def), ]); // TODO: Should this have span of original `PropertyDefinition`? diff --git a/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs b/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs index 4a57516bd0e94..5dce0179336d0 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs @@ -8,7 +8,7 @@ use oxc_ast::{ visit::{walk_mut, VisitMut}, }; use oxc_syntax::scope::{ScopeFlags, ScopeId}; -use oxc_traverse::{BoundIdentifier, TraverseCtx}; +use oxc_traverse::TraverseCtx; use super::ClassProperties; @@ -43,11 +43,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { fn set_is_transforming_static_property_initializers(&mut self, is_it: bool) { #[cfg(debug_assertions)] { - self.class_bindings.currently_transforming_static_property_initializers = is_it; - if let Some(private_props) = self.private_props_stack.last_mut() { - private_props.class_bindings.currently_transforming_static_property_initializers = - is_it; - } + let class_details = self.current_class_mut(); + class_details.bindings.currently_transforming_static_property_initializers = is_it; } } } @@ -139,16 +136,14 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> { ctx: &'v mut TraverseCtx<'a>, ) -> Self { let make_sloppy_mode = !ctx.current_scope_flags().is_strict_mode(); - Self { - walk_deep: make_sloppy_mode - || class_properties.class_bindings.name.is_some() - || class_properties.private_props_stack.last().is_some(), - make_sloppy_mode, - this_depth: 0, - scope_depth: 0, - class_properties, - ctx, - } + let walk_deep = if make_sloppy_mode { + true + } else { + let class_details = class_properties.current_class(); + class_details.bindings.name.is_some() || class_details.private_props.is_some() + }; + + Self { walk_deep, make_sloppy_mode, this_depth: 0, scope_depth: 0, class_properties, ctx } } } @@ -475,7 +470,8 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> { /// Replace `this` with reference to temp var for class. fn replace_this_with_temp_var(&mut self, expr: &mut Expression<'a>, span: Span) { if self.this_depth == 0 { - let temp_binding = self.class_properties.get_temp_binding(self.ctx); + let class_details = self.class_properties.current_class_mut(); + let temp_binding = class_details.bindings.get_or_init_temp_binding(self.ctx); *expr = temp_binding.create_spanned_read_expression(span, self.ctx); } } @@ -483,7 +479,8 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> { /// Replace reference to class name with reference to temp var for class. fn replace_class_name_with_temp_var(&mut self, ident: &mut IdentifierReference<'a>) { // Check identifier is reference to class name - let class_name_symbol_id = self.class_properties.class_bindings.name_symbol_id(); + let class_details = self.class_properties.current_class_mut(); + let class_name_symbol_id = class_details.bindings.name_symbol_id(); let Some(class_name_symbol_id) = class_name_symbol_id else { return }; let reference_id = ident.reference_id(); @@ -495,7 +492,7 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> { } // Identifier is reference to class name. Rename it. - let temp_binding = self.class_properties.get_temp_binding(self.ctx); + let temp_binding = class_details.bindings.get_or_init_temp_binding(self.ctx); ident.name = temp_binding.name.clone(); let symbols = self.ctx.symbols_mut(); @@ -520,17 +517,3 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> { } } } - -impl<'a, 'ctx> ClassProperties<'a, 'ctx> { - pub(super) fn get_temp_binding(&mut self, ctx: &mut TraverseCtx<'a>) -> &BoundIdentifier<'a> { - // `PrivateProps` is the source of truth for bindings if class has private props - // because other visitors which transform private fields may create a temp binding - // and store it on `PrivateProps` - let class_bindings = match self.private_props_stack.last_mut() { - Some(private_props) => &mut private_props.class_bindings, - None => &mut self.class_bindings, - }; - - class_bindings.get_or_init_temp_binding(ctx) - } -} diff --git a/crates/oxc_transformer/src/es2022/class_properties/supers.rs b/crates/oxc_transformer/src/es2022/class_properties/supers.rs index 83da00ca88c10..1616c6810d6bd 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/supers.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/supers.rs @@ -137,10 +137,10 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { is_callee: bool, ctx: &mut TraverseCtx<'a>, ) -> Expression<'a> { - let class_binding = self.get_temp_binding(ctx); + let temp_binding = self.current_class_mut().bindings.get_or_init_temp_binding(ctx); - let ident1 = Argument::from(class_binding.create_read_expression(ctx)); - let ident2 = Argument::from(class_binding.create_read_expression(ctx)); + let ident1 = Argument::from(temp_binding.create_read_expression(ctx)); + let ident2 = Argument::from(temp_binding.create_read_expression(ctx)); let property = Argument::from(property); let arguments = if is_callee {