Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(transformer/class-properties): support private_fields_as_properties assumption #7717

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/oxc_transformer/src/common/helper_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ pub enum Helper {
ClassPrivateFieldSet2,
AssertClassBrand,
ToSetter,
ClassPrivateFieldLooseKey,
ClassPrivateFieldLooseBase,
}

impl Helper {
Expand All @@ -177,6 +179,8 @@ impl Helper {
Self::ClassPrivateFieldSet2 => "classPrivateFieldSet2",
Self::AssertClassBrand => "assertClassBrand",
Self::ToSetter => "toSetter",
Self::ClassPrivateFieldLooseKey => "classPrivateFieldLooseKey",
Self::ClassPrivateFieldLooseBase => "classPrivateFieldLooseBase",
}
}
}
Expand Down
1 change: 0 additions & 1 deletion crates/oxc_transformer/src/compiler_assumptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ pub struct CompilerAssumptions {
pub private_fields_as_symbols: bool,

#[serde(default)]
#[deprecated = "Not Implemented"]
pub private_fields_as_properties: bool,

#[serde(default)]
Expand Down
232 changes: 196 additions & 36 deletions crates/oxc_transformer/src/es2022/class_properties/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,29 +104,40 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {

let mut exprs = ctx.ast.vec_with_capacity(expr_count);

// Insert `_prop = new WeakMap()` expressions for private instance props.
// Insert `_prop = new WeakMap()` expressions for private instance props
// (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 {
let mut weakmap_symbol_id = None;
exprs.extend(private_props.props.values().filter_map(|prop| {
// Insert `var _prop;` declaration.
// Do it 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;`
self.ctx.var_declarations.insert_var(&prop.binding, ctx);

if prop.is_static {
return None;
}
// 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)| {
// 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);
create_assignment(&prop.binding, value, ctx)
}));
} else {
let mut weakmap_symbol_id = None;
exprs.extend(private_props.props.values().filter_map(|prop| {
// Insert `var _prop;` declaration
self.ctx.var_declarations.insert_var(&prop.binding, ctx);

// `_prop = new WeakMap()`
Some(create_assignment(
&prop.binding,
create_new_weakmap(&mut weakmap_symbol_id, ctx),
ctx,
))
}));
if prop.is_static {
return None;
}

// `_prop = new WeakMap()`
let value = create_new_weakmap(&mut weakmap_symbol_id, ctx);
Some(create_assignment(&prop.binding, value, ctx))
}));
}
}

// Insert computed key initializers
Expand Down Expand Up @@ -216,23 +227,31 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
}

if let Some(private_props) = self.private_props_stack.last() {
// TODO: Only call `insert_many_before` if some private *instance* props
let mut weakmap_symbol_id = None;
self.ctx.statement_injector.insert_many_before(
&stmt_address,
private_props.props.values().filter_map(|prop| {
if prop.is_static {
return None;
}

// `var _prop = new WeakMap()`
Some(create_variable_declaration(
&prop.binding,
create_new_weakmap(&mut weakmap_symbol_id, ctx),
ctx,
))
}),
);
if self.private_fields_as_properties {
self.ctx.statement_injector.insert_many_before(
&stmt_address,
private_props.props.iter().map(|(name, prop)| {
// `var _prop = _classPrivateFieldLooseKey("prop");`
let value = self.create_private_prop_key_loose(name, ctx);
create_variable_declaration(&prop.binding, value, ctx)
}),
);
} else {
// TODO: Only call `insert_many_before` if some private *instance* props
let mut weakmap_symbol_id = None;
self.ctx.statement_injector.insert_many_before(
&stmt_address,
private_props.props.values().filter_map(|prop| {
if prop.is_static {
return None;
}

// `var _prop = new WeakMap();`
let value = create_new_weakmap(&mut weakmap_symbol_id, ctx);
Some(create_variable_declaration(&prop.binding, value, ctx))
}),
);
}
}

if !self.insert_after_stmts.is_empty() {
Expand All @@ -242,6 +261,24 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
}
}

/// `_classPrivateFieldLooseKey("prop")`
fn create_private_prop_key_loose(
&self,
name: &Atom<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
self.ctx.helper_call_expr(
Helper::ClassPrivateFieldLooseKey,
SPAN,
ctx.ast.vec1(Argument::from(ctx.ast.expression_string_literal(
SPAN,
name.clone(),
None,
))),
ctx,
)
}

/// Main guts of the transform.
fn transform_class(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) {
// TODO(improve-on-babel): If outer scope is sloppy mode, all code which is moved to outside
Expand Down Expand Up @@ -600,12 +637,87 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
self.ctx.helper_call_expr(Helper::DefineProperty, SPAN, arguments, ctx)
}

/// Create `_classPrivateFieldInitSpec(this, _prop, value)` to be inserted into class constructor.
/// Create init assignment for private instance prop, to be inserted into class constructor.
///
/// Loose: `Object.defineProperty(this, _prop, {writable: true, value: value})`
/// Not loose: `_classPrivateFieldInitSpec(this, _prop, value)`
fn create_private_instance_init_assignment(
&mut self,
ident: &PrivateIdentifier<'a>,
value: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
if self.private_fields_as_properties {
let this = ctx.ast.expression_this(SPAN);
self.create_private_init_assignment_loose(ident, value, this, ctx)
} else {
self.create_private_instance_init_assignment_not_loose(ident, value, ctx)
}
}

