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        // Whether this value from this member access only ends up being used as a truthiness check
154        // (and the full value doesn't escape).
155        in_truthiness_context: bool,
156    },
157    /// A property access created by an object destructuring pattern.
158    DestructuredMember {
159        obj: BumpBox<'a, JsValue<'a>>,
160        prop: BumpBox<'a, JsValue<'a>>,
161        span: Span,
162    },
163    /// A `x in y` expression.
164    In {
165        left: BumpBox<'a, JsValue<'a>>,
166        right: BumpBox<'a, JsValue<'a>>,
167        ast_path: BumpBox<'a, [AstParentKind]>,
168        span: Span,
169    },
170    /// A reference to an imported binding.
171    ImportedBinding {
172        esm_reference_index: usize,
173        export: Option<RcStr>,
174        /// A single member read from a named import (`z.member`), used only to narrow the part.
175        member: Option<RcStr>,
176        ast_path: BumpBox<'a, [AstParentKind]>,
177        span: Span,
178    },
179    /// A reference to a free var access.
180    FreeVar {
181        var: Atom,
182        ast_path: BumpBox<'a, [AstParentKind]>,
183        span: Span,
184    },
185    /// A typeof expression
186    TypeOf {
187        arg: BumpBox<'a, JsValue<'a>>,
188        ast_path: BumpBox<'a, [AstParentKind]>,
189        span: Span,
190    },
191    // TODO ImportMeta should be replaced with Member
192    /// A reference to `import.meta`.
193    ImportMeta {
194        ast_path: BumpBox<'a, [AstParentKind]>,
195        span: Span,
196    },
197    /// A dynamic import() call, potentially with export usage extracted from
198    /// usage patterns. Export usage is detected from these patterns:
199    ///
200    /// - `const { a, b } = await import('./lib')` (destructured await)
201    /// - `(await import('./lib')).a` (member access on await)
202    /// - `import('./lib').then(({ a, b }) => {})` (arrow .then() callback)
203    /// - `import('./lib').then(function({ a, b }) {})` (function .then() callback)
204    /// - `import(/* webpackExports: ["a"] */ './lib')` (magic comment)
205    /// - `import(/* turbopackExports: ["a"] */ './lib')` (magic comment)
206    DynamicImport {
207        args: BumpVec<'a, EffectArg<'a>>,
208        ast_path: BumpBox<'a, [AstParentKind]>,
209        span: Span,
210        in_try: bool,
211        /// The export usage extracted from the usage pattern.
212        export_usage: ExportUsage,
213    },
214    /// Unreachable code, e.g. after a `return` statement.
215    Unreachable {
216        start_ast_path: BumpBox<'a, [AstParentKind]>,
217    },
218}
219
220impl<'a> Effect<'a> {
221    /// Normalizes all contained values.
222    pub fn normalize(&mut self, arena: &'a Bump) {
223        match self {
224            Effect::Conditional {
225                condition, kind, ..
226            } => {
227                condition.normalize(arena);
228                kind.normalize(arena);
229            }
230            Effect::Call { func, args, .. } => {
231                func.normalize(arena);
232                for arg in args.iter_mut() {
233                    arg.normalize(arena);
234                }
235            }
236            Effect::MemberCall {
237                obj, prop, args, ..
238            } => {
239                obj.normalize(arena);
240                prop.normalize(arena);
241                for arg in args.iter_mut() {
242                    arg.normalize(arena);
243                }
244            }
245            Effect::Member { obj, prop, .. } => {
246                obj.normalize(arena);
247                prop.normalize(arena);
248            }
249            Effect::DestructuredMember { obj, prop, .. } => {
250                obj.normalize(arena);
251                prop.normalize(arena);
252            }
253            Effect::In { left, right, .. } => {
254                left.normalize(arena);
255                right.normalize(arena);
256            }
257            Effect::DynamicImport { args, .. } => {
258                for arg in args.iter_mut() {
259                    arg.normalize(arena);
260                }
261            }
262            Effect::ImportedBinding { .. } => {}
263            Effect::TypeOf { arg, .. } => {
264                arg.normalize(arena);
265            }
266            Effect::FreeVar { .. } => {}
267            Effect::ImportMeta { .. } => {}
268            Effect::Unreachable { .. } => {}
269        }
270    }
271}
272
273#[derive(Debug)]
274pub enum AssignmentScope {
275    /// assigned in the root scope
276    ModuleEval,
277    /// assigned in a function scopes
278    Function,
279}
280
281/// Tracks the locations where this was assigned to:
282/// This is used to track the _liveness_ of exports.
283#[derive(Debug, Copy, Clone, PartialEq, Eq)]
284pub enum AssignmentScopes {
285    /// assigned only in the root scope
286    AllInModuleEvalScope,
287    /// assigned in any set of function scopes
288    AllInFunctionScopes,
289    /// assigned in both module and function scopes
290    Mixed,
291}
292impl AssignmentScopes {
293    pub fn new(initial: AssignmentScope) -> Self {
294        match initial {
295            AssignmentScope::ModuleEval => AssignmentScopes::AllInModuleEvalScope,
296            AssignmentScope::Function => AssignmentScopes::AllInFunctionScopes,
297        }
298    }
299
300    pub fn merge(self, other: AssignmentScope) -> Self {
301        // If the other assignment kind is the same as the current one, return the current one.
302        if self == Self::new(other) {
303            self
304        } else {
305            AssignmentScopes::Mixed
306        }
307    }
308}