Skip to content

Commit 94b376a

Browse files
committed
refactor(transformer/class-properties): simplify logic for when to create temp binding (#7977)
Remove the hack of overwriting temp binding with name binding before traversing class body. Instead decide which binding to use in `ClassBindings::get_or_init_static_binding` based on a flag. This less performant than what we had before. But it simplifies some confusing logic, and prepares the ground for changes to come where we'll need to duck in and out of static context repeatedly while traversing the class body.
1 parent bb38065 commit 94b376a

File tree

5 files changed

+55
-70
lines changed

5 files changed

+55
-70
lines changed

crates/oxc_transformer/src/es2022/class_properties/class.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -257,17 +257,20 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
257257
.insert_many_after(&stmt_address, self.insert_after_stmts.drain(..));
258258
}
259259

260-
// Update class bindings prior to traversing class body and insertion of statements/expressions
261-
// before/after the class. See comments on `ClassBindings`.
260+
// Flag that static private fields should be transpiled using name binding,
261+
// while traversing class body.
262+
//
262263
// Static private fields reference class name (not temp var) in class declarations.
263264
// `class Class { static #prop; method() { return obj.#prop; } }`
264265
// -> `method() { return _assertClassBrand(Class, obj, _prop)._; }`
265266
// (note `Class` in `_assertClassBrand(Class, ...)`, not `_Class`)
266-
// So set "temp" binding to actual class name while visiting class body.
267+
//
268+
// Also see comments on `ClassBindings`.
269+
//
267270
// Note: If declaration is `export default class {}` with no name, and class has static props,
268271
// then class has had name binding created already in `transform_class`.
269272
// So name binding is always `Some`.
270-
class_details.bindings.temp = class_details.bindings.name.clone();
273+
class_details.bindings.static_private_fields_use_temp = false;
271274
}
272275

273276
/// `_classPrivateFieldLooseKey("prop")`

crates/oxc_transformer/src/es2022/class_properties/class_bindings.rs

+41-37
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,29 @@ use oxc_traverse::{BoundIdentifier, TraverseCtx};
2222
/// is unfortunately rather complicated.
2323
///
2424
/// Transpiled private fields referring to a static private prop use:
25-
/// * Class name when field is within class body and class has a name
26-
/// e.g. `class C { static #x; method() { return obj.#x; } }`
27-
/// * Temp var when field is within class body and class has no name
28-
/// e.g. `C = class { static #x; method() { return obj.#x; } }`
29-
/// * Temp var when field is within a static prop initializer.
30-
/// e.g. `class C { static #x; y = obj.#x; }`
31-
///
32-
/// To cover all these cases, the meaning of `temp` binding here changes while traversing the class body.
33-
/// [`ClassProperties::transform_class_declaration`] sets `temp` binding to be a copy of the
34-
/// `name` binding before that traversal begins. So the name `temp` is misleading at that point.
3525
///
36-
/// Debug assertions are used to make sure this complex logic is correct.
26+
/// * Class name when field is within body of class declaration
27+
/// e.g. `class C { static #x; method() { return obj.#x; } }`
28+
/// -> `_assertClassBrand(C, obj, _x)._`
29+
/// * Temp var when field is within body of class expression
30+
/// e.g. `C = class C { static #x; method() { return obj.#x; } }`
31+
/// -> `_assertClassBrand(_C, obj, _x)._`
32+
/// * Temp var when field is within a static prop initializer
33+
/// e.g. `class C { static #x; static y = obj.#x; }`
34+
/// -> `_assertClassBrand(_C, obj, _x)._`
3735
///
38-
/// [`ClassProperties::transform_class_declaration`]: super::ClassProperties::transform_class_declaration
36+
/// `static_private_fields_use_temp` is updated as transform moves through the class,
37+
/// to indicate which binding to use.
3938
#[derive(Default, Clone)]
4039
pub(super) struct ClassBindings<'a> {
4140
/// Binding for class name, if class has name
4241
pub name: Option<BoundIdentifier<'a>>,
4342
/// Temp var for class.
4443
/// e.g. `_Class` in `_Class = class {}, _Class.x = 1, _Class`
4544
pub temp: Option<BoundIdentifier<'a>>,
46-
/// `true` if currently transforming static property initializers.
47-
/// Only used in debug builds to check logic is correct.
48-
#[cfg(debug_assertions)]
49-
pub currently_transforming_static_property_initializers: bool,
45+
/// `true` if should use temp binding for references to class in transpiled static private fields,
46+
/// `false` if can use name binding
47+
pub static_private_fields_use_temp: bool,
5048
}
5149

