Skip to content
Draft
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
1,332 changes: 1,332 additions & 0 deletions crates/swc_ecma_transformer/oxc/common/arrow_function_converter.rs

Large diffs are not rendered by default.

81 changes: 81 additions & 0 deletions crates/swc_ecma_transformer/oxc/common/computed_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//! Utilities to computed key expressions.
use oxc_ast::ast::Expression;
use oxc_semantic::SymbolFlags;

use crate::{
context::{TransformCtx, TraverseCtx},
utils::ast_builder::create_assignment,
};

impl<'a> TransformCtx<'a> {
/// Check if temp var is required for `key`.
///
/// `this` does not have side effects, but in this context, it needs a temp var anyway, because `this`
/// in computed key and `this` within class constructor resolve to different `this` bindings.
/// So we need to create a temp var outside of the class to get the correct `this`.
/// `class C { [this] = 1; }`
/// -> `let _this; _this = this; class C { constructor() { this[_this] = 1; } }`
//
// TODO(improve-on-babel): Can avoid the temp var if key is for a static prop/method,
// as in that case the usage of `this` stays outside the class.
#[expect(clippy::unused_self)] // Taking `self` makes it easier to use.
pub fn key_needs_temp_var(&self, key: &Expression, ctx: &TraverseCtx) -> bool {
match key {
// Literals cannot have side effects.
// e.g. `let x = 'x'; class C { [x] = 1; }` or `class C { ['x'] = 1; }`.
Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::NumericLiteral(_)
| Expression::BigIntLiteral(_)
| Expression::RegExpLiteral(_)
| Expression::StringLiteral(_) => false,
// Template literal cannot have side effects if it has no expressions.
// If it *does* have expressions, but they're all literals, then also cannot have side effects,
// but don't bother checking for that as it shouldn't occur in real world code.
// Why would you write "`x${9}z`" when you can just write "`x9z`"?
// Note: "`x${foo}`" *can* have side effects if `foo` is an object with a `toString` method.
Expression::TemplateLiteral(lit) => !lit.expressions.is_empty(),
// `IdentifierReference`s can have side effects if is unbound.
//
// If var is mutated, it also needs a temp var, because of cases like
// `let x = 1; class { [x] = 1; [++x] = 2; }`
// `++x` is hoisted to before class in output, so `x` in 1st key would get the wrong value
// unless it's hoisted out too.
//
// TODO: Add an exec test for this odd case.
// TODO(improve-on-babel): That case is rare.
// Test for it in first pass over class elements, and avoid temp vars where possible.
Expression::Identifier(ident) => {
match ctx.scoping().get_reference(ident.reference_id()).symbol_id() {
Some(symbol_id) => ctx.scoping().symbol_is_mutated(symbol_id),
None => true,
}
}
// Treat any other expression as possibly having side effects e.g. `foo()`.
// TODO: Do fuller analysis to detect expressions which cannot have side effects.
// e.g. `"x" + "y"`.
_ => true,
}
}

/// Create `let _x;` statement and insert it.
/// Return `_x = x()` assignment, and `_x` identifier referencing same temp var.
pub fn create_computed_key_temp_var(
&self,
key: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (/* assignment */ Expression<'a>, /* identifier */ Expression<'a>) {
let outer_scope_id = ctx.current_block_scope_id();
// TODO: Handle if is a class expression defined in a function's params.
let binding =
ctx.generate_uid_based_on_node(&key, outer_scope_id, SymbolFlags::BlockScopedVariable);

self.var_declarations.insert_let(&binding, None, ctx);

let assignment = create_assignment(&binding, key, ctx);
let ident = binding.create_read_expression(ctx);

(assignment, ident)
}
}
174 changes: 174 additions & 0 deletions crates/swc_ecma_transformer/oxc/common/duplicate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//! Utilities to duplicate expressions.
use std::{
mem::{ManuallyDrop, MaybeUninit},
ptr,
};

use oxc_allocator::CloneIn;
use oxc_ast::ast::{AssignmentOperator, Expression};
use oxc_span::SPAN;
use oxc_syntax::reference::ReferenceFlags;
use oxc_traverse::BoundIdentifier;

use crate::context::{TransformCtx, TraverseCtx};

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`
///
/// If `mutated_symbol_needs_temp_var` is `true`, temp var will be created for a bound identifier,
/// if it's mutated (assigned to) anywhere in AST.
///
/// 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>,
mutated_symbol_needs_temp_var: bool,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, Expression<'a>) {
let (maybe_assignment, references) =
self.duplicate_expression_multiple::<1>(expr, mutated_symbol_needs_temp_var, 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`
///
/// If `mutated_symbol_needs_temp_var` is `true`, temp var will be created for a bound identifier,
/// if it's mutated (assigned to) anywhere in AST.
///
/// Returns 3 `Expression`s. The first may be an `AssignmentExpression`,
/// and must be inserted into output first.
pub(crate) fn duplicate_expression_twice(
&self,
expr: Expression<'a>,
mutated_symbol_needs_temp_var: bool,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, Expression<'a>, Expression<'a>) {
let (maybe_assignment, references) =
self.duplicate_expression_multiple::<2>(expr, mutated_symbol_needs_temp_var, 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]
///
/// If `mutated_symbol_needs_temp_var` is `true`, temp var will be created for a bound identifier,
/// if it's mutated (assigned to) anywhere in AST.
///
/// 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>,
mutated_symbol_needs_temp_var: bool,
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_id = ident.reference_id();
let reference = ctx.scoping().get_reference(reference_id);
if let Some(symbol_id) = reference.symbol_id()
&& (!mutated_symbol_needs_temp_var
|| !ctx.scoping().symbol_is_mutated(symbol_id))
{
// Reading bound identifier cannot have side effects, so no need for temp var
let binding = BoundIdentifier::new(ident.name, 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)
let reference = ctx.scoping_mut().get_reference_mut(reference_id);
*reference.flags_mut() = ReferenceFlags::Read;

self.var_declarations.create_uid_var(&ident.name, ctx)
}
// Reading any of these cannot have side effects, so no need for temp var
Expression::ThisExpression(_)
| Expression::Super(_)
| Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::NumericLiteral(_)
| Expression::BigIntLiteral(_)
| Expression::RegExpLiteral(_)
| Expression::StringLiteral(_) => {
let references = create_array(|| expr.clone_in(ctx.ast.allocator));
return (expr, references);
}
// Template literal cannot have side effects if it has no expressions.
// If it *does* have expressions, but they're all literals, then also cannot have side effects,
// but don't bother checking for that as it shouldn't occur in real world code.
// Why would you write "`x${9}z`" when you can just write "`x9z`"?
// Note: "`x${foo}`" *can* have side effects if `foo` is an object with a `toString` method.
Expression::TemplateLiteral(lit) if lit.expressions.is_empty() => {
let references = create_array(|| {
ctx.ast.expression_template_literal(
lit.span,
ctx.ast.vec_from_iter(lit.quasis.iter().cloned()),
ctx.ast.vec(),
)
});
return (expr, references);
}
// Anything else requires temp var
_ => 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() }
}
Loading
Loading