Skip to main content

turbopack_ecmascript/analyzer/jsvalue/
mod.rs

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
34/// Sum of [`JsValue::total_nodes`] across a slice of values.
35fn total_nodes(vec: &[JsValue<'_>]) -> u32 {
36    vec.iter().map(|v| v.total_nodes()).sum::<u32>()
37}
38
39/// Join `items` for display, switching to a multi-line layout when the content
40/// is long. Shared by the `Display` impl and the `explain` formatting.
41fn 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/// TODO: Use `Arc`
82///
83/// There are 4 kinds of values: Leaves, Nested, Operations, and Placeholders
84/// (see `JsValueMetaKind` for details). Values are processed in two phases:
85/// - Analyze phase: We convert AST into `JsValue`s. We don't have contextual information so we need
86///   to insert placeholders to represent that.
87/// - Link phase: We try to reduce a value to a constant value. The link phase has 5 substeps that
88///   are executed on each node in the graph depth-first. When a value is modified, we need to visit
89///   the new children again.
90/// - Replace variables with their values. This replaces [JsValue::Variable]. No variable should be
91///   remaining after that.
92/// - Replace placeholders with contextual information. This usually replaces [JsValue::FreeVar] and
93///   [JsValue::Module]. Some [JsValue::Call] on well- known functions might also be replaced. No
94///   free vars or modules should be remaining after that.
95/// - Replace operations on well-known objects and functions. This handles [JsValue::Call] and
96///   [JsValue::Member] on well-known objects and functions.
97/// - Replace all built-in functions with their values when they are compile-time constant.
98/// - For optimization, any nested operations are replaced with [JsValue::Unknown]. So only one
99///   layer of operation remains. Any remaining operation or placeholder can be treated as unknown.
100#[derive(Debug, Hash, PartialEq)]
101pub enum JsValue<'a> {
102    // LEAF VALUES
103    // ----------------------------
104    /// A constant primitive value.
105    Constant(ConstantValue),
106    /// A constant URL object.
107    Url(ConstantString, JsValueUrlKind),
108    /// Some kind of well-known object
109    /// (must not be an array, otherwise Array.concat needs to be changed)
110    WellKnownObject(WellKnownObjectKind),
111    /// Some kind of well-known function
112    WellKnownFunction(WellKnownFunctionKind<'a>),
113    /// Not-analyzable value. Might contain the original value for additional
114    /// info. Has a reason string for explanation.
115    Unknown {
116        original_value: Option<Arc<JsValue<'a>>>,
117        reason: RcStr,
118        has_side_effects: bool,
119    },
120
121    // NESTED VALUES
122    // ----------------------------
123    /// An array of nested values
124    Array {
125        total_nodes: u32,
126        items: BumpVec<'a, JsValue<'a>>,
127        mutable: bool,
128    },
129    /// An object of nested values
130    Object {
131        total_nodes: u32,
132        parts: BumpVec<'a, ObjectPart<'a>>,
133        mutability: ObjectMutability,
134    },
135    /// A list of alternative values
136    Alternatives {
137        total_nodes: u32,
138        values: BumpVec<'a, JsValue<'a>>,
139        logical_property: Option<LogicalProperty>,
140    },
141    /// A function reference. The return value might contain [JsValue::Argument]
142    /// placeholders that need to be replaced when calling this function.
143    /// `(total_node_count, func_ident, return_value)`
144    Function(u32, u32, BumpBox<'a, JsValue<'a>>),
145
146    // OPERATIONS
147    // ----------------------------
148    /// A string concatenation of values.
149    /// `foo.${unknownVar}.js` => 'foo' + Unknown + '.js'
150    Concat(u32, BumpVec<'a, JsValue<'a>>),
151    /// An addition of values.
152    /// This can be converted to [JsValue::Concat] if the type of the variable
153    /// is string.
154    Add(u32, BumpVec<'a, JsValue<'a>>),
155    /// Logical negation `!expr`
156    Not(u32, BumpBox<'a, JsValue<'a>>),
157    /// Logical operator chain e. g. `expr && expr`
158    Logical(u32, LogicalOperator, BumpVec<'a, JsValue<'a>>),
159    /// Binary expression e. g. `expr == expr`
160    Binary(
161        u32,
162        BumpBox<'a, JsValue<'a>>,
163        BinaryOperator,
164        BumpBox<'a, JsValue<'a>>,
165    ),
166    /// A constructor call. `(total_node_count, list)` — see [`CallList`].
167    New(u32, CallList<'a>),
168    /// A function call without a `this` context. `(total_node_count, list)` — see [`CallList`].
169    Call(u32, CallList<'a>),
170    /// A super call to the parent constructor.
171    /// `(total_node_count, args)`
172    SuperCall(u32, BumpBox<'a, [JsValue<'a>]>),
173    /// A function call with a `this` context. `(total_node_count, list)` — see [`MemberCallList`].
174    MemberCall(u32, MemberCallList<'a>),
175    /// A member access `obj[prop]`
176    /// `(total_node_count, obj, prop)`
177    Member(u32, BumpBox<'a, JsValue<'a>>, BumpBox<'a, JsValue<'a>>),
178    /// A tenary operator `test ? cons : alt`
179    /// `(total_node_count, test, cons, alt)`
180    Tenary(
181        u32,
182        BumpBox<'a, JsValue<'a>>,
183        BumpBox<'a, JsValue<'a>>,
184        BumpBox<'a, JsValue<'a>>,
185    ),
186    /// A promise resolving to some value
187    /// `(total_node_count, value)`
188    Promise(u32, BumpBox<'a, JsValue<'a>>),
189    /// An await call (potentially) unwrapping a promise.
190    /// `(total_node_count, value)`
191    Awaited(u32, BumpBox<'a, JsValue<'a>>),
192
193    /// A for-of loop
194    ///
195    /// `(total_node_count, iterable)`
196    Iterated(u32, BumpBox<'a, JsValue<'a>>),
197
198    /// A `typeof` expression.
199    ///
200    /// `(total_node_count, operand)`
201    TypeOf(u32, BumpBox<'a, JsValue<'a>>),
202
203    /// A `in` expression `left in right`
204    /// `(total_node_count, left, right)`
205    In(u32, BumpBox<'a, JsValue<'a>>, BumpBox<'a, JsValue<'a>>),
206
207    // PLACEHOLDERS
208    // ----------------------------
209    /// A reference to a variable.
210    Variable(Id),
211    /// A reference to an function argument.
212    /// (func_ident, arg_index)
213    Argument(u32, usize),
214    // TODO no predefined kinds, only Atom
215    /// A reference to a free variable.
216    FreeVar(Atom),
217    /// This is a reference to a imported module.
218    Module(ModuleValue),
219}
220
221/// Storage for [`JsValue::MemberCall`]: `[args..., prop, obj]`.
222///
223/// The reversed layout (obj/prop at the tail) is what makes the `replace_builtin`
224/// fallthrough path cheap: `pop` obj, `pop` prop, and the remaining `Vec` **is** the args
225/// `Vec` with no reallocation.
226///
227/// The custom `Debug` impl re-emits the pre-refactor derived shape
228/// (`MemberCall(total, obj, prop, [args])`) by writing obj/prop/args as siblings inside the
229/// parent's `debug_tuple`. This keeps fixture snapshots identical to the 4-tuple-payload
230/// version without forcing a hand-written `Debug` on every `JsValue` arm.
231#[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        // Layout: [args..., prop, obj]
237        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            // The parent `debug_tuple` writes the field's leading indent for us (via
243            // PadAdapter) and appends `,\n` after we return. Emitting
244            // `<obj>,\n<prop>,\n<args>` with no trailing comma makes us appear as three
245            // sibling fields in the parent's pretty-print output.
246            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    /// The receiver object. Lives at the tail of the underlying `Vec`.
289    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    /// The accessed property. Lives one slot before `obj`.
299    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    /// The call arguments — everything before `prop` and `obj`.
309    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    /// Borrow `args`, `prop`, and `obj` simultaneously as mutable references. The single
320    /// `Vec` storage means callers can't get these via separate accessor calls.
321    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    /// Take everything out. The returned `args` `Vec` reuses the original allocation — no
329    /// copy. That's the point of storing obj/prop at the tail.
330    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/// Storage for [`JsValue::Call`] and [`JsValue::New`]: `[args..., callee]`.
363///
364/// Same trick as [`MemberCallList`]: keeping the callee at the tail lets
365/// `replace_builtin`-style fallthrough paths `pop` it off cheaply and reuse the remaining
366/// `Vec` as the owned args with no reallocation.
367///
368/// The custom `Debug` impl re-emits the pre-refactor `(callee, [args])` shape so fixture
369/// snapshots remain identical to the 3-tuple-payload version.
370#[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        // Layout: [args..., callee]
376        let n = self.0.len();
377        let callee = &self.0[n - 1];
378        let args = &self.0[..n - 1];
379        if f.alternate() {
380            // Same trick as MemberCallList: emit two sibling fields inside the parent
381            // `debug_tuple`'s pretty-print output.
382            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    /// The callee. Lives at the tail of the underlying `Vec`.
417    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    /// The call arguments — everything before the callee.
428    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    /// Borrow `args` and `callee` simultaneously as mutable references. The single `Vec`
439    /// storage means callers can't get these via separate accessor calls.
440    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    /// Take everything out. The returned `args` `Vec` reuses the original allocation — no
447    /// copy. That's the point of storing the callee at the tail.
448    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    /// Build a [`JsValue`] from a [`CompileTimeDefineValue`], allocating any nested structure in
535    /// `arena`. (Cannot be a `TryFrom` impl because the conversion needs the arena.)
536    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    /// Build a [`JsValue`] from a [`FreeVarReference`], allocating any nested structure in `arena`.
653    /// (Cannot be a `TryFrom` impl because the conversion needs the arena.)
654    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
703// Private meta methods
704impl 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
740// Constructors
741impl<'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        // Check generator first to handle async generators
874        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    /// Build a `JsValue::New` from a callee and an owned args `Vec`.
920    ///
921    /// Pushes `f` onto `args` to form the `[args..., callee]` layout. If `args.capacity()`
922    /// equals `args.len()`, this triggers a Vec realloc — only use this overload when the
923    /// caller already has a `Vec` that is likely to have spare capacity for the trailing
924    /// slot (e.g. an `args` Vec returned from [`CallList::into_parts`] or
925    /// [`MemberCallList::into_parts`]). For from-scratch construction use
926    /// [`JsValue::new_from_iter`], which pre-sizes the underlying allocation exactly.
927    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    /// Build a `JsValue::New` from a callee and an args iterator with a known length.
933    ///
934    /// Allocates the underlying `Vec` with exact capacity (`args.len() + 1`), so no realloc
935    /// occurs.
936    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    /// Build a `JsValue::Call` from a callee and an owned args `Vec`.
947    ///
948    /// See [`JsValue::new_from_parts`] for the realloc caveat — only use this when the
949    /// caller already has a `Vec` that is likely to be correctly sized (typically one
950    /// obtained from [`CallList::into_parts`] / [`MemberCallList::into_parts`]). For
951    /// from-scratch construction use [`JsValue::call_from_iter`].
952    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    /// Build a `JsValue::Call` from a callee and an args iterator with a known length.
962    ///
963    /// Allocates the underlying `Vec` with exact capacity (`args.len() + 1`), so no realloc
964    /// occurs.
965    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    /// Build a `JsValue::MemberCall` from `obj`, `prop`, and an owned args `Vec`.
980    ///
981    /// See [`JsValue::new_from_parts`] for the realloc caveat — only use this when the
982    /// caller already has a `Vec` that is likely to be correctly sized (typically one
983    /// obtained from [`MemberCallList::into_parts`]). For from-scratch construction use
984    /// [`JsValue::member_call_from_iter`].
985    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    /// Build a `JsValue::MemberCall` from `obj`, `prop`, and an args iterator with a known
996    /// length.
997    ///
998    /// Allocates the underlying `Vec` with exact capacity (`args.len() + 2`), so no realloc
999    /// occurs.
1000    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        // In ecmascript Promise<Promise<T>> is equivalent to Promise<T>
1025        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
1069// Methods regarding node count
1070impl 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
1214// Unknown management
1215impl<'a> JsValue<'a> {
1216    /// Convert the value into unknown with a specific reason.
1217    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    /// Convert the owned value into unknown with a specific reason.
1222    pub fn into_unknown(mut self, side_effects: bool, reason: RcStr) -> Self {
1223        self.make_unknown(side_effects, reason);
1224        self
1225    }
1226
1227    /// Convert the value into unknown with a specific reason, but don't retain
1228    /// the original value.
1229    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    /// Make all nested operations unknown when the value is an operation.
1234    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
1258// Definable name management
1259impl JsValue<'_> {
1260    // Clippy is wrong. It's not the same lifetime
1261    #[allow(mismatched_lifetime_syntaxes)]
1262    /// When the value has a user-definable name, return it in segments. Otherwise
1263    /// returns None.
1264    /// It also returns a boolean whether the variable was potentially reassigned.
1265    /// - any free var has itself as user-definable name: ["foo"]
1266    /// - any member access adds the identifier as segment after the object: ["foo", "prop"]
1267    /// - some well-known objects/functions have a user-definable names: ["import"]
1268    /// - member calls without arguments also have a user-definable name: ["foo", Call("func")]
1269    /// - typeof expressions add `typeof` after the argument's segments: ["foo", "typeof"]
1270    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                            // `foo` was potentially reassigned
1288                            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
1343// Arena-aware cloning (replaces the derived `Clone`, which is unavailable because the arena-backed
1344// `Box`/`Vec` children can't clone without the allocator).
1345impl<'a> JsValue<'a> {
1346    /// Deep-clone this value into `arena`, returning a fresh tree owned by that arena.
1347    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}