/// `Object.defineProperty(<assignee>, _prop, {writable: true, value: value})`
fn create_private_init_assignment_loose(
&mut self,
ident: &PrivateIdentifier<'a>,
value: Expression<'a>,
assignee: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
// `Object.defineProperty`
let object_symbol_id = ctx.scopes().find_binding(ctx.current_scope_id(), "Object");
let object = ctx.create_ident_expr(
SPAN,
Atom::from("Object"),
object_symbol_id,
ReferenceFlags::Read,
);
let property = ctx.ast.identifier_name(SPAN, "defineProperty");
let callee =
Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false));

// `{writable: true, value: <value>}`
let prop_def = ctx.ast.expression_object(
SPAN,
ctx.ast.vec_from_array([
ctx.ast.object_property_kind_object_property(
SPAN,
PropertyKind::Init,
ctx.ast.property_key_identifier_name(SPAN, Atom::from("writable")),
ctx.ast.expression_boolean_literal(SPAN, true),
false,
false,
false,
),
ctx.ast.object_property_kind_object_property(
SPAN,
PropertyKind::Init,
ctx.ast.property_key_identifier_name(SPAN, Atom::from("value")),
value,
false,
false,
false,
),
]),
None,
);

let private_props = self.private_props_stack.last().unwrap();
let prop = &private_props.props[&ident.name];
let arguments = ctx.ast.vec_from_array([
Argument::from(assignee),
Argument::from(prop.binding.create_read_expression(ctx)),
Argument::from(prop_def),
]);
// TODO: Should this have span of original `PropertyDefinition`?
ctx.ast.expression_call(SPAN, callee, NONE, arguments, false)
}

/// `_classPrivateFieldInitSpec(this, _prop, value)`
fn create_private_instance_init_assignment_not_loose(
&mut self,
ident: &PrivateIdentifier<'a>,
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];
Expand All @@ -619,13 +731,58 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
}

/// Insert after class:
///
/// Not loose:
/// * Class declaration: `var _prop = {_: value};`
/// * Class expression: `_prop = {_: value}`
///
/// Loose:
/// `Object.defineProperty(Class, _prop, {writable: true, value: value});`
fn insert_private_static_init_assignment(
&mut self,
ident: &PrivateIdentifier<'a>,
value: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.private_fields_as_properties {
self.insert_private_static_init_assignment_loose(ident, value, ctx);
} else {
self.insert_private_static_init_assignment_not_loose(ident, value, ctx);
}
}

/// Insert after class:
/// `Object.defineProperty(Class, _prop, {writable: true, value: value});`
fn insert_private_static_init_assignment_loose(
&mut self,
ident: &PrivateIdentifier<'a>,
value: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
// TODO: This logic appears elsewhere. De-duplicate it.
let class_binding = if self.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()
} else {
// Binding is created when static prop found in 1st pass.
self.class_bindings.temp.as_ref().unwrap()
};

let assignee = class_binding.create_read_expression(ctx);
let assignment = self.create_private_init_assignment_loose(ident, value, assignee, ctx);
self.insert_expr_after_class(assignment, ctx);
}

/// Insert after class:
///
/// * Class declaration: `var _prop = {_: value};`
/// * Class expression: `_prop = {_: value}`
fn insert_private_static_init_assignment_not_loose(
&mut self,
ident: &PrivateIdentifier<'a>,
value: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
// `_prop = {_: value}`
let property = ctx.ast.object_property_kind_object_property(
Expand Down Expand Up @@ -791,6 +948,9 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
/// * `None` = Not looked up yet.
/// * `Some(None)` = Has been looked up, and `WeakMap` is unbound.
/// * `Some(Some(symbol_id))` = Has been looked up, and `WeakMap` has a local binding.
///
/// This is an optimization to avoid looking up the symbol for `WeakMap` over and over when defining
/// multiple private properties.
#[expect(clippy::option_option)]
fn create_new_weakmap<'a>(
symbol_id: &mut Option<Option<SymbolId>>,
Expand Down
6 changes: 6 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ pub struct ClassProperties<'a, 'ctx> {
//
/// If `true`, set properties with `=`, instead of `_defineProperty` helper.
set_public_class_fields: bool,
/// If `true`, record private properties as string keys
private_fields_as_properties: bool,
/// If `true`, transform static blocks.
transform_static_blocks: bool,

Expand Down Expand Up @@ -227,9 +229,13 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
) -> Self {
// TODO: Raise error if these 2 options are inconsistent
let set_public_class_fields = options.loose || ctx.assumptions.set_public_class_fields;
// TODO: Raise error if these 2 options are inconsistent
let private_fields_as_properties =
options.loose || ctx.assumptions.private_fields_as_properties;

Self {
set_public_class_fields,
private_fields_as_properties,
transform_static_blocks,
ctx,
private_props_stack: PrivatePropsStack::default(),
Expand Down
Loading
Loading