Skip to main content

turbopack_ecmascript/analyzer/graph/
eval_context.rs

1use std::sync::Arc;
2
3use anyhow::{Ok, Result};
4use rustc_hash::FxHashSet;
5use swc_core::{
6    base::try_with_handler,
7    common::{GLOBALS, Mark, SourceMap, SyntaxContext, comments::Comments, sync::Lrc},
8    ecma::{ast::*, atoms::atom},
9};
10use turbo_rcstr::{RcStr, rcstr};
11
12use crate::{
13    SpecifiedModuleType,
14    analyzer::{
15        Bump, BumpVec, ConstantNumber, ConstantValue, ImportMap, JsValue, ObjectPart,
16        WellKnownObjectKind, is_unresolved,
17    },
18    references::constant_value::parse_single_expr_lit,
19    utils::unparen,
20};
21
22/// A context used for assembling the evaluation graph.
23#[derive(Debug)]
24pub struct EvalContext {
25    /// Should be the same [`Mark`] used by [`swc_core::ecma::transforms::base::resolver`].
26    pub(crate) unresolved_mark: Mark,
27    /// Should be the same [`Mark`] used by [`swc_core::ecma::transforms::base::resolver`].
28    pub(crate) top_level_mark: Mark,
29    pub(crate) imports: ImportMap,
30    pub(crate) force_free_values: Arc<FxHashSet<Id>>,
31}
32
33impl EvalContext {
34    /// Produce a new [`EvalContext`] from a [`Program`].
35    ///
36    /// If you wish to support `webpackIgnore` or `turbopackIgnore` comments, you must pass those
37    /// in, since the AST does not include comments by default.
38    ///
39    /// You should use the same `unresolved_mark` and `top_level_mark` [Mark] values for this
40    /// context that you passed to [`swc_core::ecma::transforms::base::resolver`].
41    pub fn new(
42        module: Option<&Program>,
43        unresolved_mark: Mark,
44        top_level_mark: Mark,
45        force_free_values: Arc<FxHashSet<Id>>,
46        comments: Option<&dyn Comments>,
47    ) -> Self {
48        Self {
49            unresolved_mark,
50            top_level_mark,
51            imports: module.map_or(ImportMap::default(), |m| {
52                ImportMap::analyze(unresolved_mark, m, comments)
53            }),
54            force_free_values,
55        }
56    }
57
58    pub fn is_esm(&self, specified_type: SpecifiedModuleType) -> bool {
59        self.imports.is_esm(specified_type)
60    }
61
62    pub(super) fn eval_prop_name<'a>(&self, arena: &'a Bump, prop: &PropName) -> JsValue<'a> {
63        match prop {
64            PropName::Ident(ident) => ident.sym.clone().into(),
65            PropName::Str(str) => str.value.clone().to_atom_lossy().into_owned().into(),
66            PropName::Num(num) => num.value.into(),
67            PropName::Computed(ComputedPropName { expr, .. }) => self.eval(arena, expr),
68            PropName::BigInt(bigint) => (*bigint.value.clone()).into(),
69        }
70    }
71
72    pub(super) fn eval_member_prop<'a>(
73        &self,
74        arena: &'a Bump,
75        prop: &MemberProp,
76    ) -> Option<JsValue<'a>> {
77        match prop {
78            MemberProp::Ident(ident) => Some(ident.sym.clone().into()),
79            MemberProp::Computed(ComputedPropName { expr, .. }) => Some(self.eval(arena, expr)),
80            MemberProp::PrivateName(_) => None,
81        }
82    }
83
84    fn eval_tpl<'a>(&self, arena: &'a Bump, e: &Tpl, raw: bool) -> JsValue<'a> {
85        debug_assert!(e.quasis.len() == e.exprs.len() + 1);
86
87        let mut values = vec![];
88
89        for idx in 0..(e.quasis.len() + e.exprs.len()) {
90            if idx.is_multiple_of(2) {
91                let idx = idx / 2;
92                let e = &e.quasis[idx];
93                if raw {
94                    // Ignore empty strings quasis, happens frequently with e.g. after the
95                    // placeholder in `something${v}`.
96                    if !e.raw.is_empty() {
97                        values.push(JsValue::from(e.raw.clone()));
98                    }
99                } else {
100                    match &e.cooked {
101                        Some(v) => {
102                            if !v.is_empty() {
103                                values.push(JsValue::from(v.clone().to_atom_lossy().into_owned()));
104                            }
105                        }
106                        // This is actually unreachable
107                        None => return JsValue::unknown_empty(true, rcstr!("")),
108                    }
109                }
110            } else {
111                let idx = idx / 2;
112                let e = &e.exprs[idx];
113
114                values.push(self.eval(arena, e));
115            }
116        }
117
118        match values.len() {
119            0 => JsValue::Constant(ConstantValue::Str(rcstr!("").into())),
120            1 => values.into_iter().next().unwrap(),
121            _ => JsValue::concat(BumpVec::from_iter_in(arena, values)),
122        }
123    }
124
125    pub(super) fn eval_ident<'a>(&self, arena: &'a Bump, i: &Ident) -> JsValue<'a> {
126        let id = i.to_id();
127        if let Some(imported) = self.imports.get_import(arena, &id) {
128            return imported;
129        }
130        if is_unresolved(i, self.unresolved_mark) || self.force_free_values.contains(&id) {
131            // These are special globals that we shouldn't consider to be free variables and we can
132            // model their values mostly useful for truthy/falsy checks.
133            match i.sym.as_str() {
134                "undefined" => JsValue::Constant(ConstantValue::Undefined),
135                "NaN" => JsValue::Constant(ConstantValue::Num(f64::NAN.into())),
136                "Infinity" => JsValue::Constant(ConstantValue::Num(f64::INFINITY.into())),
137                _ => JsValue::FreeVar(i.sym.clone()),
138            }
139        } else {
140            JsValue::Variable(id)
141        }
142    }
143
144    pub fn eval<'a>(&self, arena: &'a Bump, e: &Expr) -> JsValue<'a> {
145        debug_assert!(
146            GLOBALS.is_set(),
147            "Eval requires globals from its parsed result"
148        );
149        match e {
150            Expr::Paren(e) => self.eval(arena, &e.expr),
151            Expr::Lit(e) => JsValue::Constant(e.clone().into()),
152            Expr::Ident(i) => self.eval_ident(arena, i),
153
154            Expr::Unary(UnaryExpr {
155                op: op!("void"),
156                // Only treat literals as constant undefined, allowing arbitrary values inside here
157                // would mean that they can have sideeffects, and `JsValue::Constant` can't model
158                // that.
159                arg: box Expr::Lit(_),
160                ..
161            }) => JsValue::Constant(ConstantValue::Undefined),
162
163            Expr::Unary(UnaryExpr {
164                op: op!(unary, "-"),
165                arg: box Expr::Lit(Lit::Num(n)),
166                ..
167            }) => JsValue::Constant(ConstantValue::Num(ConstantNumber(-n.value))),
168
169            Expr::Unary(UnaryExpr {
170                op: op!("!"), arg, ..
171            }) => {
172                let arg = self.eval(arena, arg);
173
174                JsValue::logical_not(arena, arg)
175            }
176
177            Expr::Unary(UnaryExpr {
178                op: op!("typeof"),
179                arg,
180                ..
181            }) => {
182                let arg = self.eval(arena, arg);
183
184                JsValue::type_of(arena, arg)
185            }
186
187            Expr::Bin(BinExpr {
188                op: op!(bin, "+"),
189                left,
190                right,
191                ..
192            }) => {
193                let l = self.eval(arena, left);
194                let r = self.eval(arena, right);
195
196                match (l, r) {
197                    (JsValue::Add(c, mut l), r) => {
198                        let total = c + r.total_nodes();
199                        l.push(arena, r);
200                        JsValue::Add(total, l)
201                    }
202                    (l, r) => JsValue::add(BumpVec::from_iter_in(arena, [l, r])),
203                }
204            }
205
206            Expr::Bin(BinExpr {
207                op: op!("&&"),
208                left,
209                right,
210                ..
211            }) => JsValue::logical_and(BumpVec::from_iter_in(
212                arena,
213                [self.eval(arena, left), self.eval(arena, right)],
214            )),
215
216            Expr::Bin(BinExpr {
217                op: op!("||"),
218                left,
219                right,
220                ..
221            }) => JsValue::logical_or(BumpVec::from_iter_in(
222                arena,
223                [self.eval(arena, left), self.eval(arena, right)],
224            )),
225
226            Expr::Bin(BinExpr {
227                op: op!("??"),
228                left,
229                right,
230                ..
231            }) => JsValue::nullish_coalescing(BumpVec::from_iter_in(
232                arena,
233                [self.eval(arena, left), self.eval(arena, right)],
234            )),
235
236            Expr::Bin(BinExpr {
237                op: op!("=="),
238                left,
239                right,
240                ..
241            }) => JsValue::equal(arena, self.eval(arena, left), self.eval(arena, right)),
242
243            Expr::Bin(BinExpr {
244                op: op!("!="),
245                left,
246                right,
247                ..
248            }) => JsValue::not_equal(arena, self.eval(arena, left), self.eval(arena, right)),
249
250            Expr::Bin(BinExpr {
251                op: op!("==="),
252                left,
253                right,
254                ..
255            }) => JsValue::strict_equal(arena, self.eval(arena, left), self.eval(arena, right)),
256
257            Expr::Bin(BinExpr {
258                op: op!("!=="),
259                left,
260                right,
261                ..
262            }) => JsValue::strict_not_equal(arena, self.eval(arena, left), self.eval(arena, right)),
263
264            Expr::Bin(BinExpr {
265                op: op!("in"),
266                left,
267                right,
268                ..
269            }) => JsValue::r#in(arena, self.eval(arena, left), self.eval(arena, right)),
270
271            &Expr::Cond(CondExpr {
272                box ref cons,
273                box ref alt,
274                box ref test,
275                ..
276            }) => {
277                let test = self.eval(arena, test);
278                if let Some(truthy) = test.is_truthy() {
279                    if truthy {
280                        self.eval(arena, cons)
281                    } else {
282                        self.eval(arena, alt)
283                    }
284                } else {
285                    JsValue::tenary(arena, test, self.eval(arena, cons), self.eval(arena, alt))
286                }
287            }
288
289            Expr::Tpl(e) => self.eval_tpl(arena, e, false),
290
291            Expr::TaggedTpl(TaggedTpl {
292                tag:
293                    box Expr::Member(MemberExpr {
294                        obj: box Expr::Ident(tag_obj),
295                        prop: MemberProp::Ident(tag_prop),
296                        ..
297                    }),
298                tpl,
299                ..
300            }) => {
301                if &*tag_obj.sym == "String"
302                    && &*tag_prop.sym == "raw"
303                    && is_unresolved(tag_obj, self.unresolved_mark)
304                {
305                    self.eval_tpl(arena, tpl, true)
306                } else {
307                    JsValue::unknown_empty(
308                        true,
309                        rcstr!("tagged template literal is not supported yet"),
310                    )
311                }
312            }
313
314            Expr::Fn(expr) => {
315                if let Some(ident) = &expr.ident {
316                    JsValue::Variable(ident.to_id())
317                } else {
318                    JsValue::Variable((
319                        format!("*anonymous function {}*", expr.function.span.lo.0).into(),
320                        SyntaxContext::empty(),
321                    ))
322                }
323            }
324            Expr::Arrow(expr) => JsValue::Variable((
325                format!("*arrow function {}*", expr.span.lo.0).into(),
326                SyntaxContext::empty(),
327            )),
328
329            Expr::Await(AwaitExpr { arg, .. }) => JsValue::awaited(arena, self.eval(arena, arg)),
330
331            Expr::Seq(e) => {
332                let mut seq = e.exprs.iter().map(|e| self.eval(arena, e)).peekable();
333                let mut side_effects = false;
334                let mut last = seq.next().unwrap();
335                for e in seq {
336                    side_effects |= last.has_side_effects();
337                    last = e;
338                }
339                if side_effects {
340                    last.make_unknown(true, rcstr!("sequence with side effects"));
341                }
342                last
343            }
344
345            Expr::Member(MemberExpr {
346                obj,
347                prop: MemberProp::Ident(prop),
348                ..
349            }) => {
350                let obj = self.eval(arena, obj);
351                JsValue::member(arena, obj, prop.sym.clone().into())
352            }
353
354            Expr::Member(MemberExpr {
355                obj,
356                prop: MemberProp::Computed(computed),
357                ..
358            }) => {
359                let obj = self.eval(arena, obj);
360                let prop = self.eval(arena, &computed.expr);
361                JsValue::member(arena, obj, prop)
362            }
363
364            Expr::New(NewExpr {
365                callee: box callee,
366                args,
367                ..
368            }) => {
369                let args = args.as_deref().unwrap_or(&[]);
370                // We currently do not handle spreads.
371                if args.iter().any(|arg| arg.spread.is_some()) {
372                    return JsValue::unknown_empty(
373                        true,
374                        rcstr!("spread in new calls is not supported"),
375                    );
376                }
377
378                JsValue::new_from_iter(
379                    arena,
380                    self.eval(arena, callee),
381                    args.iter().map(|arg| self.eval(arena, &arg.expr)),
382                )
383            }
384
385            Expr::Call(CallExpr {
386                callee: Callee::Expr(box callee),
387                args,
388                ..
389            }) => {
390                // We currently do not handle spreads.
391                if args.iter().any(|arg| arg.spread.is_some()) {
392                    return JsValue::unknown_empty(
393                        true,
394                        rcstr!("spread in function calls is not supported"),
395                    );
396                }
397
398                if let Expr::Member(MemberExpr { obj, prop, .. }) = unparen(callee) {
399                    let prop = match prop {
400                        MemberProp::Ident(i) => i.sym.clone().into(),
401                        MemberProp::PrivateName(_) => {
402                            return JsValue::unknown_empty(
403                                false,
404                                rcstr!("private names in function calls is not supported"),
405                            );
406                        }
407                        MemberProp::Computed(ComputedPropName { expr, .. }) => {
408                            self.eval(arena, expr)
409                        }
410                    };
411                    let obj = self.eval(arena, obj);
412                    JsValue::member_call_from_iter(
413                        arena,
414                        obj,
415                        prop,
416                        args.iter().map(|arg| self.eval(arena, &arg.expr)),
417                    )
418                } else {
419                    JsValue::call_from_iter(
420                        arena,
421                        self.eval(arena, callee),
422                        args.iter().map(|arg| self.eval(arena, &arg.expr)),
423                    )
424                }
425            }
426
427            Expr::Call(CallExpr {
428                callee: Callee::Super(_),
429                args,
430                ..
431            }) => {
432                // We currently do not handle spreads.
433                if args.iter().any(|arg| arg.spread.is_some()) {
434                    return JsValue::unknown_empty(
435                        true,
436                        rcstr!("spread in function calls is not supported"),
437                    );
438                }
439
440                let args = bumpalo::collections::Vec::from_iter_in(
441                    args.iter().map(|arg| self.eval(arena, &arg.expr)),
442                    arena,
443                )
444                .into_boxed_slice();
445
446                JsValue::super_call(args)
447            }
448
449            Expr::Call(CallExpr {
450                callee: Callee::Import(_),
451                args,
452                ..
453            }) => {
454                // We currently do not handle spreads.
455                if args.iter().any(|arg| arg.spread.is_some()) {
456                    return JsValue::unknown_empty(
457                        true,
458                        rcstr!("spread in import() is not supported"),
459                    );
460                }
461                JsValue::call_from_iter(
462                    arena,
463                    JsValue::FreeVar(atom!("import")),
464                    args.iter().map(|arg| self.eval(arena, &arg.expr)),
465                )
466            }
467
468            Expr::Array(arr) => {
469                if arr.elems.iter().flatten().any(|v| v.spread.is_some()) {
470                    return JsValue::unknown_empty(true, rcstr!("spread is not supported"));
471                }
472
473                let arr = BumpVec::from_iter_in(
474                    arena,
475                    arr.elems.iter().map(|e| match e {
476                        Some(e) => self.eval(arena, &e.expr),
477                        _ => JsValue::Constant(ConstantValue::Undefined),
478                    }),
479                );
480                JsValue::array(arr)
481            }
482
483            Expr::Object(obj) => JsValue::object(BumpVec::from_iter_in(
484                arena,
485                obj.props.iter().map(|prop| match prop {
486                    PropOrSpread::Spread(SpreadElement { expr, .. }) => {
487                        ObjectPart::Spread(self.eval(arena, expr))
488                    }
489                    PropOrSpread::Prop(box Prop::KeyValue(KeyValueProp { key, box value })) => {
490                        ObjectPart::KeyValue(
491                            self.eval_prop_name(arena, key),
492                            self.eval(arena, value),
493                        )
494                    }
495                    PropOrSpread::Prop(box Prop::Shorthand(ident)) => ObjectPart::KeyValue(
496                        ident.sym.clone().into(),
497                        self.eval(arena, &Expr::Ident(ident.clone())),
498                    ),
499                    _ => ObjectPart::Spread(JsValue::unknown_empty(
500                        true,
501                        rcstr!("unsupported object part"),
502                    )),
503                }),
504            )),
505
506            Expr::MetaProp(MetaPropExpr {
507                kind: MetaPropKind::ImportMeta,
508                ..
509            }) => JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta),
510
511            Expr::Assign(AssignExpr { op, .. }) => match op {
512                // TODO: `self.eval(arena, right)` would be the value, but we need to handle the
513                // side effect of that expression
514                AssignOp::Assign => JsValue::unknown_empty(true, rcstr!("assignment expression")),
515                _ => JsValue::unknown_empty(true, rcstr!("compound assignment expression")),
516            },
517
518            _ => JsValue::unknown_empty(true, rcstr!("unsupported expression")),
519        }
520    }
521
522    pub fn eval_single_expr_lit<'a>(arena: &'a Bump, expr_lit: &RcStr) -> Result<JsValue<'a>> {
523        let cm = Lrc::new(SourceMap::default());
524
525        let js_value = try_with_handler(cm, Default::default(), |_| {
526            GLOBALS.set(&Default::default(), || {
527                let expr = parse_single_expr_lit(expr_lit);
528                let eval_context =
529                    EvalContext::new(None, Mark::new(), Mark::new(), Default::default(), None);
530
531                Ok(eval_context.eval(arena, &expr))
532            })
533        })
534        .map_err(|e| e.to_pretty_error())?;
535
536        Ok(js_value)
537    }
538}