mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
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:
parent
fc5380442b
commit
98340bbaa1
9 changed files with 217 additions and 238 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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`?
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue