Skip to content

Commit

Permalink
Feat: Instance private fields in update expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Nov 19, 2024
1 parent 0046c54 commit 4ab1177
Show file tree
Hide file tree
Showing 4 changed files with 363 additions and 98 deletions.
180 changes: 177 additions & 3 deletions crates/oxc_transformer/src/es2022/class_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,10 @@ impl<'a, 'ctx> Traverse<'a> for ClassProperties<'a, 'ctx> {
Expression::AssignmentExpression(_) => {
self.transform_assignment_expression(expr, ctx);
}
Expression::ChainExpression(_) => {
// TODO
}
Expression::UpdateExpression(_) => {
self.transform_update_expression(expr, ctx);
}
Expression::ChainExpression(_) => {
// TODO
}
_ => {}
Expand Down Expand Up @@ -1361,6 +1361,180 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
}
}

/// Transform update expression (`++` or `--`) where argument is private field.
///
/// Instance prop:
///
/// * `++object.#prop` ->
/// ```js
/// _classPrivateFieldSet(
/// _prop, object,
/// (_object$prop = _classPrivateFieldGet(_prop, object), ++_object$prop)
/// ),
/// ```
///
/// * `object.#prop++` ->
/// ```js
/// (
/// _classPrivateFieldSet(
/// _prop, object,
/// (
/// _object$prop = _classPrivateFieldGet(_prop, object),
/// _object$prop2 = _object$prop++,
/// _object$prop
/// )
/// ),
/// _object$prop2
/// )
/// ```
///
/// Static prop:
///
/// * `++object.#prop++` ->
/// ```js
/// _prop._ = _assertClassBrand(
/// Class, object,
/// (_object$prop = _assertClassBrand(C, object, _prop)._, ++_object$prop)
/// )
/// ```
///
/// * `object.#prop++` ->
/// ```js
/// (
/// _prop._ = _assertClassBrand(
/// Class, object,
/// (
/// _object$prop = _assertClassBrand(C, object, _prop)._,
/// _object$prop2 = _object$prop++,
/// _object$prop
/// )
/// ),
/// _object$prop2
/// )
/// ```
///
/// Output in all cases contains an `UpdateExpression`, so mutate existing `UpdateExpression`
/// rather than creating a new one.
//
// `#[inline]` so that compiler sees that `expr` is an `Expression::UpdateExpression`
#[inline]
fn transform_update_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::UpdateExpression(update_expr) = expr else { unreachable!() };
if matches!(&update_expr.argument, SimpleAssignmentTarget::PrivateFieldExpression(_)) {
self.transform_update_expression_impl(expr, ctx);
};
}

