Skip to main content

turbopack_ecmascript/analyzer/graph/
effects.rs

1use bumpalo::boxed::Box as BumpBox;
2use swc_core::{atoms::Atom, common::Span, ecma::visit::fields::*};
3use turbo_rcstr::RcStr;
4use turbopack_core::resolve::ExportUsage;
5
6use crate::{
7    analyzer::{Bump, BumpVec, JsValue},
8    utils::AstPathRange,
9};
10
11#[derive(Debug)]
12pub struct EffectsBlock<'a> {
13    pub effects: BumpBox<'a, [Effect<'a>]>,
14    pub range: AstPathRange,
15}
16
17impl EffectsBlock<'_> {
18    pub fn is_empty(&self) -> bool {
19        self.effects.is_empty()
20    }
21}
22
23#[derive(Debug)]
24pub enum ConditionalKind<'a> {
25    /// The blocks of an `if` statement without an `else` block.
26    If { then: EffectsBlock<'a> },
27    /// The blocks of an `if ... else` or `if { ... return ... } ...` statement.
28    IfElse {
29        then: EffectsBlock<'a>,
30        r#else: EffectsBlock<'a>,
31    },
32    /// The blocks of an `if ... else` statement.
33    Else { r#else: EffectsBlock<'a> },
34    /// The blocks of an `if { ... return ... } else { ... } ...` or `if { ... }
35    /// else { ... return ... } ...` statement.
36    IfElseMultiple {
37        then: BumpBox<'a, [EffectsBlock<'a>]>,
38        r#else: BumpBox<'a, [EffectsBlock<'a>]>,
39    },
40    /// The expressions on the right side of the `?:` operator.
41    Ternary {
42        then: EffectsBlock<'a>,
43        r#else: EffectsBlock<'a>,
44    },
45    /// The expression on the right side of the `&&` operator.
46    And { expr: EffectsBlock<'a> },
47    /// The expression on the right side of the `||` operator.
48    Or { expr: EffectsBlock<'a> },
49    /// The expression on the right side of the `??` operator.
50    NullishCoalescing { expr: EffectsBlock<'a> },
51    /// The expression on the right side of a labeled statement.
52    Labeled { body: EffectsBlock<'a> },
53}
54
55impl<'a> ConditionalKind<'a> {
56    /// Normalizes all contained values.
57    pub fn normalize(&mut self, arena: &'a Bump) {
58        match self {
59            ConditionalKind::If { then: block }
60            | ConditionalKind::Else { r#else: block }
61            | ConditionalKind::And { expr: block, .. }
62            | ConditionalKind::Or { expr: block, .. }
63            | ConditionalKind::NullishCoalescing { expr: block, .. } => {
64                for effect in block.effects.iter_mut() {
65                    effect.normalize(arena);
66                }
67            }
68            ConditionalKind::IfElse { then, r#else, .. }
69            | ConditionalKind::Ternary { then, r#else, .. } => {
70                for effect in then.effects.iter_mut() {
71                    effect.normalize(arena);
72                }
73                for effect in r#else.effects.iter_mut() {
74                    effect.normalize(arena);
75                }
76            }
77            ConditionalKind::IfElseMultiple { then, r#else, .. } => {
78                for block in then.iter_mut().chain(r#else.iter_mut()) {
79                    for effect in block.effects.iter_mut() {
80                        effect.normalize(arena);
81                    }
82                }
83            }
84            ConditionalKind::Labeled { body } => {
85                for effect in body.effects.iter_mut() {
86                    effect.normalize(arena);
87                }
88            }
89        }
90    }
91}
92
93#[derive(Debug)]
94pub enum EffectArg<'a> {
95    Value(JsValue<'a>),
96    Closure(JsValue<'a>, BumpBox<'a, EffectsBlock<'a>>),
97    Spread,
98}
99
100impl<'a> EffectArg<'a> {
101    /// Normalizes all contained values.
102    pub fn normalize(&mut self, arena: &'a Bump) {
103        match self {
104            EffectArg::Value(value) => value.normalize(arena),
105            EffectArg::Closure(value, effects) => {
106                value.normalize(arena);
107                for effect in effects.effects.iter_mut() {
108                    effect.normalize(arena);
109                }
110            }
111            EffectArg::Spread => {}
112        }
113    }
114}
115
116#[derive(Debug)]
117pub enum Effect<'a> {
118    /// Some condition which affects which effects might be executed. If the
119    /// condition evaluates to some compile-time constant, we can use that
120    /// to determine which effects are executed and remove the others.
121    Conditional {
122        condition: BumpBox<'a, JsValue<'a>>,
123        kind: BumpBox<'a, ConditionalKind<'a>>,
124        /// The ast path to the condition.
125        ast_path: BumpBox<'a, [AstParentKind]>,
126        span: Span,
127    },
128    /// A function call or a new call of a function.
129    Call {
130        func: BumpBox<'a, JsValue<'a>>,
131        args: BumpVec<'a, EffectArg<'a>>,
132        ast_path: BumpBox<'a, [AstParentKind]>,
133        span: Span,
134        in_try: bool,
135        new: bool,
136    },
137    /// A function call or a new call of a property of an object.
138    MemberCall {
139        obj: BumpBox<'a, JsValue<'a>>,
140        prop: BumpBox<'a, JsValue<'a>>,
141        args: BumpVec<'a, EffectArg<'a>>,
142        ast_path: BumpBox<'a, [AstParentKind]>,
143        span: Span,
144        in_try: bool,
145        new: bool,
146    },
147    /// A property access.
148    Member {
149        obj: BumpBox<'a, JsValue<'a>>,
150        prop: BumpBox<'a, JsValue<'a>>,
151        ast_path: BumpBox<'a, [AstParentKind]>,
152        span: Span,
153    },
154    /// A `x in y` expression.
155    In {
156        left: BumpBox<'a, JsValue<'a>>,
157        right: BumpBox<'a, JsValue<'a>>,
158        ast_path: BumpBox<'a, [AstParentKind]>,
159        span: Span,
160    },
161    /// A reference to an imported binding.
162    ImportedBinding {
163        esm_reference_index: usize,
164        export: Option<RcStr>,
165        ast_path: BumpBox<'a, [AstParentKind]>,
166        span: Span,
167    },
168    /// A reference to a free var access.
169    FreeVar {
170        var: Atom,
171        ast_path: BumpBox<'a, [AstParentKind]>,
172        span: Span,
173    },
174    /// A typeof expression
175    TypeOf {
176        arg: BumpBox<'a, JsValue<'a>>,
177        ast_path: BumpBox<'a, [AstParentKind]>,
178        span: Span,
179    },
180    // TODO ImportMeta should be replaced with Member
181    /// A reference to `import.meta`.
182    ImportMeta {
183        ast_path: BumpBox<'a, [AstParentKind]>,
184        span: Span,
185    },
186    /// A dynamic import() call, potentially with export usage extracted from
187    /// usage patterns. Export usage is detected from these patterns:
188    ///
189    /// - `const { a, b } = await import('./lib')` (destructured await)
190    /// - `(await import('./lib')).a` (member access on await)
191    /// - `import('./lib').then(({ a, b }) => {})` (arrow .then() callback)
192    /// - `import('./lib').then(function({ a, b }) {})` (function .then() callback)
193    /// - `import(/* webpackExports: ["a"] */ './lib')` (magic comment)
194    /// - `import(/* turbopackExports: ["a"] */ './lib')` (magic comment)
195    DynamicImport {
196        args: BumpVec<'a, EffectArg<'a>>,
197        ast_path: BumpBox<'a, [AstParentKind]>,
198        span: Span,
199        in_try: bool,
200        /// The export usage extracted from the usage pattern.
201        export_usage: ExportUsage,
202    },
203    /// Unreachable code, e.g. after a `return` statement.
204    Unreachable {
205        start_ast_path: BumpBox<'a, [AstParentKind]>,
206    },
207}
208
209impl<'a> Effect<'a> {
210    /// Normalizes all contained values.
211    pub fn normalize(&mut self, arena: &'a Bump) {
212        match self {
213            Effect::Conditional {
214                condition, kind, ..
215            } => {
216                condition.normalize(arena);
217                kind.normalize(arena);
218            }
219            Effect::Call { func, args, .. } => {
220                func.normalize(arena);
221                for arg in args.iter_mut() {
222                    arg.normalize(arena);
223                }
224            }
225            Effect::MemberCall {
226                obj, prop, args, ..
227            } => {
228                obj.normalize(arena);
229                prop.normalize(arena);
230                for arg in args.iter_mut() {
231                    arg.normalize(arena);
232                }
233            }
234            Effect::Member { obj, prop, .. } => {
235                obj.normalize(arena);
236                prop.normalize(arena);
237            }
238            Effect::In { left, right, .. } => {
239                left.normalize(arena);
240                right.normalize(arena);
241            }
242            Effect::DynamicImport { args, .. } => {
243                for arg in args.iter_mut() {
244                    arg.normalize(arena);
245                }
246            }
247            Effect::ImportedBinding { .. } => {}
248            Effect::TypeOf { arg, .. } => {
249                arg.normalize(arena);
250            }
251            Effect::FreeVar { .. } => {}
252            Effect::ImportMeta { .. } => {}
253            Effect::Unreachable { .. } => {}
254        }
255    }
256}
257
258#[derive(Debug)]
259pub enum AssignmentScope {
260    /// assigned in the root scope
261    ModuleEval,
262    /// assigned in a function scopes
263    Function,
264}
265
266/// Tracks the locations where this was assigned to:
267/// This is used to track the _liveness_ of exports.
268#[derive(Debug, Copy, Clone, PartialEq, Eq)]
269pub enum AssignmentScopes {
270    /// assigned only in the root scope
271    AllInModuleEvalScope,
272    /// assigned in any set of function scopes
273    AllInFunctionScopes,
274    /// assigned in both module and function scopes
275    Mixed,
276}
277impl AssignmentScopes {
278    pub fn new(initial: AssignmentScope) -> Self {
279        match initial {
280            AssignmentScope::ModuleEval => AssignmentScopes::AllInModuleEvalScope,
281            AssignmentScope::Function => AssignmentScopes::AllInFunctionScopes,
282        }
283    }
284
285    pub fn merge(self, other: AssignmentScope) -> Self {
286        // If the other assignment kind is the same as the current one, return the current one.
287        if self == Self::new(other) {
288            self
289        } else {
290            AssignmentScopes::Mixed
291        }
292    }
293}