From b23adea4cbd83d84e0df86a059ba857351721feb Mon Sep 17 00:00:00 2001 From: overlookmotel Date: Mon, 9 Dec 2024 18:39:51 +0000 Subject: [PATCH] refactor(transformer): introduce `TransformCtx::duplicate_expression` --- .../oxc_transformer/src/common/duplicate.rs | 132 ++++++++++++++++++ crates/oxc_transformer/src/common/mod.rs | 1 + .../src/es2022/class_properties/private.rs | 67 +-------- .../src/es2022/class_properties/utils.rs | 22 --- 4 files changed, 140 insertions(+), 82 deletions(-) create mode 100644 crates/oxc_transformer/src/common/duplicate.rs diff --git a/crates/oxc_transformer/src/common/duplicate.rs b/crates/oxc_transformer/src/common/duplicate.rs new file mode 100644 index 00000000000000..2060c547dfe073 --- /dev/null +++ b/crates/oxc_transformer/src/common/duplicate.rs @@ -0,0 +1,132 @@ +//! Utilities to duplicate expressions. + +use std::{ + mem::{ManuallyDrop, MaybeUninit}, + ptr, +}; + +use oxc_ast::ast::{AssignmentOperator, Expression}; +use oxc_span::SPAN; +use oxc_syntax::reference::ReferenceFlags; +use oxc_traverse::{BoundIdentifier, TraverseCtx}; + +use crate::TransformCtx; + +impl<'a> TransformCtx<'a> { + /// Duplicate expression to be used twice. + /// + /// If `expr` may have side effects, create a temp var `_expr` and assign to it. + /// + /// * `this` -> `this`, `this` + /// * Bound identifier `foo` -> `foo`, `foo` + /// * Unbound identifier `foo` -> `_foo = foo`, `_foo` + /// * Anything else `foo()` -> `_foo = foo()`, `_foo` + /// + /// Returns 2 `Expression`s. The first may be an `AssignmentExpression`, + /// and must be inserted into output first. + pub(crate) fn duplicate_expression( + &self, + expr: Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> (Expression<'a>, Expression<'a>) { + let (maybe_assignment, references) = self.duplicate_expression_multiple::<1>(expr, ctx); + let [reference] = references; + (maybe_assignment, reference) + } + + /// Duplicate expression to be used 3 times. + /// + /// If `expr` may have side effects, create a temp var `_expr` and assign to it. + /// + /// * `this` -> `this`, `this`, `this` + /// * Bound identifier `foo` -> `foo`, `foo`, `foo` + /// * Unbound identifier `foo` -> `_foo = foo`, `_foo`, `_foo` + /// * Anything else `foo()` -> `_foo = foo()`, `_foo`, `_foo` + /// + /// Returns 3 `Expression`s. The first may be an `AssignmentExpression`, + /// and must be inserted into output first. + #[expect(clippy::similar_names)] + pub(crate) fn duplicate_expression_twice( + &self, + expr: Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> (Expression<'a>, Expression<'a>, Expression<'a>) { + let (maybe_assignment, references) = self.duplicate_expression_multiple::<2>(expr, ctx); + let [reference1, reference2] = references; + (maybe_assignment, reference1, reference2) + } + + /// Duplicate expression `N + 1` times. + /// + /// If `expr` may have side effects, create a temp var `_expr` and assign to it. + /// + /// * `this` -> `this`, [`this`; N] + /// * Bound identifier `foo` -> `foo`, [`foo`; N] + /// * Unbound identifier `foo` -> `_foo = foo`, [`_foo`; N] + /// * Anything else `foo()` -> `_foo = foo()`, [`_foo`; N] + /// + /// Returns `N + 1` x `Expression`s. The first may be an `AssignmentExpression`, + /// and must be inserted into output first. + pub(crate) fn duplicate_expression_multiple( + &self, + expr: Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> (Expression<'a>, [Expression<'a>; N]) { + // TODO: Handle if in a function's params + let temp_var_binding = match &expr { + Expression::Identifier(ident) => { + let reference = ctx.symbols_mut().get_reference_mut(ident.reference_id()); + if let Some(symbol_id) = reference.symbol_id() { + // Reading bound identifier cannot have side effects, so no need for temp var + let binding = BoundIdentifier::new(ident.name.clone(), symbol_id); + let references = + create_array(|| binding.create_spanned_read_expression(ident.span, ctx)); + return (expr, references); + } + + // Previously `x += 1` (`x` read + write), but moving to `_x = x` (`x` read only) + *reference.flags_mut() = ReferenceFlags::Read; + + self.var_declarations.create_uid_var(&ident.name, ctx) + } + Expression::ThisExpression(this) => { + // Reading `this` cannot have side effects, so no need for temp var + let references = create_array(|| ctx.ast.expression_this(this.span)); + return (expr, references); + } + _ => self.var_declarations.create_uid_var_based_on_node(&expr, ctx), + }; + + let assignment = ctx.ast.expression_assignment( + SPAN, + AssignmentOperator::Assign, + temp_var_binding.create_target(ReferenceFlags::Write, ctx), + expr, + ); + + let references = create_array(|| temp_var_binding.create_read_expression(ctx)); + + (assignment, references) + } +} + +/// Create array of length `N`, with each item initialized with provided function `init`. +/// +/// Implementation based on: +/// * +/// * +// +// `#[inline]` so compiler can inline `init()`, and it may unroll the loop if `init` is simple enough. +#[inline] +fn create_array T>(mut init: I) -> [T; N] { + let mut array: [MaybeUninit; N] = [const { MaybeUninit::uninit() }; N]; + for elem in &mut array { + elem.write(init()); + } + // Wrapping in `ManuallyDrop` should not be necessary because `MaybeUninit` does not impl `Drop`, + // but do it anyway just to make sure, as it's mentioned in issues above. + let mut array = ManuallyDrop::new(array); + // SAFETY: All elements of array are initialized. + // `[MaybeUninit; N]` and `[T; N]` have same layout. + unsafe { ptr::from_mut(&mut array).cast::<[T; N]>().read() } +} diff --git a/crates/oxc_transformer/src/common/mod.rs b/crates/oxc_transformer/src/common/mod.rs index c84b29e37c8fc2..859a165cfb14f8 100644 --- a/crates/oxc_transformer/src/common/mod.rs +++ b/crates/oxc_transformer/src/common/mod.rs @@ -8,6 +8,7 @@ use oxc_traverse::{Traverse, TraverseCtx}; use crate::{EnvOptions, TransformCtx}; pub mod arrow_function_converter; +mod duplicate; pub mod helper_loader; pub mod module_imports; pub mod statement_injector; diff --git a/crates/oxc_transformer/src/es2022/class_properties/private.rs b/crates/oxc_transformer/src/es2022/class_properties/private.rs index e2da02cb53a062..98332469f13483 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/private.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/private.rs @@ -6,10 +6,7 @@ use std::mem; use oxc_allocator::Box as ArenaBox; use oxc_ast::{ast::*, NONE}; use oxc_span::SPAN; -use oxc_syntax::{ - reference::{ReferenceFlags, ReferenceId}, - symbol::SymbolId, -}; +use oxc_syntax::{reference::ReferenceId, symbol::SymbolId}; use oxc_traverse::{ ast_operations::get_var_name_from_node, Ancestor, BoundIdentifier, TraverseCtx, }; @@ -19,7 +16,7 @@ use crate::common::helper_loader::Helper; use super::{ private_props::ResolvedPrivateProp, utils::{ - assert_expr_neither_parenthesis_nor_typescript_syntax, create_array, create_assignment, + assert_expr_neither_parenthesis_nor_typescript_syntax, create_assignment, create_underscore_ident_name, }, ClassProperties, @@ -1461,13 +1458,12 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { /// /// Returns 2 `Expression`s. The first must be inserted into output first. fn duplicate_object( - &mut self, + &self, object: Expression<'a>, ctx: &mut TraverseCtx<'a>, ) -> (Expression<'a>, Expression<'a>) { - let (object1, duplicates) = self.duplicate_object_multiple::<1>(object, ctx); - let [object2] = duplicates; - (object1, object2) + assert_expr_neither_parenthesis_nor_typescript_syntax(&object); + self.ctx.duplicate_expression(object, ctx) } /// Duplicate object to be used in triple. @@ -1481,61 +1477,12 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { /// /// Returns 3 `Expression`s. The first must be inserted into output first. fn duplicate_object_twice( - &mut self, + &self, object: Expression<'a>, ctx: &mut TraverseCtx<'a>, ) -> (Expression<'a>, Expression<'a>, Expression<'a>) { - let (object1, duplicates) = self.duplicate_object_multiple::<2>(object, ctx); - let [object2, object3] = duplicates; - (object1, object2, object3) - } - - /// Duplicate object `N + 1` times. - /// - /// If `object` may have side effects, create a temp var `_object` and assign to it. - /// - /// * `this` -> `this`, [`this`; N] - /// * Bound identifier `object` -> `object`, [`object`; N] - /// * Unbound identifier `object` -> `_object = object`, [`_object`; N] - /// * Anything else `foo()` -> `_foo = foo()`, [`_foo`; N] - /// - /// Returns `N + 1` `Expression`s. The first must be inserted into output first. - fn duplicate_object_multiple( - &mut self, - object: Expression<'a>, - ctx: &mut TraverseCtx<'a>, - ) -> (Expression<'a>, [Expression<'a>; N]) { assert_expr_neither_parenthesis_nor_typescript_syntax(&object); - - // TODO: Handle if in a function's params - let temp_var_binding = match &object { - Expression::Identifier(ident) => { - let reference = ctx.symbols_mut().get_reference_mut(ident.reference_id()); - if let Some(symbol_id) = reference.symbol_id() { - // Reading bound identifier cannot have side effects, so no need for temp var - let binding = BoundIdentifier::new(ident.name.clone(), symbol_id); - let duplicates = - create_array(|| binding.create_spanned_read_expression(ident.span, ctx)); - return (object, duplicates); - } - - // Previously `x += 1` (`x` read + write), but moving to `_x = x` (`x` read only) - *reference.flags_mut() = ReferenceFlags::Read; - - self.ctx.var_declarations.create_uid_var(&ident.name, ctx) - } - Expression::ThisExpression(this) => { - // Reading `this` cannot have side effects, so no need for temp var - let duplicates = create_array(|| ctx.ast.expression_this(this.span)); - return (object, duplicates); - } - _ => self.ctx.var_declarations.create_uid_var_based_on_node(&object, ctx), - }; - - let object1 = create_assignment(&temp_var_binding, object, ctx); - let references = create_array(|| temp_var_binding.create_read_expression(ctx)); - - (object1, references) + self.ctx.duplicate_expression_twice(object, ctx) } /// `_classPrivateFieldGet2(_prop, object)` diff --git a/crates/oxc_transformer/src/es2022/class_properties/utils.rs b/crates/oxc_transformer/src/es2022/class_properties/utils.rs index 1548337a9c9589..9324a433307ae0 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/utils.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/utils.rs @@ -1,11 +1,6 @@ //! ES2022: Class Properties //! Utility functions. -use std::{ - mem::{ManuallyDrop, MaybeUninit}, - ptr, -}; - use oxc_ast::ast::*; use oxc_span::SPAN; use oxc_syntax::reference::ReferenceFlags; @@ -66,20 +61,3 @@ pub(super) fn assert_expr_neither_parenthesis_nor_typescript_syntax(expr: &Expre "Should not be: {expr:?}", ); } - -/// Create array of length `N`, with each item initialized with provided function `init`. -#[inline] -pub(super) fn create_array T>(mut init: I) -> [T; N] { - // https://github.com/rust-lang/rust/issues/62875#issuecomment-513834029 - // https://github.com/rust-lang/rust/issues/61956 - let mut array: [MaybeUninit; N] = [const { MaybeUninit::uninit() }; N]; - for elem in &mut array { - elem.write(init()); - } - // Wrapping in `ManuallyDrop` should not be necessary because `MaybeUninit` does not impl `Drop`, - // but do it anyway just to make sure, as it's mentioned in issues above. - let mut array = ManuallyDrop::new(array); - // SAFETY: All elements of array are initialized. - // `[MaybeUninit; N]` and `[T; N]` have equivalent layout. - unsafe { ptr::from_mut(&mut array).cast::<[T; N]>().read() } -}