fn transform_update_expression_impl(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
// Unfortunately no way to make compiler see that these branches are provably unreachable.
// This function is much too large inline, because `transform_static_assignment_expression`
// and `transform_instance_assignment_expression` are inlined into it.
let Expression::UpdateExpression(update_expr) = expr else { unreachable!() };
let field_expr = match &mut update_expr.argument {
SimpleAssignmentTarget::PrivateFieldExpression(field_expr) => field_expr.as_mut(),
_ => unreachable!(),
};

let prop_and_class_name_binding = self.lookup_private_property(&field_expr.field);
// TODO: Should never be `None` - only because implementation is incomplete.
let Some((prop, _class_name_binding)) = prop_and_class_name_binding else { return };
let prop_ident = prop.binding.create_read_expression(ctx);

let temp_value_binding = ctx.generate_uid_in_current_scope_based_on_node(
field_expr,
SymbolFlags::FunctionScopedVariable,
);

// TODO(improve-on-babel): Could avoid `move_expression` here and replace `field_expr.object` instead.
// Only doing this first to match the order Babel creates temp vars.
let object = ctx.ast.move_expression(&mut field_expr.object);

if prop.is_static {
// TODO
} else {
let prop_ident2 = prop.binding.create_read_expression(ctx);

// Make 2 copies of `object`
let (object_for_set, object_for_get) = self.duplicate_object(object, ctx);

// `_classPrivateFieldGet(_prop, object)`
let get_call = self.create_private_field_get(prop_ident, object_for_get, SPAN, ctx);

// `_object$prop = _classPrivateFieldGet(_prop, object)`
self.ctx.var_declarations.insert_var(&temp_value_binding, None, ctx);
let assignment = ctx.ast.expression_assignment(
SPAN,
AssignmentOperator::Assign,
temp_value_binding.create_read_write_target(ctx),
get_call,
);

// `++_object$prop` / `_object$prop++` (reusing existing `UpdateExpression`)
let UpdateExpression { span, prefix, .. } = **update_expr;
update_expr.span = SPAN;
update_expr.argument = {
let ident = temp_value_binding.create_read_write_reference(ctx);
SimpleAssignmentTarget::AssignmentTargetIdentifier(ctx.alloc(ident))
};
let update_expr = ctx.ast.move_expression(expr);

if prefix {
// Source = `++object.#prop` (prefix `++`)
// `(_object$prop = _classPrivateFieldGet(_prop, object), ++_object$prop)`
let value = ctx
.ast
.expression_sequence(SPAN, ctx.ast.vec_from_array([assignment, update_expr]));
// `_classPrivateFieldSet(_prop, object, <value>)`
*expr =
self.create_private_field_set(prop_ident2, object_for_set, value, span, ctx);
} else {
// Source = `object.#prop++` (postfix `++`)
// `_object$prop2 = _object$prop++`
let temp_value_binding2 = ctx.generate_uid_in_current_scope(
&temp_value_binding.name[1..],
SymbolFlags::FunctionScopedVariable,
);
self.ctx.var_declarations.insert_var(&temp_value_binding2, None, ctx);
let assignment2 = ctx.ast.expression_assignment(
SPAN,
AssignmentOperator::Assign,
temp_value_binding2.create_read_write_target(ctx),
update_expr,
);
// `(_object$prop = _classPrivateFieldGet(_prop, object), _object$prop2 = _object$prop++, _object$prop)`
let value = ctx.ast.expression_sequence(
SPAN,
ctx.ast.vec_from_array([
assignment,
assignment2,
temp_value_binding.create_read_expression(ctx),
]),
);
// `_classPrivateFieldSet(_prop, object, <value>)`
let set_call =
self.create_private_field_set(prop_ident2, object_for_set, value, span, ctx);
// `(_classPrivateFieldSet(_prop, object, <value>), _object$prop2)`
// TODO(improve-on-babel): Final `_object$prop2` is only needed if this expression
// is consumed (i.e. not in an `ExpressionStatement`)
*expr = ctx.ast.expression_sequence(
span,
ctx.ast.vec_from_array([
set_call,
temp_value_binding2.create_read_expression(ctx),
]),
);
}
}
}

/// Duplicate object to be used in get/set pair.
///
/// If `object` may have side effects, create a temp var `_object` and assign to it in expression
Expand Down
108 changes: 107 additions & 1 deletion tasks/coverage/snapshots/semantic_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ commit: df9d1650

semantic_typescript Summary:
AST Parsed : 6490/6490 (100.00%)
Positive Passed: 2602/6490 (40.09%)
Positive Passed: 2600/6490 (40.06%)
tasks/coverage/typescript/tests/cases/compiler/2dArrays.ts
semantic error: Symbol reference IDs mismatch for "Cell":
after transform: SymbolId(0): [ReferenceId(1)]
Expand Down Expand Up @@ -42222,6 +42222,86 @@ semantic error: Symbol reference IDs mismatch for "A":
after transform: SymbolId(0): [ReferenceId(0), ReferenceId(3)]
rebuilt : SymbolId(3): [ReferenceId(6)]

