From 00840118ea62dc93a37e20e757de23bb49c9bee0 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Wed, 4 Dec 2024 01:40:35 +0800 Subject: [PATCH] polish, make it more readable --- .../src/es2022/class_properties/private.rs | 238 ++++++++++-------- 1 file changed, 137 insertions(+), 101 deletions(-) diff --git a/crates/oxc_transformer/src/es2022/class_properties/private.rs b/crates/oxc_transformer/src/es2022/class_properties/private.rs index 5ef66172ff9d33..279531875ef272 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/private.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/private.rs @@ -7,7 +7,7 @@ use oxc_allocator::Box as ArenaBox; use oxc_ast::{ast::*, NONE}; use oxc_span::SPAN; use oxc_syntax::{ - reference::{ReferenceFlags, ReferenceId}, + reference::{self, ReferenceFlags, ReferenceId}, symbol::{SymbolFlags, SymbolId}, }; use oxc_traverse::{ @@ -911,7 +911,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { } } - /// Recursively check if the expression has optional chaining. + /// Recursively check if the expression has optional expression. #[inline] pub(super) fn has_optional_expression(expr: &Expression<'a>) -> bool { let expr = expr.get_inner_expression(); @@ -919,9 +919,14 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { Expression::CallExpression(call) => { call.optional || Self::has_optional_expression(&call.callee) } - match_member_expression!(Expression) => { - let member = expr.to_member_expression(); - member.optional() || Self::has_optional_expression(member.object()) + Expression::StaticMemberExpression(member) => { + member.optional || Self::has_optional_expression(&member.object) + } + Expression::ComputedMemberExpression(member) => { + member.optional || Self::has_optional_expression(&member.object) + } + Expression::PrivateFieldExpression(member) => { + member.optional || Self::has_optional_expression(&member.object) } _ => false, } @@ -929,26 +934,25 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { /// Ensure that the expression is wrapped by a chain expression. pub(super) fn ensure_optional_expression_wrapper_by_chain_expression( - expr: &mut Expression<'a>, + expr: Expression<'a>, ctx: &mut TraverseCtx<'a>, - ) { - if !Self::has_optional_expression(expr) { - return; + ) -> Expression<'a> { + if Self::has_optional_expression(&expr) { + let chain_element = match expr { + Expression::CallExpression(call) => ChainElement::CallExpression(call), + expr @ match_member_expression!(Expression) => { + ChainElement::from(expr.into_member_expression()) + } + _ => unreachable!(), + }; + ctx.ast.expression_chain(SPAN, chain_element) + } else { + expr } - - let chain_element = match ctx.ast.move_expression(expr) { - Expression::CallExpression(call) => ChainElement::CallExpression(call), - expr @ match_member_expression!(Expression) => { - ChainElement::from(expr.into_member_expression()) - } - _ => { - unreachable!(); - } - }; - *expr = ctx.ast.expression_chain(SPAN, chain_element); } - pub(super) fn transform_optional_expression( + /// Go through the chain expression and transform the first optional expression. + pub(super) fn transform_first_optional_expression( &mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>, @@ -958,14 +962,18 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { Expression::CallExpression(call) => { if call.optional { call.optional = false; + // Avoid `transform_call_expression_for_bind_context` to call `get_inner_expression_mut` again, + // directly mutate `callee` with inner expression call.callee = ctx.ast.move_expression(call.callee.get_inner_expression_mut()); if call.callee.is_member_expression() { - return Some(self.transform_call_expression_for_bind_context(expr, ctx)); + // Special case for call expression because we need to make sure it has a proper context + return Some( + self.transform_call_expression_to_bind_proper_context(expr, ctx), + ); } - &mut call.callee } else { - return self.transform_optional_expression(&mut call.callee, ctx); + return self.transform_first_optional_expression(&mut call.callee, ctx); } } Expression::StaticMemberExpression(member) => { @@ -973,7 +981,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { member.optional = false; &mut member.object } else { - return self.transform_optional_expression(&mut member.object, ctx); + return self.transform_first_optional_expression(&mut member.object, ctx); } } Expression::ComputedMemberExpression(member) => { @@ -981,7 +989,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { member.optional = false; &mut member.object } else { - return self.transform_optional_expression(&mut member.object, ctx); + return self.transform_first_optional_expression(&mut member.object, ctx); } } Expression::PrivateFieldExpression(member) => { @@ -989,22 +997,31 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { member.optional = false; &mut member.object } else { - return self.transform_optional_expression(&mut member.object, ctx); + return self.transform_first_optional_expression(&mut member.object, ctx); } } _ => return None, }; + // Remove parentheses or other TS-syntax expressions *object = ctx.ast.move_expression(object.get_inner_expression_mut()); - let result = self.transform_expression_with_check(object, ctx); + let result = self.transform_expression_to_wrap_nullish_check(object, ctx); Some(result) } - fn transform_expression_with_check( + /// Transform expression to wrap nullish check. + /// + /// Returns: + /// * Bound Identifier: `A` -> `A === null || A === void 0` + /// * Unbound Identifier or anything else: `A.B` -> `(_A$B = A.B) === null || _A$B === void 0` + /// + /// NOTE: This function will mutate the passed-in `object` to a temp variable identifier. + fn transform_expression_to_wrap_nullish_check( &mut self, object: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Expression<'a> { + // `A` -> `A === null || A === void 0` if let Expression::Identifier(ident) = object { if let Some(binding) = self.get_existing_binding_for_identifier(ident, ctx) { let left1 = binding.create_read_expression(ctx); @@ -1013,6 +1030,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { } } + // `A.B` -> `(_A$B = A.B) === null || _A$B === void 0` // TODO: should add an API `generate_uid_in_current_hoist_scope_based_on_node` to instead this let temp_var_binding = ctx.generate_uid_in_current_scope_based_on_node( object, @@ -1020,78 +1038,79 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { ); self.ctx.var_declarations.insert_var(&temp_var_binding, None, ctx); - Self::ensure_optional_expression_wrapper_by_chain_expression(object, ctx); - - let expr = mem::replace(object, temp_var_binding.create_read_expression(ctx)); - let assignment = create_assignment(&temp_var_binding, expr, ctx); + let object = mem::replace(object, temp_var_binding.create_read_expression(ctx)); + let assignment = create_assignment( + &temp_var_binding, + Self::ensure_optional_expression_wrapper_by_chain_expression(object, ctx), + ctx, + ); let reference = temp_var_binding.create_read_expression(ctx); // `(binding = expr) === null || binding === void 0` self.wrap_optional_check(assignment, reference, ctx) } - // `Foo?.bar()?.zoo?.().#x;` -> `(_Foo$bar$zoo = (_Foo$bar = Foo?.bar())?.zoo)` - // ^^^^^^^^^^^^^^^^ this is a function call, to make sure it has a proper context, - // we also need to assign `Foo?.bar()` to a temp variable, and then - // bind a context when calling `_Foo$bar$zoo`. It turns into - // `_Foo$bar$zoo.call(_Foo$bar)`. - pub(super) fn transform_call_expression_for_bind_context( + /// Transform call expression to bind proper context. + /// + /// * Callee without a private field: + /// `Foo?.bar()?.zoo?.().#x;` -> + /// `(_Foo$bar$zoo = (_Foo$bar = Foo?.bar())?.zoo) === null || _Foo$bar$zoo === void 0 ? void 0 : + /// babelHelpers.assertClassBrand(Foo, _Foo$bar$zoo.call(_Foo$bar), _x)._;` + /// + /// * Callee has a private field: + /// `o?.Foo.#self.getSelf?.().#m?.();` -> + /// `(_ref = o === null || o === void 0 ? void 0 : (_babelHelpers$assertC = + /// babelHelpers.assertClassBrand(Foo, o.Foo, _self)._).getSelf) === null || + /// _ref === void 0 ? void 0 : babelHelpers.assertClassBrand(Foo, _ref$call + /// = _ref.call(_babelHelpers$assertC), _m)._?.call(_ref$call);` + /// + pub(super) fn transform_call_expression_to_bind_proper_context( &mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Expression<'a> { let Expression::CallExpression(call) = expr else { unreachable!() }; - let mut arguments = ctx.ast.move_vec(&mut call.arguments); - let callee = call.callee.get_inner_expression_mut(); - - // TODO(better-documentation): Try to transform the chain expression recursively, because we need proper context - let child_chain_result = self.transform_chain_expression_recursion(callee, ctx); + let callee = &mut call.callee; let member = callee.to_member_expression_mut(); - - let temp_var_binding = if let Some(left) = child_chain_result { - // `o?.Foo.#self.getSelf?.().#m?.();` -> - // `(_ref = o === null || o === void 0 ? void 0 : (_assertClassBrand$_ = _assertClassBrand(Foo, o.Foo, _self)._).getSelf) - // === null || _ref === void 0 ? void 0 : _assertClassBrand(Foo, _ref$call = _ref.call(_assertClassBrand$_), _m)._.call(_ref$call)` - let temp_var_binding = ctx.generate_uid_in_current_scope_based_on_node( - callee, - SymbolFlags::FunctionScopedVariable, + let object = member.object_mut(); + let nested_chain_result = self.transform_first_private_field_expression(object, ctx); + + let context = if let Some(result) = nested_chain_result { + // `o?.Foo.#self.getSelf?.().#m?.();` + // ^^^^^^^^^^^^^^^^^^^^^^ to make sure get `getSelf` call has a proper context, we need to assign + // the parent of callee (i.e `o?.Foo.#self`) to a temp variable, and then bind a context when calling `getSelf`. + let (assignment, context) = self.duplicate_object(ctx.ast.move_expression(object), ctx); + *object = assignment; + *callee = ctx.ast.expression_conditional( + SPAN, + result, + ctx.ast.void_0(SPAN), + ctx.ast.move_expression(callee), ); - - *callee = ctx.ast.expression_conditional(SPAN, left, ctx.ast.void_0(SPAN), { - let member = callee.to_member_expression_mut(); - let object = member.object_mut(); - *object = - create_assignment(&temp_var_binding, ctx.ast.move_expression(object), ctx); - ctx.ast.move_expression(callee) - }); - temp_var_binding + context } else { - let object = member.object_mut(); - let temp_var_binding = ctx.generate_uid_in_current_scope_based_on_node( - object, - SymbolFlags::FunctionScopedVariable, - ); - *object = create_assignment(&temp_var_binding, ctx.ast.move_expression(object), ctx); - temp_var_binding + // `Foo?.bar()?.zoo?.().#x;` -> `(_Foo$bar$zoo = (_Foo$bar = Foo?.bar())?.zoo)` + // ^^^^^^^^^^^^^^^^ this is a function call, to make sure it has a proper context, + // we also need to assign `Foo?.bar()` to a temp variable, and then + // bind a context when calling `_Foo$bar$zoo`. + let (assignment, context) = self.duplicate_object(ctx.ast.move_expression(object), ctx); + *object = assignment; + context }; - self.ctx.var_declarations.insert_var(&temp_var_binding, None, ctx); - *expr = ctx.ast.move_expression(callee); - - let new_expr = self.transform_expression_with_check(expr, ctx); + // After the below transformation, the `callee` will be a temp variable. + let result = self.transform_expression_to_wrap_nullish_check(callee, ctx); { - // Return `.bind(object)`, to be substituted as tag of tagged template expression - let callee = Expression::from(ctx.ast.member_expression_static( + // Modify the original call expression to `.call(object, ...arguments)`, + call.callee = Expression::from(ctx.ast.member_expression_static( SPAN, - ctx.ast.move_expression(expr), + ctx.ast.move_expression(callee), ctx.ast.identifier_name(SPAN, Atom::from("call")), false, )); - arguments.insert(0, Argument::from(temp_var_binding.create_read_expression(ctx))); - *expr = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false); + call.arguments.insert(0, Argument::from(context)); } - - new_expr + result } /// Transform chain expression where includes a private field. @@ -1107,54 +1126,71 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { let (left, chain_expr) = match &mut chain_expr.expression { ChainElement::PrivateFieldExpression(_) => { + // The PrivateFieldExpression must be transformed, so we can convert it to a normal expression here. let mut chain_expr = Self::convert_chain_expression_to_expression(expr, ctx); ( - self.transform_private_field_expression_chain(&mut chain_expr, ctx), + self.transform_private_field_expression_of_chain_expression( + &mut chain_expr, + ctx, + ), Some(chain_expr), ) } ChainElement::CallExpression(call) => { - (self.transform_call_expression_chain(call, ctx), None) + (self.transform_call_expression_of_chain_expression(call, ctx), None) } ChainElement::TSNonNullExpression(non_null) => ( - self.transform_chain_expression_recursion( + self.transform_first_private_field_expression( non_null.expression.get_inner_expression_mut(), ctx, ), None, ), expression @ match_member_expression!(ChainElement) => ( - self.transform_member_expression_chain(expression.to_member_expression_mut(), ctx), + self.transform_member_expression_of_chain_expression( + expression.to_member_expression_mut(), + ctx, + ), None, ), }; if let Some(left) = left { - let mut chain_expr = chain_expr + let chain_expr = chain_expr .unwrap_or_else(|| Self::convert_chain_expression_to_expression(expr, ctx)); - Self::ensure_optional_expression_wrapper_by_chain_expression(&mut chain_expr, ctx); - *expr = ctx.ast.expression_conditional(SPAN, left, ctx.ast.void_0(SPAN), chain_expr); + *expr = ctx.ast.expression_conditional( + SPAN, + left, + ctx.ast.void_0(SPAN), + Self::ensure_optional_expression_wrapper_by_chain_expression(chain_expr, ctx), + ); } else if let Some(chain_expr) = chain_expr { *expr = chain_expr; } } - pub(super) fn transform_chain_expression_recursion( + /// Transform first private field expression in the chain expression. + pub(super) fn transform_first_private_field_expression( &mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Option> { match expr { Expression::PrivateFieldExpression(_) => { - self.transform_private_field_expression_chain(expr, ctx) + // Transform it + self.transform_private_field_expression_of_chain_expression(expr, ctx) } - match_member_expression!(Expression) => { - self.transform_member_expression_chain(expr.to_member_expression_mut(), ctx) + match_member_expression!(Expression) => self + .transform_member_expression_of_chain_expression( + expr.to_member_expression_mut(), + ctx, + ), + Expression::CallExpression(call) => { + self.transform_call_expression_of_chain_expression(call, ctx) } - Expression::CallExpression(call) => self.transform_call_expression_chain(call, ctx), Expression::ChainExpression(_) => { *expr = Self::convert_chain_expression_to_expression(expr, ctx); - self.transform_chain_expression_recursion(expr, ctx) + self.transform_first_private_field_expression(expr, ctx) } _ => { assert_inner_expr_neither_parenthesis_nor_typescript_syntax(expr); @@ -1163,7 +1199,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { } } - pub(super) fn transform_private_field_expression_chain( + pub(super) fn transform_private_field_expression_of_chain_expression( &mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>, @@ -1173,9 +1209,9 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { let object = &mut field_expr.object; // Make sure there is no parenthesis or other TS-syntax expressions *object = ctx.ast.move_expression(object.get_inner_expression_mut()); - let left = self.transform_optional_expression(object, ctx).unwrap_or_else(|| { + let left = self.transform_first_optional_expression(object, ctx).unwrap_or_else(|| { // Even though no optional expression, we still need to transform the object - self.transform_expression_with_check(object, ctx) + self.transform_expression_to_wrap_nullish_check(object, ctx) }); if matches!(ctx.ancestor(1), Ancestor::CallExpressionCallee(_)) { @@ -1190,7 +1226,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { Some(left) } - pub(super) fn transform_member_expression_chain( + pub(super) fn transform_member_expression_of_chain_expression( &mut self, member: &mut MemberExpression<'a>, ctx: &mut TraverseCtx<'a>, @@ -1204,12 +1240,12 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // make sure it can safely access the next member *object = ctx.ast.expression_conditional( SPAN, - self.transform_chain_expression_recursion(object, ctx).unwrap(), + self.transform_first_private_field_expression(object, ctx).unwrap(), ctx.ast.void_0(SPAN), ctx.ast.move_expression(object), ); None - } else if let Some(left) = self.transform_chain_expression_recursion(object, ctx) { + } else if let Some(left) = self.transform_first_private_field_expression(object, ctx) { // TODO(improve-on-babel): It seems that no tests will fail if the code below is removed. if is_optional { *object = ctx.ast.expression_conditional( @@ -1227,7 +1263,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { } } - pub(super) fn transform_call_expression_chain( + pub(super) fn transform_call_expression_of_chain_expression( &mut self, call_expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>, @@ -1235,7 +1271,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { let is_optional = call_expr.optional; let callee = call_expr.callee.get_inner_expression_mut(); if matches!(callee, Expression::PrivateFieldExpression(_)) { - let left = self.transform_optional_expression(callee, ctx); + let left = self.transform_first_optional_expression(callee, ctx); // If the `callee` has no optional expression, we need to transform it using `transform_call_expression` directly. // `Foo.bar.#m?.();` -> `_assertClassBrand(Foo, _Foo$bar = Foo.bar, _m)._?.call(_Foo$bar);` // ^^^^ only the private field is optional @@ -1244,7 +1280,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // eventually the `left` and `call_expr` will be combined in `transform_chain_expression` self.transform_call_expression_impl(call_expr, ctx); left - } else if let Some(left) = self.transform_chain_expression_recursion(callee, ctx) { + } else if let Some(left) = self.transform_first_private_field_expression(callee, ctx) { if is_optional { // TODO(improve-on-babel): It seems that no tests will fail if the code below is removed. // o?.Foo.#self.getSelf?.()?.self.#m();