1use std::{
2 fmt::{self},
3 hash::Hash,
4 mem::take,
5 sync::Arc,
6};
7
8use anyhow::{Context, Result, bail};
9use bumpalo::boxed::Box as BumpBox;
10use num_bigint::BigInt;
11use smallvec::{SmallVec, smallvec};
12use swc_core::ecma::{ast::Id, atoms::Atom};
13use turbo_rcstr::{RcStr, rcstr};
14use turbopack_core::compile_time_info::{
15 CompileTimeDefineValue, DefinableNameSegmentRef, DefinableNameSegmentRefs, FreeVarReference,
16};
17
18use crate::analyzer::{
19 Bump, BumpVec, WellKnownFunctionKind, WellKnownObjectKind,
20 graph::{EvalContext, VarGraph},
21};
22
23mod constants;
24mod display;
25mod explain;
26mod normalize;
27mod predicates;
28mod similar;
29mod traverse;
30
31use constants::JsValueMetaKind;
32pub use constants::*;
33
34fn total_nodes(vec: &[JsValue<'_>]) -> u32 {
36 vec.iter().map(|v| v.total_nodes()).sum::<u32>()
37}
38
39fn pretty_join(
42 items: &[String],
43 indent_depth: usize,
44 single_line_separator: &str,
45 multi_line_separator_end: &str,
46 multi_line_separator_start: &str,
47) -> String {
48 let multi_line = items
49 .iter()
50 .any(|item| item.len() > 50 || item.contains('\n'))
51 || items
52 .iter()
53 .map(|item| item.len() + single_line_separator.len())
54 .sum::<usize>()
55 > 100;
56 if !multi_line {
57 items.join(single_line_separator)
58 } else if multi_line_separator_start.is_empty() {
59 format!(
60 "\n{}{}\n{}",
61 " ".repeat(indent_depth + 1),
62 items.join(&format!(
63 "{multi_line_separator_end}\n{}",
64 " ".repeat(indent_depth + 1)
65 )),
66 " ".repeat(indent_depth)
67 )
68 } else {
69 format!(
70 "\n{}{multi_line_separator_start}{}\n{}",
71 " ".repeat(indent_depth * 4 + 4 - multi_line_separator_start.len()),
72 items.join(&format!(
73 "{multi_line_separator_end}\n{}{multi_line_separator_start}",
74 " ".repeat(indent_depth * 4 + 4 - multi_line_separator_start.len())
75 )),
76 " ".repeat(indent_depth)
77 )
78 }
79}
80
81#[derive(Debug, Hash, PartialEq)]
101pub enum JsValue<'a> {
102 Constant(ConstantValue),
106 Url(ConstantString, JsValueUrlKind),
108 WellKnownObject(WellKnownObjectKind),
111 WellKnownFunction(WellKnownFunctionKind<'a>),
113 Unknown {
116 original_value: Option<Arc<JsValue<'a>>>,
117 reason: RcStr,
118 has_side_effects: bool,
119 },
120
121 Array {
125 total_nodes: u32,
126 items: BumpVec<'a, JsValue<'a>>,
127 mutable: bool,
128 },
129 Object {
131 total_nodes: u32,
132 parts: BumpVec<'a, ObjectPart<'a>>,
133 mutability: ObjectMutability,
134 },
135 Alternatives {
137 total_nodes: u32,
138 values: BumpVec<'a, JsValue<'a>>,
139 logical_property: Option<LogicalProperty>,
140 },
141 Function(u32, u32, BumpBox<'a, JsValue<'a>>),
145
146 Concat(u32, BumpVec<'a, JsValue<'a>>),
151 Add(u32, BumpVec<'a, JsValue<'a>>),
155 Not(u32, BumpBox<'a, JsValue<'a>>),
157 Logical(u32, LogicalOperator, BumpVec<'a, JsValue<'a>>),
159 Binary(
161 u32,
162 BumpBox<'a, JsValue<'a>>,
163 BinaryOperator,
164 BumpBox<'a, JsValue<'a>>,
165 ),
166 New(u32, CallList<'a>),
168 Call(u32, CallList<'a>),
170 SuperCall(u32, BumpBox<'a, [JsValue<'a>]>),
173 MemberCall(u32, MemberCallList<'a>),
175 Member(u32, BumpBox<'a, JsValue<'a>>, BumpBox<'a, JsValue<'a>>),
178 Tenary(
181 u32,
182 BumpBox<'a, JsValue<'a>>,
183 BumpBox<'a, JsValue<'a>>,
184 BumpBox<'a, JsValue<'a>>,
185 ),
186 Promise(u32, BumpBox<'a, JsValue<'a>>),
189 Awaited(u32, BumpBox<'a, JsValue<'a>>),
192
193 Iterated(u32, BumpBox<'a, JsValue<'a>>),
197
198 TypeOf(u32, BumpBox<'a, JsValue<'a>>),
202
203 In(u32, BumpBox<'a, JsValue<'a>>, BumpBox<'a, JsValue<'a>>),
206
207 Variable(Id),
211 Argument(u32, usize),
214 FreeVar(Atom),
217 Module(ModuleValue),
219}
220
221#[derive(Hash, PartialEq)]
232pub struct MemberCallList<'a>(BumpVec<'a, JsValue<'a>>);
233
234impl fmt::Debug for MemberCallList<'_> {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 let n = self.0.len();
238 let obj = &self.0[n - 1];
239 let prop = &self.0[n - 2];
240 let args = &self.0[..n - 2];
241 if f.alternate() {
242 writeln!(f, "{obj:#?},")?;
247 writeln!(f, "{prop:#?},")?;
248 write!(f, "{args:#?}")
249 } else {
250 write!(f, "{obj:?}, {prop:?}, {args:?}")
251 }
252 }
253}
254
255impl<'a> MemberCallList<'a> {
256 fn from_parts(
257 arena: &'a Bump,
258 obj: JsValue<'a>,
259 prop: JsValue<'a>,
260 args: BumpVec<'a, JsValue<'a>>,
261 ) -> Self {
262 let mut list = args;
263 list.push(arena, prop);
264 list.push(arena, obj);
265 Self(list)
266 }
267
268 fn from_iter<I>(arena: &'a Bump, obj: JsValue<'a>, prop: JsValue<'a>, args: I) -> Self
269 where
270 I: IntoIterator<Item = JsValue<'a>>,
271 I::IntoIter: ExactSizeIterator,
272 {
273 let args = args.into_iter();
274 let mut list = BumpVec::with_capacity_in(arena, args.len() + 2);
275 list.extend(arena, args);
276 list.push(arena, prop);
277 list.push(arena, obj);
278 Self(list)
279 }
280
281 fn clone_in(&self, arena: &'a Bump) -> Self {
282 Self(BumpVec::from_iter_in(
283 arena,
284 self.0.iter().map(|v| v.clone_in(arena)),
285 ))
286 }
287
288 pub fn obj(&self) -> &JsValue<'a> {
290 &self.0[self.0.len() - 1]
291 }
292
293 pub fn obj_mut(&mut self) -> &mut JsValue<'a> {
294 let n = self.0.len();
295 &mut self.0[n - 1]
296 }
297
298 pub fn prop(&self) -> &JsValue<'a> {
300 &self.0[self.0.len() - 2]
301 }
302
303 pub fn prop_mut(&mut self) -> &mut JsValue<'a> {
304 let n = self.0.len();
305 &mut self.0[n - 2]
306 }
307
308 pub fn args(&self) -> &[JsValue<'a>] {
310 let n = self.0.len();
311 &self.0[..n - 2]
312 }
313
314 pub fn args_mut(&mut self) -> &mut [JsValue<'a>] {
315 let n = self.0.len();
316 &mut self.0[..n - 2]
317 }
318
319 pub fn as_parts_mut(&mut self) -> (&mut [JsValue<'a>], &mut JsValue<'a>, &mut JsValue<'a>) {
322 let n = self.0.len();
323 let (args, tail) = self.0.split_at_mut(n - 2);
324 let (prop_slot, obj_slot) = tail.split_at_mut(1);
325 (args, &mut prop_slot[0], &mut obj_slot[0])
326 }
327
328 pub fn into_parts(mut self) -> (JsValue<'a>, JsValue<'a>, BumpVec<'a, JsValue<'a>>) {
331 let obj = self.0.pop().unwrap();
332 let prop = self.0.pop().unwrap();
333 (obj, prop, self.0)
334 }
335
336 fn total_nodes(&self) -> u32 {
337 total_nodes(&self.0)
338 }
339
340 fn for_each_children(&self, visitor: &mut impl FnMut(&JsValue<'a>)) {
341 self.0.iter().for_each(visitor)
342 }
343 fn for_each_children_mut(
344 &mut self,
345 visitor: &mut impl FnMut(&mut JsValue<'a>) -> bool,
346 ) -> bool {
347 let mut modified = false;
348 for child in self.0.iter_mut() {
349 if visitor(child) {
350 modified = true;
351 }
352 }
353
354 modified
355 }
356
357 fn all_similar(l: &Self, r: &Self, depth: usize) -> bool {
358 JsValue::all_similar(&l.0, &r.0, depth)
359 }
360}
361
362#[derive(Hash, PartialEq)]
371pub struct CallList<'a>(BumpVec<'a, JsValue<'a>>);
372
373impl fmt::Debug for CallList<'_> {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 let n = self.0.len();
377 let callee = &self.0[n - 1];
378 let args = &self.0[..n - 1];
379 if f.alternate() {
380 writeln!(f, "{callee:#?},")?;
383 write!(f, "{args:#?}")
384 } else {
385 write!(f, "{callee:?}, {args:?}")
386 }
387 }
388}
389
390impl<'a> CallList<'a> {
391 fn from_parts(arena: &'a Bump, callee: JsValue<'a>, args: BumpVec<'a, JsValue<'a>>) -> Self {
392 let mut list = args;
393 list.push(arena, callee);
394 Self(list)
395 }
396
397 fn from_iter<I>(arena: &'a Bump, callee: JsValue<'a>, args: I) -> Self
398 where
399 I: IntoIterator<Item = JsValue<'a>>,
400 I::IntoIter: ExactSizeIterator,
401 {
402 let args = args.into_iter();
403 let mut list = BumpVec::with_capacity_in(arena, args.len() + 1);
404 list.extend(arena, args);
405 list.push(arena, callee);
406 Self(list)
407 }
408
409 fn clone_in(&self, arena: &'a Bump) -> Self {
410 Self(BumpVec::from_iter_in(
411 arena,
412 self.0.iter().map(|v| v.clone_in(arena)),
413 ))
414 }
415
416 pub fn callee(&self) -> &JsValue<'a> {
418 self.0.last().expect("CallList must always have a callee")
419 }
420
421 pub fn callee_mut(&mut self) -> &mut JsValue<'a> {
422 self.0
423 .last_mut()
424 .expect("CallList must always have a callee")
425 }
426
427 pub fn args(&self) -> &[JsValue<'a>] {
429 let n = self.0.len();
430 &self.0[..n - 1]
431 }
432
433 pub fn args_mut(&mut self) -> &mut [JsValue<'a>] {
434 let n = self.0.len();
435 &mut self.0[..n - 1]
436 }
437
438 pub fn as_parts_mut(&mut self) -> (&mut [JsValue<'a>], &mut JsValue<'a>) {
441 let n = self.0.len();
442 let (args, callee_slot) = self.0.split_at_mut(n - 1);
443 (args, &mut callee_slot[0])
444 }
445
446 pub fn into_parts(mut self) -> (JsValue<'a>, BumpVec<'a, JsValue<'a>>) {
449 let callee = self.0.pop().unwrap();
450 (callee, self.0)
451 }
452
453 fn total_nodes(&self) -> u32 {
454 total_nodes(&self.0)
455 }
456
457 fn for_each_children(&self, visitor: &mut impl FnMut(&JsValue<'a>)) {
458 self.0.iter().for_each(visitor)
459 }
460 fn for_each_children_mut(
461 &mut self,
462 visitor: &mut impl FnMut(&mut JsValue<'a>) -> bool,
463 ) -> bool {
464 let mut modified = false;
465 for child in self.0.iter_mut() {
466 if visitor(child) {
467 modified = true;
468 }
469 }
470
471 modified
472 }
473
474 fn all_similar(l: &Self, r: &Self, depth: usize) -> bool {
475 JsValue::all_similar(&l.0, &r.0, depth)
476 }
477}
478
479impl<'a> From<&'_ str> for JsValue<'a> {
480 fn from(v: &str) -> Self {
481 ConstantValue::Str(ConstantString::Atom(v.into())).into()
482 }
483}
484
485impl<'a> From<Atom> for JsValue<'a> {
486 fn from(v: Atom) -> Self {
487 ConstantValue::Str(ConstantString::Atom(v)).into()
488 }
489}
490
491impl<'a> From<BigInt> for JsValue<'a> {
492 fn from(v: BigInt) -> Self {
493 Self::from(Box::new(v))
494 }
495}
496
497impl<'a> From<Box<BigInt>> for JsValue<'a> {
498 fn from(v: Box<BigInt>) -> Self {
499 ConstantValue::BigInt(v).into()
500 }
501}
502
503impl<'a> From<f64> for JsValue<'a> {
504 fn from(v: f64) -> Self {
505 ConstantValue::Num(ConstantNumber(v)).into()
506 }
507}
508
509impl<'a> From<RcStr> for JsValue<'a> {
510 fn from(v: RcStr) -> Self {
511 ConstantValue::Str(v.into()).into()
512 }
513}
514
515impl<'a> From<String> for JsValue<'a> {
516 fn from(v: String) -> Self {
517 RcStr::from(v).into()
518 }
519}
520
521impl<'a> From<swc_core::ecma::ast::Str> for JsValue<'a> {
522 fn from(v: swc_core::ecma::ast::Str) -> Self {
523 ConstantValue::Str(ConstantString::Atom(v.value.to_atom_lossy().into_owned())).into()
524 }
525}
526
527impl<'a> From<ConstantValue> for JsValue<'a> {
528 fn from(v: ConstantValue) -> Self {
529 JsValue::Constant(v)
530 }
531}
532
533impl<'a> JsValue<'a> {
534 pub fn from_compile_time_define_value_in(
537 arena: &'a Bump,
538 value: &CompileTimeDefineValue,
539 ) -> Result<Self> {
540 Ok(JsValue::Constant(match value {
541 CompileTimeDefineValue::Undefined => ConstantValue::Undefined,
542 CompileTimeDefineValue::Null => ConstantValue::Null,
543 CompileTimeDefineValue::Bool(b) => (*b).into(),
544 CompileTimeDefineValue::Number(n) => ConstantValue::Num(ConstantNumber(
545 n.as_f64()
546 .expect("unreachable: serde-json has arbitrary_precision disabled"),
547 )),
548 CompileTimeDefineValue::BigInt(n) => ConstantValue::BigInt(n.clone()),
549 CompileTimeDefineValue::String(s) => s.as_str().into(),
550 CompileTimeDefineValue::Regex(pattern, flags) => {
551 ConstantValue::Regex(Box::new((pattern.as_str().into(), flags.as_str().into())))
552 }
553 CompileTimeDefineValue::Array(a) => {
554 let mut items = BumpVec::with_capacity_in(arena, a.len());
555 for i in a {
556 items.push(arena, JsValue::from_compile_time_define_value_in(arena, i)?);
557 }
558 let mut js_value = JsValue::Array {
559 total_nodes: a.len() as u32,
560 items,
561 mutable: false,
562 };
563 js_value.update_total_nodes();
564 return Ok(js_value);
565 }
566 CompileTimeDefineValue::Object(m) => {
567 let mut parts = BumpVec::with_capacity_in(arena, m.len());
568 for (k, v) in m {
569 parts.push(
570 arena,
571 ObjectPart::KeyValue(
572 k.clone().into(),
573 JsValue::from_compile_time_define_value_in(arena, v)?,
574 ),
575 );
576 }
577 let mut js_value = JsValue::Object {
578 total_nodes: m.len() as u32,
579 parts,
580 mutability: ObjectMutability::Frozen,
581 };
582 js_value.update_total_nodes();
583 return Ok(js_value);
584 }
585 CompileTimeDefineValue::Evaluate(s) => {
586 return EvalContext::eval_single_expr_lit(arena, s);
587 }
588 }))
589 }
590}
591
592impl TryFrom<&ConstantValue> for CompileTimeDefineValue {
593 type Error = anyhow::Error;
594
595 fn try_from(value: &ConstantValue) -> Result<Self> {
596 Ok(match value {
597 ConstantValue::Undefined => CompileTimeDefineValue::Undefined,
598 ConstantValue::Null => CompileTimeDefineValue::Null,
599 ConstantValue::True => CompileTimeDefineValue::Bool(true),
600 ConstantValue::False => CompileTimeDefineValue::Bool(false),
601 ConstantValue::Num(n) => CompileTimeDefineValue::Number(
602 serde_json::Number::from_f64(n.0)
603 .ok_or_else(|| anyhow::anyhow!("NaN and Infinity cannot be represented"))?,
604 ),
605 ConstantValue::Str(s) => CompileTimeDefineValue::String(s.as_rcstr()),
606 ConstantValue::BigInt(n) => CompileTimeDefineValue::BigInt(n.clone()),
607 ConstantValue::Regex(regex) => CompileTimeDefineValue::Regex(
608 RcStr::from(regex.0.as_str()),
609 RcStr::from(regex.1.as_str()),
610 ),
611 })
612 }
613}
614
615impl TryFrom<&'_ JsValue<'_>> for CompileTimeDefineValue {
616 type Error = anyhow::Error;
617
618 fn try_from(value: &JsValue) -> Result<Self> {
619 Ok(match value {
620 JsValue::Constant(v) => return Self::try_from(v),
621 JsValue::Array { items, .. } => {
622 let mut arr = Vec::with_capacity(items.len());
623 for item in items.iter() {
624 arr.push(Self::try_from(item)?);
625 }
626 CompileTimeDefineValue::Array(arr)
627 }
628 JsValue::Object { parts, .. } => {
629 let mut obj = Vec::with_capacity(parts.len());
630 for part in parts.iter() {
631 if let ObjectPart::KeyValue(key, value) = part {
632 obj.push((
633 key.as_str()
634 .context("JsValue object key is not a string")?
635 .into(),
636 Self::try_from(value)?,
637 ));
638 } else {
639 bail!("JsValue object contains non-key-value part");
640 }
641 }
642 CompileTimeDefineValue::Object(obj)
643 }
644 _ => bail!(
645 "JsValue is not constant and could not be converted to CompileTimeDefineValue"
646 ),
647 })
648 }
649}
650
651impl<'a> JsValue<'a> {
652 pub fn from_free_var_reference_in(arena: &'a Bump, value: &FreeVarReference) -> Result<Self> {
655 match value {
656 FreeVarReference::Value(v) => JsValue::from_compile_time_define_value_in(arena, v),
657 FreeVarReference::Ident(_) => Ok(JsValue::unknown_empty(
658 false,
659 rcstr!("compile time injected ident"),
660 )),
661 FreeVarReference::Member(_, _) => Ok(JsValue::unknown_empty(
662 false,
663 rcstr!("compile time injected member"),
664 )),
665 FreeVarReference::EcmaScriptModule { .. } => Ok(JsValue::unknown_empty(
666 false,
667 rcstr!("compile time injected free var module"),
668 )),
669 FreeVarReference::ReportUsage { inner, .. } => {
670 if let Some(inner) = &inner {
671 JsValue::from_free_var_reference_in(arena, inner.as_ref())
672 } else {
673 Ok(JsValue::unknown_empty(
674 false,
675 rcstr!("compile time injected free var error"),
676 ))
677 }
678 }
679 FreeVarReference::InputRelative(kind) => {
680 use turbopack_core::compile_time_info::InputRelativeConstant;
681 Ok(JsValue::unknown_empty(
682 false,
683 match kind {
684 InputRelativeConstant::DirName => {
685 rcstr!("compile time injected free var referencing the directory name")
686 }
687 InputRelativeConstant::FileName => {
688 rcstr!("compile time injected free var referencing the file name")
689 }
690 },
691 ))
692 }
693 }
694 }
695}
696
697impl Default for JsValue<'_> {
698 fn default() -> Self {
699 JsValue::unknown_empty(false, rcstr!(""))
700 }
701}
702
703impl JsValue<'_> {
705 fn meta_type(&self) -> JsValueMetaKind {
706 match self {
707 JsValue::Constant(..)
708 | JsValue::Url(..)
709 | JsValue::WellKnownObject(..)
710 | JsValue::WellKnownFunction(..)
711 | JsValue::Unknown { .. } => JsValueMetaKind::Leaf,
712 JsValue::Array { .. }
713 | JsValue::Object { .. }
714 | JsValue::Alternatives { .. }
715 | JsValue::Function(..)
716 | JsValue::Promise(..)
717 | JsValue::Member(..) => JsValueMetaKind::Nested,
718 JsValue::Concat(..)
719 | JsValue::Add(..)
720 | JsValue::Not(..)
721 | JsValue::Logical(..)
722 | JsValue::Binary(..)
723 | JsValue::New(..)
724 | JsValue::Call(..)
725 | JsValue::SuperCall(..)
726 | JsValue::Tenary(..)
727 | JsValue::MemberCall(..)
728 | JsValue::Iterated(..)
729 | JsValue::Awaited(..)
730 | JsValue::TypeOf(..)
731 | JsValue::In(..) => JsValueMetaKind::Operation,
732 JsValue::Variable(..)
733 | JsValue::Argument(..)
734 | JsValue::FreeVar(..)
735 | JsValue::Module(..) => JsValueMetaKind::Placeholder,
736 }
737 }
738}
739
740impl<'a> JsValue<'a> {
742 pub fn alternatives(list: BumpVec<'a, JsValue<'a>>) -> Self {
743 Self::Alternatives {
744 total_nodes: 1 + total_nodes(&list),
745 values: list,
746 logical_property: None,
747 }
748 }
749
750 pub fn alternatives_with_additional_property(
751 list: BumpVec<'a, JsValue<'a>>,
752 logical_property: LogicalProperty,
753 ) -> Self {
754 Self::Alternatives {
755 total_nodes: 1 + total_nodes(&list),
756 values: list,
757 logical_property: Some(logical_property),
758 }
759 }
760
761 pub fn concat(list: BumpVec<'a, JsValue<'a>>) -> Self {
762 Self::Concat(1 + total_nodes(&list), list)
763 }
764
765 pub fn add(list: BumpVec<'a, JsValue<'a>>) -> Self {
766 Self::Add(1 + total_nodes(&list), list)
767 }
768
769 pub fn logical_and(list: BumpVec<'a, JsValue<'a>>) -> Self {
770 Self::Logical(1 + total_nodes(&list), LogicalOperator::And, list)
771 }
772
773 pub fn logical_or(list: BumpVec<'a, JsValue<'a>>) -> Self {
774 Self::Logical(1 + total_nodes(&list), LogicalOperator::Or, list)
775 }
776
777 pub fn nullish_coalescing(list: BumpVec<'a, JsValue<'a>>) -> Self {
778 Self::Logical(
779 1 + total_nodes(&list),
780 LogicalOperator::NullishCoalescing,
781 list,
782 )
783 }
784
785 pub fn tenary(arena: &'a Bump, test: JsValue<'a>, cons: JsValue<'a>, alt: JsValue<'a>) -> Self {
786 Self::Tenary(
787 1 + test.total_nodes() + cons.total_nodes() + alt.total_nodes(),
788 BumpBox::new_in(test, arena),
789 BumpBox::new_in(cons, arena),
790 BumpBox::new_in(alt, arena),
791 )
792 }
793
794 pub fn iterated(arena: &'a Bump, iterable: JsValue<'a>) -> Self {
795 Self::Iterated(1 + iterable.total_nodes(), BumpBox::new_in(iterable, arena))
796 }
797
798 pub fn equal(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
799 Self::Binary(
800 1 + a.total_nodes() + b.total_nodes(),
801 BumpBox::new_in(a, arena),
802 BinaryOperator::Equal,
803 BumpBox::new_in(b, arena),
804 )
805 }
806
807 pub fn not_equal(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
808 Self::Binary(
809 1 + a.total_nodes() + b.total_nodes(),
810 BumpBox::new_in(a, arena),
811 BinaryOperator::NotEqual,
812 BumpBox::new_in(b, arena),
813 )
814 }
815
816 pub fn strict_equal(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
817 Self::Binary(
818 1 + a.total_nodes() + b.total_nodes(),
819 BumpBox::new_in(a, arena),
820 BinaryOperator::StrictEqual,
821 BumpBox::new_in(b, arena),
822 )
823 }
824
825 pub fn strict_not_equal(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
826 Self::Binary(
827 1 + a.total_nodes() + b.total_nodes(),
828 BumpBox::new_in(a, arena),
829 BinaryOperator::StrictNotEqual,
830 BumpBox::new_in(b, arena),
831 )
832 }
833
834 pub fn r#in(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
835 Self::In(
836 1 + a.total_nodes() + b.total_nodes(),
837 BumpBox::new_in(a, arena),
838 BumpBox::new_in(b, arena),
839 )
840 }
841
842 pub fn logical_not(arena: &'a Bump, inner: JsValue<'a>) -> Self {
843 Self::Not(1 + inner.total_nodes(), BumpBox::new_in(inner, arena))
844 }
845
846 pub fn type_of(arena: &'a Bump, operand: JsValue<'a>) -> Self {
847 Self::TypeOf(1 + operand.total_nodes(), BumpBox::new_in(operand, arena))
848 }
849
850 pub fn array(items: BumpVec<'a, JsValue<'a>>) -> Self {
851 Self::Array {
852 total_nodes: 1 + total_nodes(&items),
853 items,
854 mutable: true,
855 }
856 }
857
858 pub fn frozen_array(items: BumpVec<'a, JsValue<'a>>) -> Self {
859 Self::Array {
860 total_nodes: 1 + total_nodes(&items),
861 items,
862 mutable: false,
863 }
864 }
865
866 pub fn function(
867 arena: &'a Bump,
868 func_ident: u32,
869 is_async: bool,
870 is_generator: bool,
871 return_value: JsValue<'a>,
872 ) -> Self {
873 let return_value = if is_generator {
875 JsValue::WellKnownObject(WellKnownObjectKind::Generator)
876 } else if is_async {
877 JsValue::promise(arena, return_value)
878 } else {
879 return_value
880 };
881 Self::Function(
882 1 + return_value.total_nodes(),
883 func_ident,
884 BumpBox::new_in(return_value, arena),
885 )
886 }
887
888 pub fn object(list: BumpVec<'a, ObjectPart<'a>>) -> Self {
889 Self::Object {
890 total_nodes: 1 + list
891 .iter()
892 .map(|v| match v {
893 ObjectPart::KeyValue(k, v) => k.total_nodes() + v.total_nodes(),
894 ObjectPart::Spread(s) => s.total_nodes(),
895 })
896 .sum::<u32>(),
897 parts: list,
898 mutability: ObjectMutability::Mutable,
899 }
900 }
901
902 pub fn object_with_mutability(
903 list: BumpVec<'a, ObjectPart<'a>>,
904 mutability: ObjectMutability,
905 ) -> Self {
906 Self::Object {
907 total_nodes: 1 + list
908 .iter()
909 .map(|v| match v {
910 ObjectPart::KeyValue(k, v) => k.total_nodes() + v.total_nodes(),
911 ObjectPart::Spread(s) => s.total_nodes(),
912 })
913 .sum::<u32>(),
914 parts: list,
915 mutability,
916 }
917 }
918
919 pub fn new_from_parts(arena: &'a Bump, f: JsValue<'a>, args: BumpVec<'a, JsValue<'a>>) -> Self {
928 let total = 1 + f.total_nodes() + total_nodes(&args);
929 Self::New(total, CallList::from_parts(arena, f, args))
930 }
931
932 pub fn new_from_iter<I>(arena: &'a Bump, f: JsValue<'a>, args: I) -> Self
937 where
938 I: IntoIterator<Item = JsValue<'a>>,
939 I::IntoIter: ExactSizeIterator,
940 {
941 let list = CallList::from_iter(arena, f, args);
942 let total = 1 + total_nodes(&list.0);
943 Self::New(total, list)
944 }
945
946 pub fn call_from_parts(
953 arena: &'a Bump,
954 f: JsValue<'a>,
955 args: BumpVec<'a, JsValue<'a>>,
956 ) -> Self {
957 let total = 1 + f.total_nodes() + total_nodes(&args);
958 Self::Call(total, CallList::from_parts(arena, f, args))
959 }
960
961 pub fn call_from_iter<I>(arena: &'a Bump, f: JsValue<'a>, args: I) -> Self
966 where
967 I: IntoIterator<Item = JsValue<'a>>,
968 I::IntoIter: ExactSizeIterator,
969 {
970 let list = CallList::from_iter(arena, f, args);
971 let total = 1 + total_nodes(&list.0);
972 Self::Call(total, list)
973 }
974
975 pub fn super_call(args: BumpBox<'a, [JsValue<'a>]>) -> Self {
976 Self::SuperCall(1 + total_nodes(&args), args)
977 }
978
979 pub fn member_call_from_parts(
986 arena: &'a Bump,
987 o: JsValue<'a>,
988 p: JsValue<'a>,
989 args: BumpVec<'a, JsValue<'a>>,
990 ) -> Self {
991 let total = 1 + o.total_nodes() + p.total_nodes() + total_nodes(&args);
992 Self::MemberCall(total, MemberCallList::from_parts(arena, o, p, args))
993 }
994
995 pub fn member_call_from_iter<I>(
1001 arena: &'a Bump,
1002 o: JsValue<'a>,
1003 p: JsValue<'a>,
1004 args: I,
1005 ) -> Self
1006 where
1007 I: IntoIterator<Item = JsValue<'a>>,
1008 I::IntoIter: ExactSizeIterator,
1009 {
1010 let list = MemberCallList::from_iter(arena, o, p, args);
1011 let total = 1 + total_nodes(&list.0);
1012 Self::MemberCall(total, list)
1013 }
1014
1015 pub fn member(arena: &'a Bump, o: JsValue<'a>, p: JsValue<'a>) -> Self {
1016 Self::Member(
1017 1 + o.total_nodes() + p.total_nodes(),
1018 BumpBox::new_in(o, arena),
1019 BumpBox::new_in(p, arena),
1020 )
1021 }
1022
1023 pub fn promise(arena: &'a Bump, operand: JsValue<'a>) -> Self {
1024 if let JsValue::Promise(_, _) = operand {
1026 return operand;
1027 }
1028 Self::Promise(1 + operand.total_nodes(), BumpBox::new_in(operand, arena))
1029 }
1030
1031 pub fn awaited(arena: &'a Bump, operand: JsValue<'a>) -> Self {
1032 Self::Awaited(1 + operand.total_nodes(), BumpBox::new_in(operand, arena))
1033 }
1034
1035 pub fn unknown(value: impl Into<Arc<JsValue<'a>>>, side_effects: bool, reason: RcStr) -> Self {
1036 Self::Unknown {
1037 original_value: Some(value.into()),
1038 reason,
1039 has_side_effects: side_effects,
1040 }
1041 }
1042
1043 pub fn unknown_empty(side_effects: bool, reason: RcStr) -> Self {
1044 Self::Unknown {
1045 original_value: None,
1046 reason,
1047 has_side_effects: side_effects,
1048 }
1049 }
1050
1051 pub fn unknown_if(
1052 is_unknown: bool,
1053 value: JsValue<'a>,
1054 side_effects: bool,
1055 reason: RcStr,
1056 ) -> Self {
1057 if is_unknown {
1058 Self::Unknown {
1059 original_value: Some(value.into()),
1060 reason,
1061 has_side_effects: side_effects,
1062 }
1063 } else {
1064 value
1065 }
1066 }
1067}
1068
1069impl JsValue<'_> {
1071 pub fn has_children(&self) -> bool {
1072 self.total_nodes() > 1
1073 }
1074
1075 pub fn total_nodes(&self) -> u32 {
1076 match self {
1077 JsValue::Constant(_)
1078 | JsValue::Url(_, _)
1079 | JsValue::FreeVar(_)
1080 | JsValue::Variable(_)
1081 | JsValue::Module(..)
1082 | JsValue::WellKnownObject(_)
1083 | JsValue::WellKnownFunction(_)
1084 | JsValue::Unknown { .. }
1085 | JsValue::Argument(..) => 1,
1086
1087 JsValue::Array { total_nodes: c, .. }
1088 | JsValue::Object { total_nodes: c, .. }
1089 | JsValue::Alternatives { total_nodes: c, .. }
1090 | JsValue::Concat(c, _)
1091 | JsValue::Add(c, _)
1092 | JsValue::Not(c, _)
1093 | JsValue::Logical(c, _, _)
1094 | JsValue::Binary(c, _, _, _)
1095 | JsValue::Tenary(c, _, _, _)
1096 | JsValue::New(c, _)
1097 | JsValue::Call(c, _)
1098 | JsValue::SuperCall(c, _)
1099 | JsValue::MemberCall(c, _)
1100 | JsValue::Member(c, _, _)
1101 | JsValue::Function(c, _, _)
1102 | JsValue::Iterated(c, ..)
1103 | JsValue::Promise(c, ..)
1104 | JsValue::Awaited(c, ..)
1105 | JsValue::TypeOf(c, ..)
1106 | JsValue::In(c, ..) => *c,
1107 }
1108 }
1109
1110 pub(crate) fn update_total_nodes(&mut self) {
1111 match self {
1112 JsValue::Constant(_)
1113 | JsValue::Url(_, _)
1114 | JsValue::FreeVar(_)
1115 | JsValue::Variable(_)
1116 | JsValue::Module(..)
1117 | JsValue::WellKnownObject(_)
1118 | JsValue::WellKnownFunction(_)
1119 | JsValue::Unknown { .. }
1120 | JsValue::Argument(..) => {}
1121
1122 JsValue::Array {
1123 total_nodes: c,
1124 items: list,
1125 ..
1126 }
1127 | JsValue::Alternatives {
1128 total_nodes: c,
1129 values: list,
1130 ..
1131 }
1132 | JsValue::Concat(c, list)
1133 | JsValue::Add(c, list)
1134 | JsValue::Logical(c, _, list) => {
1135 *c = 1 + total_nodes(list);
1136 }
1137
1138 JsValue::Binary(c, a, _, b) => {
1139 *c = 1 + a.total_nodes() + b.total_nodes();
1140 }
1141 JsValue::Tenary(c, test, cons, alt) => {
1142 *c = 1 + test.total_nodes() + cons.total_nodes() + alt.total_nodes();
1143 }
1144 JsValue::Not(c, r) => {
1145 *c = 1 + r.total_nodes();
1146 }
1147 JsValue::Promise(c, r) => {
1148 *c = 1 + r.total_nodes();
1149 }
1150 JsValue::Awaited(c, r) => {
1151 *c = 1 + r.total_nodes();
1152 }
1153
1154 JsValue::Object {
1155 total_nodes: c,
1156 parts,
1157 mutability: _,
1158 } => {
1159 *c = 1 + parts
1160 .iter()
1161 .map(|v| match v {
1162 ObjectPart::KeyValue(k, v) => k.total_nodes() + v.total_nodes(),
1163 ObjectPart::Spread(s) => s.total_nodes(),
1164 })
1165 .sum::<u32>();
1166 }
1167 JsValue::New(c, call) => {
1168 *c = 1 + call.total_nodes();
1169 }
1170 JsValue::Call(c, call) => {
1171 *c = 1 + call.total_nodes();
1172 }
1173 JsValue::SuperCall(c, args) => {
1174 *c = 1 + total_nodes(args);
1175 }
1176 JsValue::MemberCall(c, call) => {
1177 *c = 1 + call.total_nodes();
1178 }
1179 JsValue::Member(c, o, p) => {
1180 *c = 1 + o.total_nodes() + p.total_nodes();
1181 }
1182 JsValue::Function(c, _, r) => {
1183 *c = 1 + r.total_nodes();
1184 }
1185
1186 JsValue::Iterated(c, iterable) => {
1187 *c = 1 + iterable.total_nodes();
1188 }
1189
1190 JsValue::TypeOf(c, operand) => {
1191 *c = 1 + operand.total_nodes();
1192 }
1193 JsValue::In(c, l, r) => {
1194 *c = 1 + l.total_nodes() + r.total_nodes();
1195 }
1196 }
1197 }
1198
1199 #[cfg(debug_assertions)]
1200 pub fn debug_assert_total_nodes_up_to_date(&mut self) {
1201 let old = self.total_nodes();
1202 self.update_total_nodes();
1203 assert_eq!(
1204 old,
1205 self.total_nodes(),
1206 "total nodes not up to date {self:?}"
1207 );
1208 }
1209
1210 #[cfg(not(debug_assertions))]
1211 pub fn debug_assert_total_nodes_up_to_date(&mut self) {}
1212}
1213
1214impl<'a> JsValue<'a> {
1216 pub fn make_unknown(&mut self, side_effects: bool, reason: RcStr) {
1218 *self = JsValue::unknown(take(self), side_effects || self.has_side_effects(), reason);
1219 }
1220
1221 pub fn into_unknown(mut self, side_effects: bool, reason: RcStr) -> Self {
1223 self.make_unknown(side_effects, reason);
1224 self
1225 }
1226
1227 pub fn make_unknown_without_content(&mut self, side_effects: bool, reason: RcStr) {
1230 *self = JsValue::unknown_empty(side_effects || self.has_side_effects(), reason);
1231 }
1232
1233 pub fn make_nested_operations_unknown(&mut self) -> bool {
1235 fn inner(this: &mut JsValue) -> bool {
1236 if matches!(this.meta_type(), JsValueMetaKind::Operation) {
1237 this.make_unknown(false, rcstr!("nested operation"));
1238 true
1239 } else {
1240 this.for_each_children_mut(&mut inner)
1241 }
1242 }
1243 if matches!(self.meta_type(), JsValueMetaKind::Operation) {
1244 self.for_each_children_mut(&mut inner)
1245 } else {
1246 false
1247 }
1248 }
1249
1250 pub fn add_unknown_mutations(&mut self, arena: &'a Bump, side_effects: bool) {
1251 self.add_alt(
1252 arena,
1253 JsValue::unknown_empty(side_effects, rcstr!("unknown mutation")),
1254 );
1255 }
1256}
1257
1258impl JsValue<'_> {
1260 #[allow(mismatched_lifetime_syntaxes)]
1262 pub fn get_definable_name<'v>(
1271 &'v self,
1272 var_graph: Option<&VarGraph<'_>>,
1273 ) -> SmallVec<[Option<(DefinableNameSegmentRefs<'_>, bool)>; 1]> {
1274 let inner = |value: &'v JsValue| {
1275 let mut current = value;
1276 let mut segments = SmallVec::new();
1277 let mut potentially_reassigned = false;
1278 loop {
1279 match current {
1280 JsValue::FreeVar(name) => {
1281 if var_graph.is_some_and(|var_graph| {
1282 var_graph
1283 .free_var_ids
1284 .get(name)
1285 .is_some_and(|id| var_graph.values.contains_key(id))
1286 }) {
1287 potentially_reassigned = true;
1289 }
1290 segments.push(DefinableNameSegmentRef::Name(name));
1291 break;
1292 }
1293 JsValue::Member(_, obj, prop) => {
1294 segments.push(DefinableNameSegmentRef::Name(prop.as_str()?));
1295 current = obj;
1296 }
1297 JsValue::WellKnownObject(obj) => {
1298 segments.extend(
1299 obj.as_define_name()?
1300 .iter()
1301 .rev()
1302 .copied()
1303 .map(DefinableNameSegmentRef::Name),
1304 );
1305 break;
1306 }
1307 JsValue::WellKnownFunction(func) => {
1308 segments.extend(
1309 func.as_define_name()?
1310 .iter()
1311 .rev()
1312 .copied()
1313 .map(DefinableNameSegmentRef::Name),
1314 );
1315 break;
1316 }
1317 JsValue::MemberCall(_, call) if call.args().is_empty() => {
1318 let Some(call_prop) = call.prop().as_str() else {
1319 return Default::default();
1320 };
1321 segments.push(DefinableNameSegmentRef::Call(call_prop));
1322 current = call.obj();
1323 }
1324 JsValue::TypeOf(_, arg) => {
1325 segments.push(DefinableNameSegmentRef::TypeOf);
1326 current = arg;
1327 }
1328 _ => return None,
1329 }
1330 }
1331 segments.reverse();
1332 Some((DefinableNameSegmentRefs(segments), potentially_reassigned))
1333 };
1334
1335 if let JsValue::Alternatives { values, .. } = self {
1336 values.iter().map(inner).collect()
1337 } else {
1338 smallvec![inner(self)]
1339 }
1340 }
1341}
1342
1343impl<'a> JsValue<'a> {
1346 pub fn clone_in(&self, arena: &'a Bump) -> JsValue<'a> {
1348 match self {
1349 JsValue::Constant(v) => JsValue::Constant(v.clone()),
1350 JsValue::Url(s, k) => JsValue::Url(s.clone(), *k),
1351 JsValue::WellKnownObject(k) => JsValue::WellKnownObject(k.clone()),
1352 JsValue::WellKnownFunction(k) => JsValue::WellKnownFunction(k.clone()),
1353 JsValue::Unknown {
1354 original_value,
1355 reason,
1356 has_side_effects,
1357 } => JsValue::Unknown {
1358 original_value: original_value.clone(),
1359 reason: reason.clone(),
1360 has_side_effects: *has_side_effects,
1361 },
1362 JsValue::Array {
1363 total_nodes,
1364 items,
1365 mutable,
1366 } => JsValue::Array {
1367 total_nodes: *total_nodes,
1368 items: BumpVec::from_iter_in(arena, items.iter().map(|v| v.clone_in(arena))),
1369 mutable: *mutable,
1370 },
1371 JsValue::Object {
1372 total_nodes,
1373 parts,
1374 mutability,
1375 } => JsValue::Object {
1376 total_nodes: *total_nodes,
1377 parts: BumpVec::from_iter_in(arena, parts.iter().map(|p| p.clone_in(arena))),
1378 mutability: *mutability,
1379 },
1380 JsValue::Alternatives {
1381 total_nodes,
1382 values,
1383 logical_property,
1384 } => JsValue::Alternatives {
1385 total_nodes: *total_nodes,
1386 values: BumpVec::from_iter_in(arena, values.iter().map(|v| v.clone_in(arena))),
1387 logical_property: *logical_property,
1388 },
1389 JsValue::Function(c, id, r) => {
1390 JsValue::Function(*c, *id, BumpBox::new_in(r.clone_in(arena), arena))
1391 }
1392 JsValue::Concat(c, list) => JsValue::Concat(
1393 *c,
1394 BumpVec::from_iter_in(arena, list.iter().map(|v| v.clone_in(arena))),
1395 ),
1396 JsValue::Add(c, list) => JsValue::Add(
1397 *c,
1398 BumpVec::from_iter_in(arena, list.iter().map(|v| v.clone_in(arena))),
1399 ),
1400 JsValue::Not(c, v) => JsValue::Not(*c, BumpBox::new_in(v.clone_in(arena), arena)),
1401 JsValue::Logical(c, op, list) => JsValue::Logical(
1402 *c,
1403 *op,
1404 BumpVec::from_iter_in(arena, list.iter().map(|v| v.clone_in(arena))),
1405 ),
1406 JsValue::Binary(c, a, op, b) => JsValue::Binary(
1407 *c,
1408 BumpBox::new_in(a.clone_in(arena), arena),
1409 *op,
1410 BumpBox::new_in(b.clone_in(arena), arena),
1411 ),
1412 JsValue::New(c, call) => JsValue::New(*c, call.clone_in(arena)),
1413 JsValue::Call(c, call) => JsValue::Call(*c, call.clone_in(arena)),
1414 JsValue::SuperCall(c, args) => JsValue::SuperCall(
1415 *c,
1416 bumpalo::collections::Vec::from_iter_in(
1417 args.iter().map(|v| v.clone_in(arena)),
1418 arena,
1419 )
1420 .into_boxed_slice(),
1421 ),
1422 JsValue::MemberCall(c, call) => JsValue::MemberCall(*c, call.clone_in(arena)),
1423 JsValue::Member(c, o, p) => JsValue::Member(
1424 *c,
1425 BumpBox::new_in(o.clone_in(arena), arena),
1426 BumpBox::new_in(p.clone_in(arena), arena),
1427 ),
1428 JsValue::Tenary(c, test, cons, alt) => JsValue::Tenary(
1429 *c,
1430 BumpBox::new_in(test.clone_in(arena), arena),
1431 BumpBox::new_in(cons.clone_in(arena), arena),
1432 BumpBox::new_in(alt.clone_in(arena), arena),
1433 ),
1434 JsValue::Promise(c, v) => {
1435 JsValue::Promise(*c, BumpBox::new_in(v.clone_in(arena), arena))
1436 }
1437 JsValue::Awaited(c, v) => {
1438 JsValue::Awaited(*c, BumpBox::new_in(v.clone_in(arena), arena))
1439 }
1440 JsValue::Iterated(c, v) => {
1441 JsValue::Iterated(*c, BumpBox::new_in(v.clone_in(arena), arena))
1442 }
1443 JsValue::TypeOf(c, v) => JsValue::TypeOf(*c, BumpBox::new_in(v.clone_in(arena), arena)),
1444 JsValue::In(c, l, r) => JsValue::In(
1445 *c,
1446 BumpBox::new_in(l.clone_in(arena), arena),
1447 BumpBox::new_in(r.clone_in(arena), arena),
1448 ),
1449 JsValue::Variable(id) => JsValue::Variable(id.clone()),
1450 JsValue::Argument(i, idx) => JsValue::Argument(*i, *idx),
1451 JsValue::FreeVar(a) => JsValue::FreeVar(a.clone()),
1452 JsValue::Module(m) => JsValue::Module(m.clone()),
1453 }
1454 }
1455}
1456
1457#[cfg(test)]
1458mod tests {
1459 use super::*;
1460
1461 #[test]
1462 #[cfg(target_pointer_width = "64")]
1463 fn jsvalue_size() {
1464 assert_eq!(32, size_of::<JsValue>());
1465 }
1466}