1use std::{
2 fmt::{self},
3 hash::Hash,
4 mem::take,
5 sync::Arc,
6};
7
8use anyhow::Result;
9use bumpalo::boxed::Box as BumpBox;
10use num_bigint::BigInt;
11use 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 mutable: bool,
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 mutable: false,
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<'a> JsValue<'a> {
616 pub fn from_free_var_reference_in(arena: &'a Bump, value: &FreeVarReference) -> Result<Self> {
619 match value {
620 FreeVarReference::Value(v) => JsValue::from_compile_time_define_value_in(arena, v),
621 FreeVarReference::Ident(_) => Ok(JsValue::unknown_empty(
622 false,
623 rcstr!("compile time injected ident"),
624 )),
625 FreeVarReference::Member(_, _) => Ok(JsValue::unknown_empty(
626 false,
627 rcstr!("compile time injected member"),
628 )),
629 FreeVarReference::EcmaScriptModule { .. } => Ok(JsValue::unknown_empty(
630 false,
631 rcstr!("compile time injected free var module"),
632 )),
633 FreeVarReference::ReportUsage { inner, .. } => {
634 if let Some(inner) = &inner {
635 JsValue::from_free_var_reference_in(arena, inner.as_ref())
636 } else {
637 Ok(JsValue::unknown_empty(
638 false,
639 rcstr!("compile time injected free var error"),
640 ))
641 }
642 }
643 FreeVarReference::InputRelative(kind) => {
644 use turbopack_core::compile_time_info::InputRelativeConstant;
645 Ok(JsValue::unknown_empty(
646 false,
647 match kind {
648 InputRelativeConstant::DirName => {
649 rcstr!("compile time injected free var referencing the directory name")
650 }
651 InputRelativeConstant::FileName => {
652 rcstr!("compile time injected free var referencing the file name")
653 }
654 },
655 ))
656 }
657 }
658 }
659}
660
661impl Default for JsValue<'_> {
662 fn default() -> Self {
663 JsValue::unknown_empty(false, rcstr!(""))
664 }
665}
666
667impl JsValue<'_> {
669 fn meta_type(&self) -> JsValueMetaKind {
670 match self {
671 JsValue::Constant(..)
672 | JsValue::Url(..)
673 | JsValue::WellKnownObject(..)
674 | JsValue::WellKnownFunction(..)
675 | JsValue::Unknown { .. } => JsValueMetaKind::Leaf,
676 JsValue::Array { .. }
677 | JsValue::Object { .. }
678 | JsValue::Alternatives { .. }
679 | JsValue::Function(..)
680 | JsValue::Promise(..)
681 | JsValue::Member(..) => JsValueMetaKind::Nested,
682 JsValue::Concat(..)
683 | JsValue::Add(..)
684 | JsValue::Not(..)
685 | JsValue::Logical(..)
686 | JsValue::Binary(..)
687 | JsValue::New(..)
688 | JsValue::Call(..)
689 | JsValue::SuperCall(..)
690 | JsValue::Tenary(..)
691 | JsValue::MemberCall(..)
692 | JsValue::Iterated(..)
693 | JsValue::Awaited(..)
694 | JsValue::TypeOf(..)
695 | JsValue::In(..) => JsValueMetaKind::Operation,
696 JsValue::Variable(..)
697 | JsValue::Argument(..)
698 | JsValue::FreeVar(..)
699 | JsValue::Module(..) => JsValueMetaKind::Placeholder,
700 }
701 }
702}
703
704impl<'a> JsValue<'a> {
706 pub fn alternatives(list: BumpVec<'a, JsValue<'a>>) -> Self {
707 Self::Alternatives {
708 total_nodes: 1 + total_nodes(&list),
709 values: list,
710 logical_property: None,
711 }
712 }
713
714 pub fn alternatives_with_additional_property(
715 list: BumpVec<'a, JsValue<'a>>,
716 logical_property: LogicalProperty,
717 ) -> Self {
718 Self::Alternatives {
719 total_nodes: 1 + total_nodes(&list),
720 values: list,
721 logical_property: Some(logical_property),
722 }
723 }
724
725 pub fn concat(list: BumpVec<'a, JsValue<'a>>) -> Self {
726 Self::Concat(1 + total_nodes(&list), list)
727 }
728
729 pub fn add(list: BumpVec<'a, JsValue<'a>>) -> Self {
730 Self::Add(1 + total_nodes(&list), list)
731 }
732
733 pub fn logical_and(list: BumpVec<'a, JsValue<'a>>) -> Self {
734 Self::Logical(1 + total_nodes(&list), LogicalOperator::And, list)
735 }
736
737 pub fn logical_or(list: BumpVec<'a, JsValue<'a>>) -> Self {
738 Self::Logical(1 + total_nodes(&list), LogicalOperator::Or, list)
739 }
740
741 pub fn nullish_coalescing(list: BumpVec<'a, JsValue<'a>>) -> Self {
742 Self::Logical(
743 1 + total_nodes(&list),
744 LogicalOperator::NullishCoalescing,
745 list,
746 )
747 }
748
749 pub fn tenary(arena: &'a Bump, test: JsValue<'a>, cons: JsValue<'a>, alt: JsValue<'a>) -> Self {
750 Self::Tenary(
751 1 + test.total_nodes() + cons.total_nodes() + alt.total_nodes(),
752 BumpBox::new_in(test, arena),
753 BumpBox::new_in(cons, arena),
754 BumpBox::new_in(alt, arena),
755 )
756 }
757
758 pub fn iterated(arena: &'a Bump, iterable: JsValue<'a>) -> Self {
759 Self::Iterated(1 + iterable.total_nodes(), BumpBox::new_in(iterable, arena))
760 }
761
762 pub fn equal(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
763 Self::Binary(
764 1 + a.total_nodes() + b.total_nodes(),
765 BumpBox::new_in(a, arena),
766 BinaryOperator::Equal,
767 BumpBox::new_in(b, arena),
768 )
769 }
770
771 pub fn not_equal(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
772 Self::Binary(
773 1 + a.total_nodes() + b.total_nodes(),
774 BumpBox::new_in(a, arena),
775 BinaryOperator::NotEqual,
776 BumpBox::new_in(b, arena),
777 )
778 }
779
780 pub fn strict_equal(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
781 Self::Binary(
782 1 + a.total_nodes() + b.total_nodes(),
783 BumpBox::new_in(a, arena),
784 BinaryOperator::StrictEqual,
785 BumpBox::new_in(b, arena),
786 )
787 }
788
789 pub fn strict_not_equal(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
790 Self::Binary(
791 1 + a.total_nodes() + b.total_nodes(),
792 BumpBox::new_in(a, arena),
793 BinaryOperator::StrictNotEqual,
794 BumpBox::new_in(b, arena),
795 )
796 }
797
798 pub fn r#in(arena: &'a Bump, a: JsValue<'a>, b: JsValue<'a>) -> Self {
799 Self::In(
800 1 + a.total_nodes() + b.total_nodes(),
801 BumpBox::new_in(a, arena),
802 BumpBox::new_in(b, arena),
803 )
804 }
805
806 pub fn logical_not(arena: &'a Bump, inner: JsValue<'a>) -> Self {
807 Self::Not(1 + inner.total_nodes(), BumpBox::new_in(inner, arena))
808 }
809
810 pub fn type_of(arena: &'a Bump, operand: JsValue<'a>) -> Self {
811 Self::TypeOf(1 + operand.total_nodes(), BumpBox::new_in(operand, arena))
812 }
813
814 pub fn array(items: BumpVec<'a, JsValue<'a>>) -> Self {
815 Self::Array {
816 total_nodes: 1 + total_nodes(&items),
817 items,
818 mutable: true,
819 }
820 }
821
822 pub fn frozen_array(items: BumpVec<'a, JsValue<'a>>) -> Self {
823 Self::Array {
824 total_nodes: 1 + total_nodes(&items),
825 items,
826 mutable: false,
827 }
828 }
829
830 pub fn function(
831 arena: &'a Bump,
832 func_ident: u32,
833 is_async: bool,
834 is_generator: bool,
835 return_value: JsValue<'a>,
836 ) -> Self {
837 let return_value = if is_generator {
839 JsValue::WellKnownObject(WellKnownObjectKind::Generator)
840 } else if is_async {
841 JsValue::promise(arena, return_value)
842 } else {
843 return_value
844 };
845 Self::Function(
846 1 + return_value.total_nodes(),
847 func_ident,
848 BumpBox::new_in(return_value, arena),
849 )
850 }
851
852 pub fn object(list: BumpVec<'a, ObjectPart<'a>>) -> Self {
853 Self::Object {
854 total_nodes: 1 + list
855 .iter()
856 .map(|v| match v {
857 ObjectPart::KeyValue(k, v) => k.total_nodes() + v.total_nodes(),
858 ObjectPart::Spread(s) => s.total_nodes(),
859 })
860 .sum::<u32>(),
861 parts: list,
862 mutable: true,
863 }
864 }
865
866 pub fn frozen_object(list: BumpVec<'a, ObjectPart<'a>>) -> Self {
867 Self::Object {
868 total_nodes: 1 + list
869 .iter()
870 .map(|v| match v {
871 ObjectPart::KeyValue(k, v) => k.total_nodes() + v.total_nodes(),
872 ObjectPart::Spread(s) => s.total_nodes(),
873 })
874 .sum::<u32>(),
875 parts: list,
876 mutable: false,
877 }
878 }
879
880 pub fn new_from_parts(arena: &'a Bump, f: JsValue<'a>, args: BumpVec<'a, JsValue<'a>>) -> Self {
889 let total = 1 + f.total_nodes() + total_nodes(&args);
890 Self::New(total, CallList::from_parts(arena, f, args))
891 }
892
893 pub fn new_from_iter<I>(arena: &'a Bump, f: JsValue<'a>, args: I) -> Self
898 where
899 I: IntoIterator<Item = JsValue<'a>>,
900 I::IntoIter: ExactSizeIterator,
901 {
902 let list = CallList::from_iter(arena, f, args);
903 let total = 1 + total_nodes(&list.0);
904 Self::New(total, list)
905 }
906
907 pub fn call_from_parts(
914 arena: &'a Bump,
915 f: JsValue<'a>,
916 args: BumpVec<'a, JsValue<'a>>,
917 ) -> Self {
918 let total = 1 + f.total_nodes() + total_nodes(&args);
919 Self::Call(total, CallList::from_parts(arena, f, args))
920 }
921
922 pub fn call_from_iter<I>(arena: &'a Bump, f: JsValue<'a>, args: I) -> Self
927 where
928 I: IntoIterator<Item = JsValue<'a>>,
929 I::IntoIter: ExactSizeIterator,
930 {
931 let list = CallList::from_iter(arena, f, args);
932 let total = 1 + total_nodes(&list.0);
933 Self::Call(total, list)
934 }
935
936 pub fn super_call(args: BumpBox<'a, [JsValue<'a>]>) -> Self {
937 Self::SuperCall(1 + total_nodes(&args), args)
938 }
939
940 pub fn member_call_from_parts(
947 arena: &'a Bump,
948 o: JsValue<'a>,
949 p: JsValue<'a>,
950 args: BumpVec<'a, JsValue<'a>>,
951 ) -> Self {
952 let total = 1 + o.total_nodes() + p.total_nodes() + total_nodes(&args);
953 Self::MemberCall(total, MemberCallList::from_parts(arena, o, p, args))
954 }
955
956 pub fn member_call_from_iter<I>(
962 arena: &'a Bump,
963 o: JsValue<'a>,
964 p: JsValue<'a>,
965 args: I,
966 ) -> Self
967 where
968 I: IntoIterator<Item = JsValue<'a>>,
969 I::IntoIter: ExactSizeIterator,
970 {
971 let list = MemberCallList::from_iter(arena, o, p, args);
972 let total = 1 + total_nodes(&list.0);
973 Self::MemberCall(total, list)
974 }
975
976 pub fn member(arena: &'a Bump, o: JsValue<'a>, p: JsValue<'a>) -> Self {
977 Self::Member(
978 1 + o.total_nodes() + p.total_nodes(),
979 BumpBox::new_in(o, arena),
980 BumpBox::new_in(p, arena),
981 )
982 }
983
984 pub fn promise(arena: &'a Bump, operand: JsValue<'a>) -> Self {
985 if let JsValue::Promise(_, _) = operand {
987 return operand;
988 }
989 Self::Promise(1 + operand.total_nodes(), BumpBox::new_in(operand, arena))
990 }
991
992 pub fn awaited(arena: &'a Bump, operand: JsValue<'a>) -> Self {
993 Self::Awaited(1 + operand.total_nodes(), BumpBox::new_in(operand, arena))
994 }
995
996 pub fn unknown(value: impl Into<Arc<JsValue<'a>>>, side_effects: bool, reason: RcStr) -> Self {
997 Self::Unknown {
998 original_value: Some(value.into()),
999 reason,
1000 has_side_effects: side_effects,
1001 }
1002 }
1003
1004 pub fn unknown_empty(side_effects: bool, reason: RcStr) -> Self {
1005 Self::Unknown {
1006 original_value: None,
1007 reason,
1008 has_side_effects: side_effects,
1009 }
1010 }
1011
1012 pub fn unknown_if(
1013 is_unknown: bool,
1014 value: JsValue<'a>,
1015 side_effects: bool,
1016 reason: RcStr,
1017 ) -> Self {
1018 if is_unknown {
1019 Self::Unknown {
1020 original_value: Some(value.into()),
1021 reason,
1022 has_side_effects: side_effects,
1023 }
1024 } else {
1025 value
1026 }
1027 }
1028}
1029
1030impl JsValue<'_> {
1032 pub fn has_children(&self) -> bool {
1033 self.total_nodes() > 1
1034 }
1035
1036 pub fn total_nodes(&self) -> u32 {
1037 match self {
1038 JsValue::Constant(_)
1039 | JsValue::Url(_, _)
1040 | JsValue::FreeVar(_)
1041 | JsValue::Variable(_)
1042 | JsValue::Module(..)
1043 | JsValue::WellKnownObject(_)
1044 | JsValue::WellKnownFunction(_)
1045 | JsValue::Unknown { .. }
1046 | JsValue::Argument(..) => 1,
1047
1048 JsValue::Array { total_nodes: c, .. }
1049 | JsValue::Object { total_nodes: c, .. }
1050 | JsValue::Alternatives { total_nodes: c, .. }
1051 | JsValue::Concat(c, _)
1052 | JsValue::Add(c, _)
1053 | JsValue::Not(c, _)
1054 | JsValue::Logical(c, _, _)
1055 | JsValue::Binary(c, _, _, _)
1056 | JsValue::Tenary(c, _, _, _)
1057 | JsValue::New(c, _)
1058 | JsValue::Call(c, _)
1059 | JsValue::SuperCall(c, _)
1060 | JsValue::MemberCall(c, _)
1061 | JsValue::Member(c, _, _)
1062 | JsValue::Function(c, _, _)
1063 | JsValue::Iterated(c, ..)
1064 | JsValue::Promise(c, ..)
1065 | JsValue::Awaited(c, ..)
1066 | JsValue::TypeOf(c, ..)
1067 | JsValue::In(c, ..) => *c,
1068 }
1069 }
1070
1071 pub(crate) fn update_total_nodes(&mut self) {
1072 match self {
1073 JsValue::Constant(_)
1074 | JsValue::Url(_, _)
1075 | JsValue::FreeVar(_)
1076 | JsValue::Variable(_)
1077 | JsValue::Module(..)
1078 | JsValue::WellKnownObject(_)
1079 | JsValue::WellKnownFunction(_)
1080 | JsValue::Unknown { .. }
1081 | JsValue::Argument(..) => {}
1082
1083 JsValue::Array {
1084 total_nodes: c,
1085 items: list,
1086 ..
1087 }
1088 | JsValue::Alternatives {
1089 total_nodes: c,
1090 values: list,
1091 ..
1092 }
1093 | JsValue::Concat(c, list)
1094 | JsValue::Add(c, list)
1095 | JsValue::Logical(c, _, list) => {
1096 *c = 1 + total_nodes(list);
1097 }
1098
1099 JsValue::Binary(c, a, _, b) => {
1100 *c = 1 + a.total_nodes() + b.total_nodes();
1101 }
1102 JsValue::Tenary(c, test, cons, alt) => {
1103 *c = 1 + test.total_nodes() + cons.total_nodes() + alt.total_nodes();
1104 }
1105 JsValue::Not(c, r) => {
1106 *c = 1 + r.total_nodes();
1107 }
1108 JsValue::Promise(c, r) => {
1109 *c = 1 + r.total_nodes();
1110 }
1111 JsValue::Awaited(c, r) => {
1112 *c = 1 + r.total_nodes();
1113 }
1114
1115 JsValue::Object {
1116 total_nodes: c,
1117 parts,
1118 mutable: _,
1119 } => {
1120 *c = 1 + parts
1121 .iter()
1122 .map(|v| match v {
1123 ObjectPart::KeyValue(k, v) => k.total_nodes() + v.total_nodes(),
1124 ObjectPart::Spread(s) => s.total_nodes(),
1125 })
1126 .sum::<u32>();
1127 }
1128 JsValue::New(c, call) => {
1129 *c = 1 + call.total_nodes();
1130 }
1131 JsValue::Call(c, call) => {
1132 *c = 1 + call.total_nodes();
1133 }
1134 JsValue::SuperCall(c, args) => {
1135 *c = 1 + total_nodes(args);
1136 }
1137 JsValue::MemberCall(c, call) => {
1138 *c = 1 + call.total_nodes();
1139 }
1140 JsValue::Member(c, o, p) => {
1141 *c = 1 + o.total_nodes() + p.total_nodes();
1142 }
1143 JsValue::Function(c, _, r) => {
1144 *c = 1 + r.total_nodes();
1145 }
1146
1147 JsValue::Iterated(c, iterable) => {
1148 *c = 1 + iterable.total_nodes();
1149 }
1150
1151 JsValue::TypeOf(c, operand) => {
1152 *c = 1 + operand.total_nodes();
1153 }
1154 JsValue::In(c, l, r) => {
1155 *c = 1 + l.total_nodes() + r.total_nodes();
1156 }
1157 }
1158 }
1159
1160 #[cfg(debug_assertions)]
1161 pub fn debug_assert_total_nodes_up_to_date(&mut self) {
1162 let old = self.total_nodes();
1163 self.update_total_nodes();
1164 assert_eq!(
1165 old,
1166 self.total_nodes(),
1167 "total nodes not up to date {self:?}"
1168 );
1169 }
1170
1171 #[cfg(not(debug_assertions))]
1172 pub fn debug_assert_total_nodes_up_to_date(&mut self) {}
1173}
1174
1175impl<'a> JsValue<'a> {
1177 pub fn make_unknown(&mut self, side_effects: bool, reason: RcStr) {
1179 *self = JsValue::unknown(take(self), side_effects || self.has_side_effects(), reason);
1180 }
1181
1182 pub fn into_unknown(mut self, side_effects: bool, reason: RcStr) -> Self {
1184 self.make_unknown(side_effects, reason);
1185 self
1186 }
1187
1188 pub fn make_unknown_without_content(&mut self, side_effects: bool, reason: RcStr) {
1191 *self = JsValue::unknown_empty(side_effects || self.has_side_effects(), reason);
1192 }
1193
1194 pub fn make_nested_operations_unknown(&mut self) -> bool {
1196 fn inner(this: &mut JsValue) -> bool {
1197 if matches!(this.meta_type(), JsValueMetaKind::Operation) {
1198 this.make_unknown(false, rcstr!("nested operation"));
1199 true
1200 } else {
1201 this.for_each_children_mut(&mut inner)
1202 }
1203 }
1204 if matches!(self.meta_type(), JsValueMetaKind::Operation) {
1205 self.for_each_children_mut(&mut inner)
1206 } else {
1207 false
1208 }
1209 }
1210
1211 pub fn add_unknown_mutations(&mut self, arena: &'a Bump, side_effects: bool) {
1212 self.add_alt(
1213 arena,
1214 JsValue::unknown_empty(side_effects, rcstr!("unknown mutation")),
1215 );
1216 }
1217}
1218
1219impl JsValue<'_> {
1221 pub fn get_definable_name(
1230 &self,
1231 var_graph: Option<&VarGraph<'_>>,
1232 ) -> Option<(DefinableNameSegmentRefs<'_>, bool)> {
1233 let mut current = self;
1234 let mut segments = SmallVec::new();
1235 let mut potentially_reassigned = false;
1236 loop {
1237 match current {
1238 JsValue::FreeVar(name) => {
1239 if var_graph.is_some_and(|var_graph| {
1240 var_graph
1241 .free_var_ids
1242 .get(name)
1243 .is_some_and(|id| var_graph.values.contains_key(id))
1244 }) {
1245 potentially_reassigned = true;
1247 }
1248 segments.push(DefinableNameSegmentRef::Name(name));
1249 break;
1250 }
1251 JsValue::Member(_, obj, prop) => {
1252 segments.push(DefinableNameSegmentRef::Name(prop.as_str()?));
1253 current = obj;
1254 }
1255 JsValue::WellKnownObject(obj) => {
1256 segments.extend(
1257 obj.as_define_name()?
1258 .iter()
1259 .rev()
1260 .copied()
1261 .map(DefinableNameSegmentRef::Name),
1262 );
1263 break;
1264 }
1265 JsValue::WellKnownFunction(func) => {
1266 segments.extend(
1267 func.as_define_name()?
1268 .iter()
1269 .rev()
1270 .copied()
1271 .map(DefinableNameSegmentRef::Name),
1272 );
1273 break;
1274 }
1275 JsValue::MemberCall(_, call) if call.args().is_empty() => {
1276 segments.push(DefinableNameSegmentRef::Call(call.prop().as_str()?));
1277 current = call.obj();
1278 }
1279 JsValue::TypeOf(_, arg) => {
1280 segments.push(DefinableNameSegmentRef::TypeOf);
1281 current = arg;
1282 }
1283 _ => return None,
1284 }
1285 }
1286 segments.reverse();
1287 Some((DefinableNameSegmentRefs(segments), potentially_reassigned))
1288 }
1289}
1290
1291impl<'a> JsValue<'a> {
1294 pub fn clone_in(&self, arena: &'a Bump) -> JsValue<'a> {
1296 match self {
1297 JsValue::Constant(v) => JsValue::Constant(v.clone()),
1298 JsValue::Url(s, k) => JsValue::Url(s.clone(), *k),
1299 JsValue::WellKnownObject(k) => JsValue::WellKnownObject(k.clone()),
1300 JsValue::WellKnownFunction(k) => JsValue::WellKnownFunction(k.clone()),
1301 JsValue::Unknown {
1302 original_value,
1303 reason,
1304 has_side_effects,
1305 } => JsValue::Unknown {
1306 original_value: original_value.clone(),
1307 reason: reason.clone(),
1308 has_side_effects: *has_side_effects,
1309 },
1310 JsValue::Array {
1311 total_nodes,
1312 items,
1313 mutable,
1314 } => JsValue::Array {
1315 total_nodes: *total_nodes,
1316 items: BumpVec::from_iter_in(arena, items.iter().map(|v| v.clone_in(arena))),
1317 mutable: *mutable,
1318 },
1319 JsValue::Object {
1320 total_nodes,
1321 parts,
1322 mutable,
1323 } => JsValue::Object {
1324 total_nodes: *total_nodes,
1325 parts: BumpVec::from_iter_in(arena, parts.iter().map(|p| p.clone_in(arena))),
1326 mutable: *mutable,
1327 },
1328 JsValue::Alternatives {
1329 total_nodes,
1330 values,
1331 logical_property,
1332 } => JsValue::Alternatives {
1333 total_nodes: *total_nodes,
1334 values: BumpVec::from_iter_in(arena, values.iter().map(|v| v.clone_in(arena))),
1335 logical_property: *logical_property,
1336 },
1337 JsValue::Function(c, id, r) => {
1338 JsValue::Function(*c, *id, BumpBox::new_in(r.clone_in(arena), arena))
1339 }
1340 JsValue::Concat(c, list) => JsValue::Concat(
1341 *c,
1342 BumpVec::from_iter_in(arena, list.iter().map(|v| v.clone_in(arena))),
1343 ),
1344 JsValue::Add(c, list) => JsValue::Add(
1345 *c,
1346 BumpVec::from_iter_in(arena, list.iter().map(|v| v.clone_in(arena))),
1347 ),
1348 JsValue::Not(c, v) => JsValue::Not(*c, BumpBox::new_in(v.clone_in(arena), arena)),
1349 JsValue::Logical(c, op, list) => JsValue::Logical(
1350 *c,
1351 *op,
1352 BumpVec::from_iter_in(arena, list.iter().map(|v| v.clone_in(arena))),
1353 ),
1354 JsValue::Binary(c, a, op, b) => JsValue::Binary(
1355 *c,
1356 BumpBox::new_in(a.clone_in(arena), arena),
1357 *op,
1358 BumpBox::new_in(b.clone_in(arena), arena),
1359 ),
1360 JsValue::New(c, call) => JsValue::New(*c, call.clone_in(arena)),
1361 JsValue::Call(c, call) => JsValue::Call(*c, call.clone_in(arena)),
1362 JsValue::SuperCall(c, args) => JsValue::SuperCall(
1363 *c,
1364 bumpalo::collections::Vec::from_iter_in(
1365 args.iter().map(|v| v.clone_in(arena)),
1366 arena,
1367 )
1368 .into_boxed_slice(),
1369 ),
1370 JsValue::MemberCall(c, call) => JsValue::MemberCall(*c, call.clone_in(arena)),
1371 JsValue::Member(c, o, p) => JsValue::Member(
1372 *c,
1373 BumpBox::new_in(o.clone_in(arena), arena),
1374 BumpBox::new_in(p.clone_in(arena), arena),
1375 ),
1376 JsValue::Tenary(c, test, cons, alt) => JsValue::Tenary(
1377 *c,
1378 BumpBox::new_in(test.clone_in(arena), arena),
1379 BumpBox::new_in(cons.clone_in(arena), arena),
1380 BumpBox::new_in(alt.clone_in(arena), arena),
1381 ),
1382 JsValue::Promise(c, v) => {
1383 JsValue::Promise(*c, BumpBox::new_in(v.clone_in(arena), arena))
1384 }
1385 JsValue::Awaited(c, v) => {
1386 JsValue::Awaited(*c, BumpBox::new_in(v.clone_in(arena), arena))
1387 }
1388 JsValue::Iterated(c, v) => {
1389 JsValue::Iterated(*c, BumpBox::new_in(v.clone_in(arena), arena))
1390 }
1391 JsValue::TypeOf(c, v) => JsValue::TypeOf(*c, BumpBox::new_in(v.clone_in(arena), arena)),
1392 JsValue::In(c, l, r) => JsValue::In(
1393 *c,
1394 BumpBox::new_in(l.clone_in(arena), arena),
1395 BumpBox::new_in(r.clone_in(arena), arena),
1396 ),
1397 JsValue::Variable(id) => JsValue::Variable(id.clone()),
1398 JsValue::Argument(i, idx) => JsValue::Argument(*i, *idx),
1399 JsValue::FreeVar(a) => JsValue::FreeVar(a.clone()),
1400 JsValue::Module(m) => JsValue::Module(m.clone()),
1401 }
1402 }
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407 use super::*;
1408
1409 #[test]
1410 #[cfg(target_pointer_width = "64")]
1411 fn jsvalue_size() {
1412 assert_eq!(32, size_of::<JsValue>());
1413 }
1414}