Skip to content

Commit 9fe6eb6

Browse files
committed
copy!
1 parent 3894f90 commit 9fe6eb6

File tree

95 files changed

+29304
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+29304
-0
lines changed

crates/swc_ecma_compiler/src/oxc/common/arrow_function_converter.rs

Lines changed: 1332 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! Utilities to computed key expressions.
2+
3+
use oxc_ast::ast::Expression;
4+
use oxc_semantic::SymbolFlags;
5+
6+
use crate::{
7+
context::{TransformCtx, TraverseCtx},
8+
utils::ast_builder::create_assignment,
9+
};
10+
11+
impl<'a> TransformCtx<'a> {
12+
/// Check if temp var is required for `key`.
13+
///
14+
/// `this` does not have side effects, but in this context, it needs a temp var anyway, because `this`
15+
/// in computed key and `this` within class constructor resolve to different `this` bindings.
16+
/// So we need to create a temp var outside of the class to get the correct `this`.
17+
/// `class C { [this] = 1; }`
18+
/// -> `let _this; _this = this; class C { constructor() { this[_this] = 1; } }`
19+
//
20+
// TODO(improve-on-babel): Can avoid the temp var if key is for a static prop/method,
21+
// as in that case the usage of `this` stays outside the class.
22+
#[expect(clippy::unused_self)] // Taking `self` makes it easier to use.
23+
pub fn key_needs_temp_var(&self, key: &Expression, ctx: &TraverseCtx) -> bool {
24+
match key {
25+
// Literals cannot have side effects.
26+
// e.g. `let x = 'x'; class C { [x] = 1; }` or `class C { ['x'] = 1; }`.
27+
Expression::BooleanLiteral(_)
28+
| Expression::NullLiteral(_)
29+
| Expression::NumericLiteral(_)
30+
| Expression::BigIntLiteral(_)
31+
| Expression::RegExpLiteral(_)
32+
| Expression::StringLiteral(_) => false,
33+
// Template literal cannot have side effects if it has no expressions.
34+
// If it *does* have expressions, but they're all literals, then also cannot have side effects,
35+
// but don't bother checking for that as it shouldn't occur in real world code.
36+
// Why would you write "`x${9}z`" when you can just write "`x9z`"?
37+
// Note: "`x${foo}`" *can* have side effects if `foo` is an object with a `toString` method.
38+
Expression::TemplateLiteral(lit) => !lit.expressions.is_empty(),
39+
// `IdentifierReference`s can have side effects if is unbound.
40+
//
41+
// If var is mutated, it also needs a temp var, because of cases like
42+
// `let x = 1; class { [x] = 1; [++x] = 2; }`
43+
// `++x` is hoisted to before class in output, so `x` in 1st key would get the wrong value
44+
// unless it's hoisted out too.
45+
//
46+
// TODO: Add an exec test for this odd case.
47+
// TODO(improve-on-babel): That case is rare.
48+
// Test for it in first pass over class elements, and avoid temp vars where possible.
49+
Expression::Identifier(ident) => {
50+
match ctx.scoping().get_reference(ident.reference_id()).symbol_id() {
51+
Some(symbol_id) => ctx.scoping().symbol_is_mutated(symbol_id),
52+
None => true,
53+
}
54+
}
55+
// Treat any other expression as possibly having side effects e.g. `foo()`.
56+
// TODO: Do fuller analysis to detect expressions which cannot have side effects.
57+
// e.g. `"x" + "y"`.
58+
_ => true,
59+
}
60+
}
61+
62+
/// Create `let _x;` statement and insert it.
63+
/// Return `_x = x()` assignment, and `_x` identifier referencing same temp var.
64+
pub fn create_computed_key_temp_var(
65+
&self,
66+
key: Expression<'a>,
67+
ctx: &mut TraverseCtx<'a>,
68+
) -> (/* assignment */ Expression<'a>, /* identifier */ Expression<'a>) {
69+
let outer_scope_id = ctx.current_block_scope_id();
70+
// TODO: Handle if is a class expression defined in a function's params.
71+
let binding =
72+
ctx.generate_uid_based_on_node(&key, outer_scope_id, SymbolFlags::BlockScopedVariable);
73+
74+
self.var_declarations.insert_let(&binding, None, ctx);
75+
76+
let assignment = create_assignment(&binding, key, ctx);
77+
let ident = binding.create_read_expression(ctx);
78+
79+
(assignment, ident)
80+
}
81+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//! Utilities to duplicate expressions.
2+
3+
use std::{
4+
mem::{ManuallyDrop, MaybeUninit},
5+
ptr,
6+
};
7+
8+
use oxc_allocator::CloneIn;
9+
use oxc_ast::ast::{AssignmentOperator, Expression};
10+
use oxc_span::SPAN;
11+
use oxc_syntax::reference::ReferenceFlags;
12+
use oxc_traverse::BoundIdentifier;
13+
14+
use crate::context::{TransformCtx, TraverseCtx};
15+
16+
impl<'a> TransformCtx<'a> {
17+
/// Duplicate expression to be used twice.
18+
///
19+
/// If `expr` may have side effects, create a temp var `_expr` and assign to it.
20+
///
21+
/// * `this` -> `this`, `this`
22+
/// * Bound identifier `foo` -> `foo`, `foo`
23+
/// * Unbound identifier `foo` -> `_foo = foo`, `_foo`
24+
/// * Anything else `foo()` -> `_foo = foo()`, `_foo`
25+
///
26+
/// If `mutated_symbol_needs_temp_var` is `true`, temp var will be created for a bound identifier,
27+
/// if it's mutated (assigned to) anywhere in AST.
28+
///
29+
/// Returns 2 `Expression`s. The first may be an `AssignmentExpression`,
30+
/// and must be inserted into output first.
31+
pub(crate) fn duplicate_expression(
32+
&self,
33+
expr: Expression<'a>,
34+
mutated_symbol_needs_temp_var: bool,
35+
ctx: &mut TraverseCtx<'a>,
36+
) -> (Expression<'a>, Expression<'a>) {
37+
let (maybe_assignment, references) =
38+
self.duplicate_expression_multiple::<1>(expr, mutated_symbol_needs_temp_var, ctx);
39+
let [reference] = references;
40+
(maybe_assignment, reference)
41+
}
42+
43+
/// Duplicate expression to be used 3 times.
44+
///
45+
/// If `expr` may have side effects, create a temp var `_expr` and assign to it.
46+
///
47+
/// * `this` -> `this`, `this`, `this`
48+
/// * Bound identifier `foo` -> `foo`, `foo`, `foo`
49+
/// * Unbound identifier `foo` -> `_foo = foo`, `_foo`, `_foo`
50+
/// * Anything else `foo()` -> `_foo = foo()`, `_foo`, `_foo`
51+
///
52+
/// If `mutated_symbol_needs_temp_var` is `true`, temp var will be created for a bound identifier,
53+
/// if it's mutated (assigned to) anywhere in AST.
54+
///
55+
/// Returns 3 `Expression`s. The first may be an `AssignmentExpression`,
56+
/// and must be inserted into output first.
57+
pub(crate) fn duplicate_expression_twice(
58+
&self,
59+
expr: Expression<'a>,
60+
mutated_symbol_needs_temp_var: bool,
61+
ctx: &mut TraverseCtx<'a>,
62+
) -> (Expression<'a>, Expression<'a>, Expression<'a>) {
63+
let (maybe_assignment, references) =
64+
self.duplicate_expression_multiple::<2>(expr, mutated_symbol_needs_temp_var, ctx);
65+
let [reference1, reference2] = references;
66+
(maybe_assignment, reference1, reference2)
67+
}
68+
69+
/// Duplicate expression `N + 1` times.
70+
///
71+
/// If `expr` may have side effects, create a temp var `_expr` and assign to it.
72+
///
73+
/// * `this` -> `this`, [`this`; N]
74+
/// * Bound identifier `foo` -> `foo`, [`foo`; N]
75+
/// * Unbound identifier `foo` -> `_foo = foo`, [`_foo`; N]
76+
/// * Anything else `foo()` -> `_foo = foo()`, [`_foo`; N]
77+
///
78+
/// If `mutated_symbol_needs_temp_var` is `true`, temp var will be created for a bound identifier,
79+
/// if it's mutated (assigned to) anywhere in AST.
80+
///
81+
/// Returns `N + 1` x `Expression`s. The first may be an `AssignmentExpression`,
82+
/// and must be inserted into output first.
83+
pub(crate) fn duplicate_expression_multiple<const N: usize>(
84+
&self,
85+
expr: Expression<'a>,
86+
mutated_symbol_needs_temp_var: bool,
87+
ctx: &mut TraverseCtx<'a>,
88+
) -> (Expression<'a>, [Expression<'a>; N]) {
89+
// TODO: Handle if in a function's params
90+
let temp_var_binding = match &expr {
91+
Expression::Identifier(ident) => {
92+
let reference_id = ident.reference_id();
93+
let reference = ctx.scoping().get_reference(reference_id);
94+
if let Some(symbol_id) = reference.symbol_id()
95+
&& (!mutated_symbol_needs_temp_var
96+
|| !ctx.scoping().symbol_is_mutated(symbol_id))
97+
{
98+
// Reading bound identifier cannot have side effects, so no need for temp var
99+
let binding = BoundIdentifier::new(ident.name, symbol_id);
100+
let references =
101+
create_array(|| binding.create_spanned_read_expression(ident.span, ctx));
102+
return (expr, references);
103+
}
104+
105+
// Previously `x += 1` (`x` read + write), but moving to `_x = x` (`x` read only)
106+
let reference = ctx.scoping_mut().get_reference_mut(reference_id);
107+
*reference.flags_mut() = ReferenceFlags::Read;
108+
109+
self.var_declarations.create_uid_var(&ident.name, ctx)
110+
}
111+
// Reading any of these cannot have side effects, so no need for temp var
112+
Expression::ThisExpression(_)
113+
| Expression::Super(_)
114+
| Expression::BooleanLiteral(_)
115+
| Expression::NullLiteral(_)
116+
| Expression::NumericLiteral(_)
117+
| Expression::BigIntLiteral(_)
118+
| Expression::RegExpLiteral(_)
119+
| Expression::StringLiteral(_) => {
120+
let references = create_array(|| expr.clone_in(ctx.ast.allocator));
121+
return (expr, references);
122+
}
123+
// Template literal cannot have side effects if it has no expressions.
124+
// If it *does* have expressions, but they're all literals, then also cannot have side effects,
125+
// but don't bother checking for that as it shouldn't occur in real world code.
126+
// Why would you write "`x${9}z`" when you can just write "`x9z`"?
127+
// Note: "`x${foo}`" *can* have side effects if `foo` is an object with a `toString` method.
128+
Expression::TemplateLiteral(lit) if lit.expressions.is_empty() => {
129+
let references = create_array(|| {
130+
ctx.ast.expression_template_literal(
131+
lit.span,
132+
ctx.ast.vec_from_iter(lit.quasis.iter().cloned()),
133+
ctx.ast.vec(),
134+
)
135+
});
136+
return (expr, references);
137+
}
138+
// Anything else requires temp var
139+
_ => self.var_declarations.create_uid_var_based_on_node(&expr, ctx),
140+
};
141+
142+
let assignment = ctx.ast.expression_assignment(
143+
SPAN,
144+
AssignmentOperator::Assign,
145+
temp_var_binding.create_target(ReferenceFlags::Write, ctx),
146+
expr,
147+
);
148+
149+
let references = create_array(|| temp_var_binding.create_read_expression(ctx));
150+
151+
(assignment, references)
152+
}
153+
}
154+
155+
/// Create array of length `N`, with each item initialized with provided function `init`.
156+
///
157+
/// Implementation based on:
158+
/// * <https://github.com/rust-lang/rust/issues/62875#issuecomment-513834029>
159+
/// * <https://github.com/rust-lang/rust/issues/61956>
160+
//
161+
// `#[inline]` so compiler can inline `init()`, and it may unroll the loop if `init` is simple enough.
162+
#[inline]
163+
fn create_array<const N: usize, T, I: FnMut() -> T>(mut init: I) -> [T; N] {
164+
let mut array: [MaybeUninit<T>; N] = [const { MaybeUninit::uninit() }; N];
165+
for elem in &mut array {
166+
elem.write(init());
167+
}
168+
// Wrapping in `ManuallyDrop` should not be necessary because `MaybeUninit` does not impl `Drop`,
169+
// but do it anyway just to make sure, as it's mentioned in issues above.
170+
let mut array = ManuallyDrop::new(array);
171+
// SAFETY: All elements of array are initialized.
172+
// `[MaybeUninit<T>; N]` and `[T; N]` have same layout.
173+
unsafe { ptr::from_mut(&mut array).cast::<[T; N]>().read() }
174+
}

0 commit comments

Comments
 (0)