tasks/coverage/typescript/tests/cases/conformance/classes/members/privateNames/privateNameFieldUnaryMutation.ts
semantic error: Bindings mismatch:
after transform: ScopeId(2): ["_this$test", "_this$test10", "_this$test11", "_this$test12", "_this$test16", "_this$test17", "_this$test18", "_this$test19", "_this$test2", "_this$test20", "_this$test21", "_this$test22", "_this$test23", "_this$test24", "_this$test25", "_this$test26", "_this$test27", "_this$test3", "_this$test4", "_this$test5", "_this$test6", "_this$test7", "_this$test8", "_this$test9", "a", "b", "c", "d", "e", "f", "g", "h"]
rebuilt : ScopeId(2): ["_this$test", "_this$test10", "_this$test11", "_this$test12", "_this$test13", "_this$test14", "_this$test15", "_this$test16", "_this$test17", "_this$test18", "_this$test19", "_this$test2", "_this$test20", "_this$test21", "_this$test22", "_this$test23", "_this$test24", "_this$test25", "_this$test26", "_this$test27", "_this$test28", "_this$test29", "_this$test3", "_this$test30", "_this$test4", "_this$test5", "_this$test6", "_this$test7", "_this$test8", "_this$test9", "a", "b", "c", "d", "e", "f", "g", "h"]
Bindings mismatch:
after transform: ScopeId(3): ["_this$test13"]
rebuilt : ScopeId(3): []
Bindings mismatch:
after transform: ScopeId(5): ["_this$test14", "_this$test15"]
rebuilt : ScopeId(5): []
Bindings mismatch:
after transform: ScopeId(7): ["_this$test28"]
rebuilt : ScopeId(7): []
Bindings mismatch:
after transform: ScopeId(9): ["_this$test29", "_this$test30"]
rebuilt : ScopeId(9): []
Bindings mismatch:
after transform: ScopeId(11): ["_this$getInstance", "_this$getInstance$tes", "_this$getInstance$tes10", "_this$getInstance$tes11", "_this$getInstance$tes12", "_this$getInstance$tes16", "_this$getInstance$tes17", "_this$getInstance$tes18", "_this$getInstance$tes19", "_this$getInstance$tes2", "_this$getInstance$tes20", "_this$getInstance$tes21", "_this$getInstance$tes22", "_this$getInstance$tes23", "_this$getInstance$tes24", "_this$getInstance$tes25", "_this$getInstance$tes26", "_this$getInstance$tes27", "_this$getInstance$tes3", "_this$getInstance$tes4", "_this$getInstance$tes5", "_this$getInstance$tes6", "_this$getInstance$tes7", "_this$getInstance$tes8", "_this$getInstance$tes9", "_this$getInstance11", "_this$getInstance12", "_this$getInstance13", "_this$getInstance14", "_this$getInstance15", "_this$getInstance16", "_this$getInstance17", "_this$getInstance18", "_this$getInstance2", "_this$getInstance3", "_this$getInstance4", "_this$getInstance5", "_this$getInstance6", "_this$getInstance7", "_this$getInstance8", "a", "b", "c", "d", "e", "f", "g", "h"]
rebuilt : ScopeId(11): ["_this$getInstance", "_this$getInstance$tes", "_this$getInstance$tes10", "_this$getInstance$tes11", "_this$getInstance$tes12", "_this$getInstance$tes13", "_this$getInstance$tes14", "_this$getInstance$tes15", "_this$getInstance$tes16", "_this$getInstance$tes17", "_this$getInstance$tes18", "_this$getInstance$tes19", "_this$getInstance$tes2", "_this$getInstance$tes20", "_this$getInstance$tes21", "_this$getInstance$tes22", "_this$getInstance$tes23", "_this$getInstance$tes24", "_this$getInstance$tes25", "_this$getInstance$tes26", "_this$getInstance$tes27", "_this$getInstance$tes28", "_this$getInstance$tes29", "_this$getInstance$tes3", "_this$getInstance$tes30", "_this$getInstance$tes4", "_this$getInstance$tes5", "_this$getInstance$tes6", "_this$getInstance$tes7", "_this$getInstance$tes8", "_this$getInstance$tes9", "_this$getInstance10", "_this$getInstance11", "_this$getInstance12", "_this$getInstance13", "_this$getInstance14", "_this$getInstance15", "_this$getInstance16", "_this$getInstance17", "_this$getInstance18", "_this$getInstance19", "_this$getInstance2", "_this$getInstance20", "_this$getInstance3", "_this$getInstance4", "_this$getInstance5", "_this$getInstance6", "_this$getInstance7", "_this$getInstance8", "_this$getInstance9", "a", "b", "c", "d", "e", "f", "g", "h"]
Bindings mismatch:
after transform: ScopeId(12): ["_this$getInstance$tes13", "_this$getInstance9"]
rebuilt : ScopeId(12): []
Bindings mismatch:
after transform: ScopeId(14): ["_this$getInstance$tes14", "_this$getInstance$tes15", "_this$getInstance10"]
rebuilt : ScopeId(14): []
Bindings mismatch:
after transform: ScopeId(16): ["_this$getInstance$tes28", "_this$getInstance19"]
rebuilt : ScopeId(16): []
Bindings mismatch:
after transform: ScopeId(18): ["_this$getInstance$tes29", "_this$getInstance$tes30", "_this$getInstance20"]
rebuilt : ScopeId(18): []
Symbol scope ID mismatch for "_this$test13":
after transform: SymbolId(33): ScopeId(3)
rebuilt : SymbolId(17): ScopeId(2)
Symbol scope ID mismatch for "_this$test14":
after transform: SymbolId(34): ScopeId(5)
rebuilt : SymbolId(18): ScopeId(2)
Symbol scope ID mismatch for "_this$test15":
after transform: SymbolId(35): ScopeId(5)
rebuilt : SymbolId(19): ScopeId(2)
Symbol scope ID mismatch for "_this$test28":
after transform: SymbolId(48): ScopeId(7)
rebuilt : SymbolId(32): ScopeId(2)
Symbol scope ID mismatch for "_this$test29":
after transform: SymbolId(49): ScopeId(9)
rebuilt : SymbolId(33): ScopeId(2)
Symbol scope ID mismatch for "_this$test30":
after transform: SymbolId(50): ScopeId(9)
rebuilt : SymbolId(34): ScopeId(2)
Symbol scope ID mismatch for "_this$getInstance9":
after transform: SymbolId(72): ScopeId(12)
rebuilt : SymbolId(63): ScopeId(11)
Symbol scope ID mismatch for "_this$getInstance$tes13":
after transform: SymbolId(71): ScopeId(12)
rebuilt : SymbolId(64): ScopeId(11)
Symbol scope ID mismatch for "_this$getInstance10":
after transform: SymbolId(74): ScopeId(14)
rebuilt : SymbolId(65): ScopeId(11)
Symbol scope ID mismatch for "_this$getInstance$tes14":
after transform: SymbolId(73): ScopeId(14)
rebuilt : SymbolId(66): ScopeId(11)
Symbol scope ID mismatch for "_this$getInstance$tes15":
after transform: SymbolId(75): ScopeId(14)
rebuilt : SymbolId(67): ScopeId(11)
Symbol scope ID mismatch for "_this$getInstance19":
after transform: SymbolId(97): ScopeId(16)
rebuilt : SymbolId(88): ScopeId(11)
Symbol scope ID mismatch for "_this$getInstance$tes28":
after transform: SymbolId(96): ScopeId(16)
rebuilt : SymbolId(89): ScopeId(11)
Symbol scope ID mismatch for "_this$getInstance20":
after transform: SymbolId(99): ScopeId(18)
rebuilt : SymbolId(90): ScopeId(11)
Symbol scope ID mismatch for "_this$getInstance$tes29":
after transform: SymbolId(98): ScopeId(18)
rebuilt : SymbolId(91): ScopeId(11)
Symbol scope ID mismatch for "_this$getInstance$tes30":
after transform: SymbolId(100): ScopeId(18)
rebuilt : SymbolId(92): ScopeId(11)

