refactor(transformer/class-properties): use stack of ClassDetails (#7947)

Replace stack of `PrivateProps` with a stack of `ClassDetails`.

Primary purpose is to prepare for further changes to come, but this also allows removing the messy hack of storing `ClassBindings` in 2 places, and having to decide which is the source of truth at any given moment. Now there is only 1 copy of `ClassBindings`.
This commit is contained in:
overlookmotel 2024-12-17 04:30:41 +00:00
parent fc5380442b
commit 98340bbaa1
9 changed files with 217 additions and 238 deletions

View file

@ -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.

View file

@ -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

View file

@ -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<FxIndexMap<Atom<'a>, 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<ClassDetails<'a>>,
}
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<ResolvedPrivateProp<'a, 'b>> {
// 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()
}
}

View file

@ -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<K, V> = IndexMap<K, V, FxBuildHasher>;
@ -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,

View file

@ -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(

View file

@ -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<PrivateProps<'a>>,
}
impl<'a> PrivatePropsStack<'a> {
// Forward methods to underlying `SparseStack`
#[inline]
pub fn push(&mut self, props: Option<PrivateProps<'a>>) {
self.stack.push(props);
}
#[inline]
pub fn pop(&mut self) -> Option<PrivateProps<'a>> {
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<ResolvedPrivateProp<'a, 'b>> {
// 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<Atom<'a>, 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,
}

View file

@ -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`?

View file

@ -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)
}
}

View file

@ -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 {