|
| 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