tasks/coverage/typescript/tests/cases/conformance/classes/members/privateNames/privateNameInLhsReceiverExpression.ts
semantic error: Symbol reference IDs mismatch for "Test":
after transform: SymbolId(0): [ReferenceId(0)]
Expand Down Expand Up @@ -42266,6 +42346,32 @@ semantic error: Symbol reference IDs mismatch for "A":
after transform: SymbolId(0): [ReferenceId(0), ReferenceId(1), ReferenceId(3), ReferenceId(5), ReferenceId(6), ReferenceId(7), ReferenceId(8), ReferenceId(9), ReferenceId(10), ReferenceId(11), ReferenceId(12)]
rebuilt : SymbolId(1): [ReferenceId(2), ReferenceId(3), ReferenceId(5), ReferenceId(7), ReferenceId(8), ReferenceId(9), ReferenceId(10), ReferenceId(11), ReferenceId(12), ReferenceId(13)]

tasks/coverage/typescript/tests/cases/conformance/classes/members/privateNames/privateNameStaticFieldUnaryMutation.ts
semantic error: Bindings mismatch:
after transform: ScopeId(2): ["_C$test", "_C$test2", "_C$test3", "_C$test4", "_C$test5", "_C$test6", "_C$test7", "_C$test8", "a", "b", "c", "d"]
rebuilt : ScopeId(2): ["a", "b", "c", "d"]
Bindings mismatch:
after transform: ScopeId(3): ["_C$test9"]
rebuilt : ScopeId(3): []
Bindings mismatch:
after transform: ScopeId(5): ["_C$test10"]
rebuilt : ScopeId(5): []
Bindings mismatch:
after transform: ScopeId(7): ["_this$getClass$test", "_this$getClass$test2", "_this$getClass$test3", "_this$getClass$test4", "_this$getClass$test5", "_this$getClass$test6", "_this$getClass$test7", "_this$getClass$test8", "a", "b", "c", "d"]
rebuilt : ScopeId(7): ["a", "b", "c", "d"]
Bindings mismatch:
after transform: ScopeId(8): ["_this$getClass$test9"]
rebuilt : ScopeId(8): []
Bindings mismatch:
after transform: ScopeId(10): ["_this$getClass$test10"]
rebuilt : ScopeId(10): []
Symbol reference IDs mismatch for "C":
after transform: SymbolId(0): [ReferenceId(0), ReferenceId(1), ReferenceId(2), ReferenceId(3), ReferenceId(4), ReferenceId(5), ReferenceId(6), ReferenceId(7), ReferenceId(8), ReferenceId(9), ReferenceId(10), ReferenceId(11), ReferenceId(12), ReferenceId(13), ReferenceId(14), ReferenceId(24), ReferenceId(27), ReferenceId(31), ReferenceId(34), ReferenceId(46), ReferenceId(49), ReferenceId(53), ReferenceId(56)]
rebuilt : SymbolId(1): [ReferenceId(3), ReferenceId(4), ReferenceId(6), ReferenceId(7), ReferenceId(11), ReferenceId(12), ReferenceId(14), ReferenceId(15), ReferenceId(19), ReferenceId(21), ReferenceId(25), ReferenceId(27), ReferenceId(29)]
Symbol reference IDs mismatch for "_test":
after transform: SymbolId(9): [ReferenceId(15), ReferenceId(16), ReferenceId(17), ReferenceId(18), ReferenceId(19), ReferenceId(20), ReferenceId(21), ReferenceId(22), ReferenceId(23), ReferenceId(26), ReferenceId(29), ReferenceId(30), ReferenceId(33), ReferenceId(36), ReferenceId(37), ReferenceId(38), ReferenceId(39), ReferenceId(40), ReferenceId(41), ReferenceId(42), ReferenceId(43), ReferenceId(44), ReferenceId(45), ReferenceId(48), ReferenceId(51), ReferenceId(52), ReferenceId(55), ReferenceId(58)]
rebuilt : SymbolId(10): [ReferenceId(1), ReferenceId(8), ReferenceId(9), ReferenceId(16), ReferenceId(17), ReferenceId(22), ReferenceId(23), ReferenceId(28)]

tasks/coverage/typescript/tests/cases/conformance/classes/members/privateNames/privateNameWhenNotUseDefineForClassFieldsInEsNext.ts
semantic error: Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1), ScopeId(9)]
Expand Down
7 changes: 2 additions & 5 deletions tasks/transform_conformance/snapshots/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: d20b314c

Passed: 392/822
Passed: 393/822

# All Passed:
* babel-plugin-transform-class-static-block
Expand Down Expand Up @@ -273,7 +273,7 @@ x Output mismatch
x Output mismatch


# babel-plugin-transform-class-properties (65/248)
# babel-plugin-transform-class-properties (66/248)
* assumption-constantSuper/complex-super-class/input.js
x Output mismatch

Expand Down Expand Up @@ -528,9 +528,6 @@ x Output mismatch
* private/tagged-template/input.js
x Output mismatch

* private/update/input.js
x Output mismatch

* private-loose/assignment/input.js
x Output mismatch

Expand Down
Loading

0 comments on commit 4ab1177

Please sign in to comment.