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, trace::TraceRawVcs};
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, TraceRawVcs)]
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, TraceRawVcs)]
77pub enum ConstantString {
78    Atom(#[turbo_tasks(trace_ignore)] 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, TraceRawVcs, Encode, Decode)]
170pub enum ConstantValue {
171    #[default]
172    Undefined,
173    Str(ConstantString),
174    Num(ConstantNumber),
175    True,
176    False,
177    Null,
178    BigInt(
179        #[turbo_tasks(trace_ignore)]
180        #[bincode(with_serde)]
181        Box<BigInt>,
182    ),
183    Regex(
184        #[turbo_tasks(trace_ignore)]
185        #[bincode(with_serde)]
186        Box<(Atom, Atom)>,
187    ),
188}
189unsafe impl NonLocalValue for ConstantValue {}
190
191impl ConstantValue {
192    pub fn as_str(&self) -> Option<&str> {
193        match self {
194            Self::Str(s) => Some(s.as_str()),
195            _ => None,
196        }
197    }
198
199    pub fn as_bool(&self) -> Option<bool> {
200        match self {
201            Self::True => Some(true),
202            Self::False => Some(false),
203            _ => None,
204        }
205    }
206
207    pub fn is_truthy(&self) -> bool {
208        match self {
209            Self::Undefined | Self::False | Self::Null => false,
210            Self::True | Self::Regex(..) => true,
211            Self::Str(s) => !s.is_empty(),
212            Self::Num(ConstantNumber(n)) => *n != 0.0,
213            Self::BigInt(n) => !n.is_zero(),
214        }
215    }
216
217    pub fn is_nullish(&self) -> bool {
218        match self {
219            Self::Undefined | Self::Null => true,
220            Self::Str(..)
221            | Self::Num(..)
222            | Self::True
223            | Self::False
224            | Self::BigInt(..)
225            | Self::Regex(..) => false,
226        }
227    }
228
229    pub fn is_empty_string(&self) -> bool {
230        match self {
231            Self::Str(s) => s.is_empty(),
232            _ => false,
233        }
234    }
235
236    pub fn is_value_type(&self) -> bool {
237        !matches!(self, Self::Regex(..))
238    }
239}
240
241impl From<bool> for ConstantValue {
242    fn from(v: bool) -> Self {
243        match v {
244            true => ConstantValue::True,
245            false => ConstantValue::False,
246        }
247    }
248}
249
250impl From<&'_ str> for ConstantValue {
251    fn from(v: &str) -> Self {
252        ConstantValue::Str(ConstantString::Atom(v.into()))
253    }
254}
255
256impl From<Lit> for ConstantValue {
257    fn from(v: Lit) -> Self {
258        match v {
259            Lit::Str(v) => {
260                ConstantValue::Str(ConstantString::Atom(v.value.to_atom_lossy().into_owned()))
261            }
262            Lit::Bool(v) => {
263                if v.value {
264                    ConstantValue::True
265                } else {
266                    ConstantValue::False
267                }
268            }
269            Lit::Null(_) => ConstantValue::Null,
270            Lit::Num(v) => ConstantValue::Num(ConstantNumber(v.value)),
271            Lit::BigInt(v) => ConstantValue::BigInt(v.value),
272            Lit::Regex(v) => ConstantValue::Regex(Box::new((v.exp, v.flags))),
273            Lit::JSXText(v) => {
274                // TODO
275                ConstantValue::Str(ConstantString::Atom(v.value.to_atom_lossy().into_owned()))
276            }
277        }
278    }
279}
280
281impl Display for ConstantValue {
282    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283        match self {
284            ConstantValue::Undefined => write!(f, "undefined"),
285            ConstantValue::Str(str) => write!(f, "{}", StringifyJs(str.as_str())),
286            ConstantValue::True => write!(f, "true"),
287            ConstantValue::False => write!(f, "false"),
288            ConstantValue::Null => write!(f, "null"),
289            ConstantValue::Num(ConstantNumber(n)) => write!(f, "{n}"),
290            ConstantValue::BigInt(n) => write!(f, "{n}"),
291            ConstantValue::Regex(regex) => write!(f, "/{}/{}", regex.0, regex.1),
292        }
293    }
294}
295
296#[derive(Debug, Clone, Hash, PartialEq, Eq)]
297pub struct ModuleValue {
298    pub module: Wtf8Atom,
299    pub annotations: Option<Arc<ImportAnnotations>>,
300    /// Whether to analyze this module for constants
301    // TODO this is a hack: ideally we'd have truly "bidirectional linking" instead of the current
302    // `early_visitor` plus `visitor` setup. Then this could just be implemented with a rewrite
303    // rule for `Member(ModuleValue, prop) if prop.as_str().is_upper_case() => { ... }`
304    pub analyze_for_constants: bool,
305    // This is an Option<NonZeroU32> to JsValue to only be 32 bytes in size.
306    pub reference: Option<ModuleReferenceIndex>,
307}
308
309#[derive(Copy, Debug, Clone, Hash, PartialEq, Eq)]
310pub struct ModuleReferenceIndex(NonZeroU32);
311
312impl From<u32> for ModuleReferenceIndex {
313    fn from(value: u32) -> Self {
314        // The only way this can overflow is if value == u32::MAX, which is unlikely since it would
315        // require us to have that many module references.
316        ModuleReferenceIndex(NonZeroU32::new(value + 1).unwrap())
317    }
318}
319impl ModuleReferenceIndex {
320    pub fn get(&self) -> usize {
321        (self.0.get() - 1) as usize
322    }
323}
324
325#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
326pub enum LogicalOperator {
327    And,
328    Or,
329    NullishCoalescing,
330}
331
332impl LogicalOperator {
333    pub(super) fn joiner(&self) -> &'static str {
334        match self {
335            LogicalOperator::And => " && ",
336            LogicalOperator::Or => " || ",
337            LogicalOperator::NullishCoalescing => " ?? ",
338        }
339    }
340    pub(super) fn multi_line_joiner(&self) -> &'static str {
341        match self {
342            LogicalOperator::And => "&& ",
343            LogicalOperator::Or => "|| ",
344            LogicalOperator::NullishCoalescing => "?? ",
345        }
346    }
347}
348
349#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
350pub enum BinaryOperator {
351    Equal,
352    NotEqual,
353    StrictEqual,
354    StrictNotEqual,
355}
356
357impl BinaryOperator {
358    pub(super) fn joiner(&self) -> &'static str {
359        match self {
360            BinaryOperator::Equal => " == ",
361            BinaryOperator::NotEqual => " != ",
362            BinaryOperator::StrictEqual => " === ",
363            BinaryOperator::StrictNotEqual => " !== ",
364        }
365    }
366
367    pub(super) fn positive_op(&self) -> (PositiveBinaryOperator, bool) {
368        match self {
369            BinaryOperator::Equal => (PositiveBinaryOperator::Equal, false),
370            BinaryOperator::NotEqual => (PositiveBinaryOperator::Equal, true),
371            BinaryOperator::StrictEqual => (PositiveBinaryOperator::StrictEqual, false),
372            BinaryOperator::StrictNotEqual => (PositiveBinaryOperator::StrictEqual, true),
373        }
374    }
375}
376
377#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
378pub enum PositiveBinaryOperator {
379    Equal,
380    StrictEqual,
381}
382
383#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
384pub enum JsValueUrlKind {
385    Absolute,
386    Relative,
387}
388
389impl Display for JsValueUrlKind {
390    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
391        f.write_str(match self {
392            JsValueUrlKind::Absolute => "absolute",
393            JsValueUrlKind::Relative => "relative",
394        })
395    }
396}
397
398/// The four categories of [JsValue]s.
399pub(super) enum JsValueMetaKind {
400    /// Doesn't contain nested values.
401    Leaf,
402    /// Contains nested values. Nested values represent some structure and can't
403    /// be replaced during linking. They might contain placeholders.
404    Nested,
405    /// Contains nested values. Operations are replaced during linking. They
406    /// might contain placeholders.
407    Operation,
408    /// These values are replaced during linking.
409    Placeholder,
410}
411
412#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
413pub enum LogicalProperty {
414    Truthy,
415    Falsy,
416    Nullish,
417    NonNullish,
418}
419
420impl Display for LogicalProperty {
421    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422        match self {
423            LogicalProperty::Truthy => write!(f, "truthy"),
424            LogicalProperty::Falsy => write!(f, "falsy"),
425            LogicalProperty::Nullish => write!(f, "nullish"),
426            LogicalProperty::NonNullish => write!(f, "non-nullish"),
427        }
428    }
429}
430
431#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
432pub enum ObjectMutability {
433    // Don't reorder these variants, as their order is used in `merge_with`.
434    /// Known properties: frozen (= <value>)
435    /// Missing properties: frozen (= Undefined)
436    Frozen,
437    /// Known properties: frozen (= <value>)
438    /// Missing properties: mutable (= Unknown)
439    FrozenSubset,
440    /// Known properties: mutable (= <value> | Unknown)
441    /// Missing properties: mutable (= Unknown)
442    Mutable,
443}
444
445impl ObjectMutability {
446    pub fn merge_with(&mut self, other: Self) {
447        *self = std::cmp::max(*self, other)
448    }
449
450    pub fn is_mutable(&self) -> bool {
451        matches!(self, ObjectMutability::Mutable)
452    }
453    pub fn is_missing_unknown(&self) -> bool {
454        matches!(self, ObjectMutability::FrozenSubset)
455    }
456}
457
458impl Display for ObjectMutability {
459    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460        match self {
461            ObjectMutability::Frozen => write!(f, "frozen"),
462            ObjectMutability::FrozenSubset => write!(f, "frozen subset"),
463            ObjectMutability::Mutable => write!(f, ""),
464        }
465    }
466}