mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
refactor(transformer): introduce TransformCtx::duplicate_expression (#7754)
Move implementation of `duplicate_object` from the class properties transform into `TransformCtx::duplicate_expression`, so it can also be used in other transforms.
This commit is contained in:
parent
3c1b2bf439
commit
b500f5527a
4 changed files with 140 additions and 82 deletions
132
crates/oxc_transformer/src/common/duplicate.rs
Normal file
132
crates/oxc_transformer/src/common/duplicate.rs
Normal file
|
|
@ -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<const N: usize>(
|
||||
&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:
|
||||
/// * <https://github.com/rust-lang/rust/issues/62875#issuecomment-513834029>
|
||||
/// * <https://github.com/rust-lang/rust/issues/61956>
|
||||
//
|
||||
// `#[inline]` so compiler can inline `init()`, and it may unroll the loop if `init` is simple enough.
|
||||
#[inline]
|
||||
fn create_array<const N: usize, T, I: FnMut() -> T>(mut init: I) -> [T; N] {
|
||||
let mut array: [MaybeUninit<T>; 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<T>; N]` and `[T; N]` have same layout.
|
||||
unsafe { ptr::from_mut(&mut array).cast::<[T; N]>().read() }
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<const N: usize>(
|
||||
&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)`
|
||||
|
|
|
|||
|
|
@ -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<const N: usize, T, I: FnMut() -> 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<T>; 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<T>; N]` and `[T; N]` have equivalent layout.
|
||||
unsafe { ptr::from_mut(&mut array).cast::<[T; N]>().read() }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue