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