5250
impl<'a> ClassBindings<'a> {
@@ -55,37 +53,43 @@ impl<'a> ClassBindings<'a> {
5553
name_binding: Option<BoundIdentifier<'a>>,
5654
temp_binding: Option<BoundIdentifier<'a>>,
5755
) -> Self {
58-
Self {
59-
name: name_binding,
60-
temp: temp_binding,
61-
#[cfg(debug_assertions)]
62-
currently_transforming_static_property_initializers: false,
63-
}
56+
Self { name: name_binding, temp: temp_binding, static_private_fields_use_temp: true }
6457
}
6558

6659
/// Get `SymbolId` of name binding.
6760
pub fn name_symbol_id(&self) -> Option<SymbolId> {
6861
self.name.as_ref().map(|binding| binding.symbol_id)
6962
}
7063

71-
/// Create a binding for temp var, if there isn't one already.
72-
pub fn get_or_init_temp_binding(&mut self, ctx: &mut TraverseCtx<'a>) -> &BoundIdentifier<'a> {
73-
if self.temp.is_none() {
74-
// This should only be possible if we are currently transforming static prop initializers
75-
#[cfg(debug_assertions)]
76-
{
77-
assert!(
78-
self.currently_transforming_static_property_initializers,
79-
"Should be unreachable"
80-
);
81-
}
82-
83-
self.temp = Some(Self::create_temp_binding(self.name.as_ref(), ctx));
64+
/// Get binding to use for referring to class in transpiled static private fields.
65+
///
66+
/// e.g. `Class` in `_assertClassBrand(Class, object, _prop)._` (class name)
67+
/// or `_Class` in `_assertClassBrand(_Class, object, _prop)._` (temp var)
68+
///
69+
/// * In class expressions, this is always be temp binding.
70+
/// * In class declarations, it's the name binding when code is inside class body,
71+
/// and temp binding when code is outside class body.
72+
///
73+
/// `static_private_fields_use_temp` is set accordingly at the right moments
74+
/// elsewhere in this transform.
75+
///
76+
/// If a temp binding is required, and one doesn't already exist, a temp binding is created.
77+
pub fn get_or_init_static_binding(
78+
&mut self,
79+
ctx: &mut TraverseCtx<'a>,
80+
) -> &BoundIdentifier<'a> {
81+
if self.static_private_fields_use_temp {
82+
// Create temp binding if doesn't already exist
83+
self.temp.get_or_insert_with(|| Self::create_temp_binding(self.name.as_ref(), ctx))
84+
} else {
85+
// `static_private_fields_use_temp` is always `true` for class expressions.
86+
// Class declarations always have a name binding if they have any static props.
87+
// So `unwrap` here cannot panic.
88+
self.name.as_ref().unwrap()
8489
}
85-
self.temp.as_ref().unwrap()
8690
}
8791

88-
/// Generate a binding for temp var.
92+
/// Generate binding for temp var.
8993
pub fn create_temp_binding(
9094
name_binding: Option<&BoundIdentifier<'a>>,
9195
ctx: &mut TraverseCtx<'a>,

crates/oxc_transformer/src/es2022/class_properties/private_field.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
8888
Self::create_underscore_member_expression(prop_ident, span, ctx)
8989
} else {
9090
// `_assertClassBrand(Class, object, _prop)._`
91-
let class_binding = class_bindings.get_or_init_temp_binding(ctx);
91+
let class_binding = class_bindings.get_or_init_static_binding(ctx);
9292
let class_ident = class_binding.create_read_expression(ctx);
9393

9494
self.create_assert_class_brand_underscore(
@@ -282,7 +282,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
282282
Self::create_underscore_member_expression(prop_ident, field_expr.span, ctx);
283283
(callee, object)
284284
} else {
285-
let class_binding = class_bindings.get_or_init_temp_binding(ctx);
285+
let class_binding = class_bindings.get_or_init_static_binding(ctx);
286286
let class_ident = class_binding.create_read_expression(ctx);
287287

288288
// Make 2 copies of `object`
@@ -383,7 +383,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
383383
// for shortcut/no shortcut and do the "can we shortcut?" check here.
384384
// Then only create temp var for the "no shortcut" branch, and clone the resulting binding
385385
// before passing it to the "no shortcut" method. What a palaver!
386-
let class_binding = class_bindings.get_or_init_temp_binding(ctx);
386+
let class_binding = class_bindings.get_or_init_static_binding(ctx);
387387
let class_binding = class_binding.clone();
388388
let class_symbol_id = class_bindings.name_symbol_id();
389389

@@ -792,7 +792,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
792792
let get_expr = Self::create_underscore_member_expression(prop_ident, SPAN, ctx);
793793
(get_expr, object, None)
794794
} else {
795-
let class_binding = class_bindings.get_or_init_temp_binding(ctx);
795+
let class_binding = class_bindings.get_or_init_static_binding(ctx);
796796
let class_ident = class_binding.create_read_expression(ctx);
797797
let class_ident2 = class_binding.create_read_expression(ctx);
798798

crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs

+2-24
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
2222
value: &mut Expression<'a>,
2323
ctx: &mut TraverseCtx<'a>,
2424
) {
25-
self.set_is_transforming_static_property_initializers(true);
26-
2725
let mut replacer = StaticInitializerVisitor::new(self, ctx);
2826
replacer.visit_expression(value);
29-
30-
self.set_is_transforming_static_property_initializers(false);
31-
}
32-
33-
/// Set flag on `ClassBindings` that we are/are not currently transforming static prop initializers.
34-
///
35-
/// The logic around which bindings are used for transforming private fields is complex,
36-
/// so we use this to make sure the logic is correct.
37-
///
38-
/// In debug builds, `ClassBindings::get_or_init_temp_binding` will panic if we end up transforming
39-
/// a static private field, and there's no `temp` binding - which should be impossible.
40-
#[inline(always)] // `#[inline(always)]` because is no-op in release builds
41-
#[allow(clippy::inline_always)]
42-
#[cfg_attr(not(debug_assertions), expect(unused_variables, clippy::unused_self))]
43-
fn set_is_transforming_static_property_initializers(&mut self, is_it: bool) {
44-
#[cfg(debug_assertions)]
45-
{
46-
let class_details = self.current_class_mut();
47-
class_details.bindings.currently_transforming_static_property_initializers = is_it;
48-
}
4927
}
5028
}
5129

@@ -470,7 +448,7 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> {
470448
fn replace_this_with_temp_var(&mut self, expr: &mut Expression<'a>, span: Span) {
471449
if self.this_depth == 0 {
472450
let class_details = self.class_properties.current_class_mut();
473-
let temp_binding = class_details.bindings.get_or_init_temp_binding(self.ctx);
451+
let temp_binding = class_details.bindings.get_or_init_static_binding(self.ctx);
474452
*expr = temp_binding.create_spanned_read_expression(span, self.ctx);
475453
}
476454
}
@@ -491,7 +469,7 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> {
491469
}
492470

493471
// Identifier is reference to class name. Rename it.
494-
let temp_binding = class_details.bindings.get_or_init_temp_binding(self.ctx);
472+
let temp_binding = class_details.bindings.get_or_init_static_binding(self.ctx);
495473
ident.name = temp_binding.name.clone();
496474

497475
let symbols = self.ctx.symbols_mut();

crates/oxc_transformer/src/es2022/class_properties/supers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
137137
is_callee: bool,
138138
ctx: &mut TraverseCtx<'a>,
139139
) -> Expression<'a> {
140-
let temp_binding = self.current_class_mut().bindings.get_or_init_temp_binding(ctx);
140+
let temp_binding = self.current_class_mut().bindings.get_or_init_static_binding(ctx);
141141

142142
let ident1 = Argument::from(temp_binding.create_read_expression(ctx));
143143
let ident2 = Argument::from(temp_binding.create_read_expression(ctx));

0 commit comments

Comments
 (0)