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#[derive(Debug)]
24pub struct EvalContext {
25 pub(crate) unresolved_mark: Mark,
27 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 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 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 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 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 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 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 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 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 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 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}