Skip to main content

turbopack_ecmascript/analyzer/jsvalue/
constants.rs

1use std::{
2    borrow::Cow,
3    fmt::{Display, Formatter},
4    hash::{Hash, Hasher},
5    num::NonZeroU32,
6    sync::Arc,
7};
8
9use bincode::{
10    Decode, Encode,
11    de::Decoder,
12    enc::Encoder,
13    error::{DecodeError, EncodeError},
14    impl_borrow_decode,
15};
16use num_bigint::BigInt;
17use num_traits::Zero;
18use swc_core::{
19    atoms::Wtf8Atom,
20    ecma::{ast::Lit, atoms::Atom},
21};
22use turbo_rcstr::RcStr;
23use turbo_tasks::NonLocalValue;
24
25use crate::{
26    analyzer::{Bump, JsValue, imports::ImportAnnotations},
27    utils::StringifyJs,
28};
29
30#[derive(Debug, Hash, PartialEq)]
31pub enum ObjectPart<'a> {
32    KeyValue(JsValue<'a>, JsValue<'a>),
33    Spread(JsValue<'a>),
34}
35
36impl Default for ObjectPart<'_> {
37    fn default() -> Self {
38        ObjectPart::Spread(Default::default())
39    }
40}
41
42impl<'a> ObjectPart<'a> {
43    /// Deep-clone this object part into `arena`. See [`JsValue::clone_in`].
44    pub(crate) fn clone_in(&self, arena: &'a Bump) -> Self {
45        match self {
46            ObjectPart::KeyValue(k, v) => {
47                ObjectPart::KeyValue(k.clone_in(arena), v.clone_in(arena))
48            }
49            ObjectPart::Spread(s) => ObjectPart::Spread(s.clone_in(arena)),
50        }
51    }
52}
53
54#[derive(Debug, Clone, PartialEq, Encode, Decode)]
55pub struct ConstantNumber(pub f64);
56
57impl ConstantNumber {
58    pub fn as_u32_index(&self) -> Option<usize> {
59        let index: u32 = self.0 as u32;
60        (index as f64 == self.0).then_some(index as usize)
61    }
62}
63
64impl Hash for ConstantNumber {
65    fn hash<H: Hasher>(&self, state: &mut H) {
66        self.0.to_ne_bytes().hash(state);
67    }
68}
69
70impl From<f64> for ConstantNumber {
71    fn from(value: f64) -> Self {
72        ConstantNumber(value)
73    }
74}
75
76#[derive(Debug, Clone)]
77pub enum ConstantString {
78    Atom(Atom),
79    RcStr(RcStr),
80}
81// SAFETY: ConstantString doesn't contain any Vcs
82unsafe impl NonLocalValue for ConstantString {}
83impl Encode for ConstantString {
84    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
85        match self {
86            ConstantString::Atom(s) => s.as_str().encode(encoder),
87            ConstantString::RcStr(s) => s.as_str().encode(encoder),
88        }
89    }
90}
91impl<Context> Decode<Context> for ConstantString {
92    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
93        Ok(Self::RcStr(Decode::decode(decoder)?))
94    }
95}
96impl_borrow_decode!(ConstantString);
97
98impl ConstantString {
99    pub fn as_str(&self) -> &str {
100        match self {
101            Self::Atom(s) => s,
102            Self::RcStr(s) => s,
103        }
104    }
105
106    pub fn as_rcstr(&self) -> RcStr {
107        match self {
108            Self::Atom(s) => RcStr::from(s.as_str()),
109            Self::RcStr(s) => s.clone(),
110        }
111    }
112
113    pub fn as_atom(&self) -> Cow<'_, Atom> {
114        match self {
115            Self::Atom(s) => Cow::Borrowed(s),
116            Self::RcStr(s) => Cow::Owned(s.as_str().into()),
117        }
118    }
119
120    pub fn is_empty(&self) -> bool {
121        self.as_str().is_empty()
122    }
123}
124
125impl PartialEq for ConstantString {
126    fn eq(&self, other: &Self) -> bool {
127        self.as_str() == other.as_str()
128    }
129}
130
131impl Eq for ConstantString {}
132
133impl Hash for ConstantString {
134    fn hash<H: Hasher>(&self, state: &mut H) {
135        self.as_str().hash(state);
136    }
137}
138
139impl Display for ConstantString {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        self.as_str().fmt(f)
142    }
143}
144
145impl From<Atom> for ConstantString {
146    fn from(v: Atom) -> Self {
147        ConstantString::Atom(v)
148    }
149}
150
151impl From<&'static str> for ConstantString {
152    fn from(v: &'static str) -> Self {
153        ConstantString::Atom(v.into())
154    }
155}
156
157impl From<String> for ConstantString {
158    fn from(v: String) -> Self {
159        ConstantString::Atom(v.into())
160    }
161}
162
163impl From<RcStr> for ConstantString {
164    fn from(v: RcStr) -> Self {
165        ConstantString::RcStr(v)
166    }
167}
168
169#[derive(Debug, Clone, PartialEq, Default, Hash, Encode, Decode)]
170pub enum ConstantValue {
171    #[default]
172    Undefined,
173    Str(ConstantString),
174    Num(ConstantNumber),
175    True,
176    False,
177    Null,
178    BigInt(#[bincode(with_serde)] Box<BigInt>),
179    Regex(#[bincode(with_serde)] Box<(Atom, Atom)>),
180}
181unsafe impl NonLocalValue for ConstantValue {}
182
183impl ConstantValue {
184    pub fn as_str(&self) -> Option<&str> {
185        match self {
186            Self::Str(s) => Some(s.as_str()),
187            _ => None,
188        }
189    }
190
191    pub fn as_bool(&self) -> Option<bool> {
192        match self {
193            Self::True => Some(true),
194            Self::False => Some(false),
195            _ => None,
196        }
197    }
198
199    pub fn is_truthy(&self) -> bool {
200        match self {
201            Self::Undefined | Self::False | Self::Null => false,
202            Self::True | Self::Regex(..) => true,
203            Self::Str(s) => !s.is_empty(),
204            Self::Num(ConstantNumber(n)) => *n != 0.0,
205            Self::BigInt(n) => !n.is_zero(),
206        }
207    }
208
209    pub fn is_nullish(&self) -> bool {
210        match self {
211            Self::Undefined | Self::Null => true,
212            Self::Str(..)
213            | Self::Num(..)
214            | Self::True
215            | Self::False
216            | Self::BigInt(..)
217            | Self::Regex(..) => false,
218        }
219    }
220
221    pub fn is_empty_string(&self) -> bool {
222        match self {
223            Self::Str(s) => s.is_empty(),
224            _ => false,
225        }
226    }
227
228    pub fn is_value_type(&self) -> bool {
229        !matches!(self, Self::Regex(..))
230    }
231}
232
233impl From<bool> for ConstantValue {
234    fn from(v: bool) -> Self {
235        match v {
236            true => ConstantValue::True,
237            false => ConstantValue::False,
238        }
239    }
240}
241
242impl From<&'_ str> for ConstantValue {
243    fn from(v: &str) -> Self {
244        ConstantValue::Str(ConstantString::Atom(v.into()))
245    }
246}
247
248impl From<Lit> for ConstantValue {
249    fn from(v: Lit) -> Self {
250        match v {
251            Lit::Str(v) => {
252                ConstantValue::Str(ConstantString::Atom(v.value.to_atom_lossy().into_owned()))
253            }
254            Lit::Bool(v) => {
255                if v.value {
256                    ConstantValue::True
257                } else {
258                    ConstantValue::False
259                }
260            }
261            Lit::Null(_) => ConstantValue::Null,
262            Lit::Num(v) => ConstantValue::Num(ConstantNumber(v.value)),
263            Lit::BigInt(v) => ConstantValue::BigInt(v.value),
264            Lit::Regex(v) => ConstantValue::Regex(Box::new((v.exp, v.flags))),
265            Lit::JSXText(v) => {
266                // TODO
267                ConstantValue::Str(ConstantString::Atom(v.value.to_atom_lossy().into_owned()))
268            }
269        }
270    }
271}
272
273impl Display for ConstantValue {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        match self {
276            ConstantValue::Undefined => write!(f, "undefined"),
277            ConstantValue::Str(str) => write!(f, "{}", StringifyJs(str.as_str())),
278            ConstantValue::True => write!(f, "true"),
279            ConstantValue::False => write!(f, "false"),
280            ConstantValue::Null => write!(f, "null"),
281            ConstantValue::Num(ConstantNumber(n)) => write!(f, "{n}"),
282            ConstantValue::BigInt(n) => write!(f, "{n}"),
283            ConstantValue::Regex(regex) => write!(f, "/{}/{}", regex.0, regex.1),
284        }
285    }
286}
287
288#[derive(Debug, Clone, Hash, PartialEq, Eq)]
289pub struct ModuleValue {
290    pub module: Wtf8Atom,
291    pub annotations: Option<Arc<ImportAnnotations>>,
292    /// Whether to analyze this module for constants
293    // TODO this is a hack: ideally we'd have truly "bidirectional linking" instead of the current
294    // `early_visitor` plus `visitor` setup. Then this could just be implemented with a rewrite
295    // rule for `Member(ModuleValue, prop) if prop.as_str().is_upper_case() => { ... }`
296    pub analyze_for_constants: bool,
297    // This is an Option<NonZeroU32> to JsValue to only be 32 bytes in size.
298    pub reference: Option<ModuleReferenceIndex>,
299}
300
301#[derive(Copy, Debug, Clone, Hash, PartialEq, Eq)]
302pub struct ModuleReferenceIndex(NonZeroU32);
303
304impl From<u32> for ModuleReferenceIndex {
305    fn from(value: u32) -> Self {
306        // The only way this can overflow is if value == u32::MAX, which is unlikely since it would
307        // require us to have that many module references.
308        ModuleReferenceIndex(NonZeroU32::new(value + 1).unwrap())
309    }
310}
311impl ModuleReferenceIndex {
312    pub fn get(&self) -> usize {
313        (self.0.get() - 1) as usize
314    }
315}
316
317#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
318pub enum LogicalOperator {
319    And,
320    Or,
321    NullishCoalescing,
322}
323
324impl LogicalOperator {
325    pub(super) fn joiner(&self) -> &'static str {
326        match self {
327            LogicalOperator::And => " && ",
328            LogicalOperator::Or => " || ",
329            LogicalOperator::NullishCoalescing => " ?? ",
330        }
331    }
332    pub(super) fn multi_line_joiner(&self) -> &'static str {
333        match self {
334            LogicalOperator::And => "&& ",
335            LogicalOperator::Or => "|| ",
336            LogicalOperator::NullishCoalescing => "?? ",
337        }
338    }
339}
340
341#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
342pub enum BinaryOperator {
343    Equal,
344    NotEqual,
345    StrictEqual,
346    StrictNotEqual,
347}
348
349impl BinaryOperator {
350    pub(super) fn joiner(&self) -> &'static str {
351        match self {
352            BinaryOperator::Equal => " == ",
353            BinaryOperator::NotEqual => " != ",
354            BinaryOperator::StrictEqual => " === ",
355            BinaryOperator::StrictNotEqual => " !== ",
356        }
357    }
358
359    pub(super) fn positive_op(&self) -> (PositiveBinaryOperator, bool) {
360        match self {
361            BinaryOperator::Equal => (PositiveBinaryOperator::Equal, false),
362            BinaryOperator::NotEqual => (PositiveBinaryOperator::Equal, true),
363            BinaryOperator::StrictEqual => (PositiveBinaryOperator::StrictEqual, false),
364            BinaryOperator::StrictNotEqual => (PositiveBinaryOperator::StrictEqual, true),
365        }
366    }
367}
368
369#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
370pub enum PositiveBinaryOperator {
371    Equal,
372    StrictEqual,
373}
374
375#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
376pub enum JsValueUrlKind {
377    Absolute,
378    Relative,
379}
380
381impl Display for JsValueUrlKind {
382    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
383        f.write_str(match self {
384            JsValueUrlKind::Absolute => "absolute",
385            JsValueUrlKind::Relative => "relative",
386        })
387    }
388}
389
390/// The four categories of [JsValue]s.
391pub(super) enum JsValueMetaKind {
392    /// Doesn't contain nested values.
393    Leaf,
394    /// Contains nested values. Nested values represent some structure and can't
395    /// be replaced during linking. They might contain placeholders.
396    Nested,
397    /// Contains nested values. Operations are replaced during linking. They
398    /// might contain placeholders.
399    Operation,
400    /// These values are replaced during linking.
401    Placeholder,
402}
403
404#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
405pub enum LogicalProperty {
406    Truthy,
407    Falsy,
408    Nullish,
409    NonNullish,
410}
411
412impl Display for LogicalProperty {
413    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414        match self {
415            LogicalProperty::Truthy => write!(f, "truthy"),
416            LogicalProperty::Falsy => write!(f, "falsy"),
417            LogicalProperty::Nullish => write!(f, "nullish"),
418            LogicalProperty::NonNullish => write!(f, "non-nullish"),
419        }
420    }
421}
422
423#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
424pub enum ObjectMutability {
425    // Don't reorder these variants, as their order is used in `merge_with`.
426    /// Known properties: frozen (= `<value>`)
427    /// Missing properties: frozen (= Undefined)
428    Frozen,
429    /// Known properties: frozen (= `<value>`)
430    /// Missing properties: mutable (= Unknown)
431    FrozenSubset,
432    /// Known properties: mutable (= `<value> | Unknown`)
433    /// Missing properties: mutable (= Unknown)
434    Mutable,
435}
436
437impl ObjectMutability {
438    pub fn merge_with(&mut self, other: Self) {
439        *self = std::cmp::max(*self, other)
440    }
441
442    pub fn is_mutable(&self) -> bool {
443        matches!(self, ObjectMutability::Mutable)
444    }
445    pub fn is_missing_unknown(&self) -> bool {
446        matches!(self, ObjectMutability::FrozenSubset)
447    }
448}
449
450impl Display for ObjectMutability {
451    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
452        match self {
453            ObjectMutability::Frozen => write!(f, "frozen"),
454            ObjectMutability::FrozenSubset => write!(f, "frozen subset"),
455            ObjectMutability::Mutable => write!(f, ""),
456        }
457    }
458}