Skip to main content

turbopack_core/
compile_time_info.rs

1use std::{
2    fmt::Display,
3    hash::{Hash, Hasher},
4    ops::Deref,
5};
6
7use anyhow::Result;
8use bincode::{Decode, Encode};
9use indexmap::Equivalent;
10use num_bigint::BigInt;
11use rustc_hash::FxHashSet;
12use smallvec::{SmallVec, smallvec};
13use turbo_rcstr::RcStr;
14use turbo_tasks::{FxIndexMap, NonLocalValue, ResolvedVc, Vc, trace::TraceRawVcs};
15use turbo_tasks_fs::FileSystemPath;
16
17use crate::{environment::Environment, issue::IssueSeverity};
18
19#[macro_export]
20macro_rules! definable_name_map_pattern_internal {
21    ($name:ident) => {
22        [stringify!($name).into()]
23    };
24    ($name:ident typeof) => {
25        [stringify!($name).into(), $crate::compile_time_info::DefinableNameSegment::TypeOf]
26    };
27    // Entry point for non-recursive calls
28    ($name:ident . $($more:ident).+ typeof) => {
29        $crate::definable_name_map_pattern_internal!($($more).+ typeof, [stringify!($name).into()])
30    };
31    ($name:ident . $($more:ident).+) => {
32        $crate::definable_name_map_pattern_internal!($($more).+, [stringify!($name).into()])
33    };
34    // Pop first ident and push to end of array: (id, ..., [...]) => (..., [..., id])
35    ($name:ident, [$($array:expr),+]) => {
36        [$($array),+, stringify!($name).into()]
37    };
38    ($name:ident . $($more:ident).+, [$($array:expr),+]) => {
39        $crate::definable_name_map_pattern_internal!($($more).+, [$($array),+, stringify!($name).into()])
40    };
41    ($name:ident typeof, [$($array:expr),+]) => {
42        [$($array),+, stringify!($name).into(), $crate::compile_time_info::DefinableNameSegment::TypeOf]
43    };
44    ($name:ident . $($more:ident).+ typeof, [$($array:expr),+]) => {
45        $crate::definable_name_map_pattern_internal!($($more).+ typeof, [$($array),+, stringify!($name).into()])
46    };
47}
48
49// TODO stringify split map collect could be optimized with a marco
50#[macro_export]
51macro_rules! definable_name_map_internal {
52    // Allow spreading a map: free_var_references!(..xy.into_iter(), FOO = "bar")
53    ($map:ident, .. $value:expr) => {
54        for (key, value) in $value {
55            $map.insert(
56                key.into(),
57                value.into()
58            );
59        }
60    };
61    ($map:ident, .. $value:expr, $($more:tt)+) => {
62        $crate::definable_name_map_internal!($map, .. $value);
63        $crate::definable_name_map_internal!($map, $($more)+);
64    };
65    // Base case: a single entry
66    ($map:ident, typeof $($name:ident).+ = $value:expr $(,)?) => {
67        $map.insert(
68            $crate::definable_name_map_pattern_internal!($($name).+ typeof).into(),
69            $value.into()
70        );
71    };
72    ($map:ident, $($name:ident).+ = $value:expr $(,)?) => {
73        $map.insert(
74            $crate::definable_name_map_pattern_internal!($($name).+).into(),
75            $value.into()
76        );
77    };
78    // Recursion: split off first entry
79    ($map:ident, typeof $($name:ident).+ = $value:expr, $($more:tt)+) => {
80        $crate::definable_name_map_internal!($map, typeof $($name).+ = $value);
81        $crate::definable_name_map_internal!($map, $($more)+);
82    };
83    ($map:ident, $($name:ident).+ = $value:expr, $($more:tt)+) => {
84        $crate::definable_name_map_internal!($map, $($name).+ = $value);
85        $crate::definable_name_map_internal!($map, $($more)+);
86    };
87
88}
89
90#[macro_export]
91macro_rules! compile_time_defines {
92    ($($more:tt)+) => {
93        {
94            let mut map = $crate::__private::FxIndexMap::default();
95            $crate::definable_name_map_internal!(map, $($more)+);
96            $crate::compile_time_info::CompileTimeDefines(map)
97        }
98    };
99}
100
101#[macro_export]
102macro_rules! free_var_references {
103    ($($more:tt)+) => {
104        {
105            let mut map = $crate::__private::FxIndexMap::default();
106            $crate::definable_name_map_internal!(map, $($more)+);
107            $crate::compile_time_info::FreeVarReferences(map)
108        }
109    };
110}
111
112// TODO: replace with just a `serde_json::Value`
113// https://linear.app/vercel/issue/WEB-1641/compiletimedefinevalue-should-just-use-serde-jsonvalue
114#[derive(Debug, Clone, Hash, TraceRawVcs, NonLocalValue, Encode, Decode, PartialEq, Eq)]
115pub enum CompileTimeDefineValue {
116    Null,
117    Bool(bool),
118    Number(TotalOrderF64),
119    String(RcStr),
120    BigInt(
121        #[bincode(with_serde)]
122        #[turbo_tasks(trace_ignore)]
123        Box<BigInt>,
124    ),
125    Array(Vec<CompileTimeDefineValue>),
126    Object(Vec<(RcStr, CompileTimeDefineValue)>),
127    Undefined,
128    Evaluate(RcStr),
129    Regex(RcStr, RcStr),
130}
131
132/// Wrapper around f64 that implements total Eq and Hash, based on total ordering.
133#[derive(Debug, Copy, Clone, TraceRawVcs, NonLocalValue, Encode, Decode)]
134pub struct TotalOrderF64(f64);
135impl PartialEq for TotalOrderF64 {
136    fn eq(&self, other: &Self) -> bool {
137        self.0.total_cmp(&other.0) == std::cmp::Ordering::Equal
138    }
139}
140impl Eq for TotalOrderF64 {}
141impl Hash for TotalOrderF64 {
142    fn hash<H: Hasher>(&self, state: &mut H) {
143        self.0.to_le_bytes().hash(state);
144    }
145}
146impl From<f64> for TotalOrderF64 {
147    fn from(value: f64) -> Self {
148        Self(value)
149    }
150}
151impl Deref for TotalOrderF64 {
152    type Target = f64;
153    fn deref(&self) -> &Self::Target {
154        &self.0
155    }
156}
157impl Display for TotalOrderF64 {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        self.0.fmt(f)
160    }
161}
162
163impl From<bool> for CompileTimeDefineValue {
164    fn from(value: bool) -> Self {
165        Self::Bool(value)
166    }
167}
168
169impl From<RcStr> for CompileTimeDefineValue {
170    fn from(value: RcStr) -> Self {
171        Self::String(value)
172    }
173}
174
175impl From<String> for CompileTimeDefineValue {
176    fn from(value: String) -> Self {
177        Self::String(value.into())
178    }
179}
180
181impl From<&str> for CompileTimeDefineValue {
182    fn from(value: &str) -> Self {
183        Self::String(value.into())
184    }
185}
186
187impl From<serde_json::Value> for CompileTimeDefineValue {
188    fn from(value: serde_json::Value) -> Self {
189        match value {
190            serde_json::Value::Null => Self::Null,
191            serde_json::Value::Bool(b) => Self::Bool(b),
192            serde_json::Value::Number(n) => Self::Number(
193                n.as_f64()
194                    .expect("unreachable: serde-json has arbitrary_precision disabled")
195                    .into(),
196            ),
197            serde_json::Value::String(s) => Self::String(s.into()),
198            serde_json::Value::Array(a) => Self::Array(a.into_iter().map(|i| i.into()).collect()),
199            serde_json::Value::Object(m) => {
200                Self::Object(m.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
201            }
202        }
203    }
204}
205
206#[turbo_tasks::value]
207#[derive(Debug, Clone, PartialOrd, Ord)]
208pub enum DefinableNameSegment {
209    Name(RcStr),
210    Call(RcStr),
211    TypeOf,
212}
213
214// Hash can't be derived because DefinableNameSegmentRef must have a matching
215// Hash implementation for Equivalent lookups, and derived discriminants are
216// not guaranteed to match between different enum types.
217// Also, we must use s.as_str().hash() instead of s.hash() because RcStr's Hash
218// implementation for prehashed strings is not compatible with str's Hash.
219impl std::hash::Hash for DefinableNameSegment {
220    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
221        match self {
222            Self::Name(s) => {
223                0u8.hash(state);
224                s.as_str().hash(state);
225            }
226            Self::Call(s) => {
227                1u8.hash(state);
228                s.as_str().hash(state);
229            }
230            Self::TypeOf => {
231                2u8.hash(state);
232            }
233        }
234    }
235}
236
237impl From<RcStr> for DefinableNameSegment {
238    fn from(value: RcStr) -> Self {
239        DefinableNameSegment::Name(value)
240    }
241}
242
243impl From<&str> for DefinableNameSegment {
244    fn from(value: &str) -> Self {
245        DefinableNameSegment::Name(value.into())
246    }
247}
248
249impl From<String> for DefinableNameSegment {
250    fn from(value: String) -> Self {
251        DefinableNameSegment::Name(value.into())
252    }
253}
254
255#[derive(PartialEq, Eq)]
256pub enum DefinableNameSegmentRef<'a> {
257    Name(&'a str),
258    Call(&'a str),
259    TypeOf,
260}
261
262// Hash can't be derived because it must match DefinableNameSegment's Hash
263// implementation for Equivalent lookups, and derived discriminants are
264// not guaranteed to match between different enum types.
265impl std::hash::Hash for DefinableNameSegmentRef<'_> {
266    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
267        match self {
268            Self::Name(s) => {
269                0u8.hash(state);
270                s.hash(state);
271            }
272            Self::Call(s) => {
273                1u8.hash(state);
274                s.hash(state);
275            }
276            Self::TypeOf => {
277                2u8.hash(state);
278            }
279        }
280    }
281}
282
283impl Equivalent<DefinableNameSegment> for DefinableNameSegmentRef<'_> {
284    fn equivalent(&self, key: &DefinableNameSegment) -> bool {
285        match (self, key) {
286            (DefinableNameSegmentRef::Name(a), DefinableNameSegment::Name(b)) => **a == *b.as_str(),
287            (DefinableNameSegmentRef::Call(a), DefinableNameSegment::Call(b)) => **a == *b.as_str(),
288            (DefinableNameSegmentRef::TypeOf, DefinableNameSegment::TypeOf) => true,
289            _ => false,
290        }
291    }
292}
293
294#[derive(PartialEq, Eq)]
295pub struct DefinableNameSegmentRefs<'a>(pub SmallVec<[DefinableNameSegmentRef<'a>; 4]>);
296
297// Hash can't be derived because it must match Vec<DefinableNameSegment>'s Hash.
298impl std::hash::Hash for DefinableNameSegmentRefs<'_> {
299    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
300        self.0.len().hash(state);
301        for segment in &self.0 {
302            segment.hash(state);
303        }
304    }
305}
306
307impl Equivalent<Vec<DefinableNameSegment>> for DefinableNameSegmentRefs<'_> {
308    fn equivalent(&self, key: &Vec<DefinableNameSegment>) -> bool {
309        if self.0.len() != key.len() {
310            return false;
311        }
312        for (a, b) in self.0.iter().zip(key.iter()) {
313            if !a.equivalent(b) {
314                return false;
315            }
316        }
317        true
318    }
319}
320
321#[turbo_tasks::value(transparent, cell = "keyed")]
322#[derive(Debug, Clone)]
323pub struct CompileTimeDefines(
324    #[bincode(with = "turbo_bincode::indexmap")]
325    pub  FxIndexMap<Vec<DefinableNameSegment>, CompileTimeDefineValue>,
326);
327
328impl IntoIterator for CompileTimeDefines {
329    type Item = (Vec<DefinableNameSegment>, CompileTimeDefineValue);
330    type IntoIter = indexmap::map::IntoIter<Vec<DefinableNameSegment>, CompileTimeDefineValue>;
331
332    fn into_iter(self) -> Self::IntoIter {
333        self.0.into_iter()
334    }
335}
336
337#[turbo_tasks::value_impl]
338impl CompileTimeDefines {
339    #[turbo_tasks::function]
340    pub fn empty() -> Vc<Self> {
341        Vc::cell(FxIndexMap::default())
342    }
343
344    #[turbo_tasks::function]
345    pub async fn read_process_env(&self, key: RcStr) -> Result<Vc<Option<RcStr>>> {
346        let key = DefinableNameSegmentRefs(smallvec![
347            DefinableNameSegmentRef::Name("process"),
348            DefinableNameSegmentRef::Name("env"),
349            DefinableNameSegmentRef::Name(&key),
350        ]);
351        Ok(Vc::cell(match self.0.get(&key) {
352            Some(CompileTimeDefineValue::String(s)) => Some(s.clone()),
353            _ => None,
354        }))
355    }
356}
357
358#[derive(Debug, Clone, Copy, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
359pub enum InputRelativeConstant {
360    // The project relative directory name of the source file
361    DirName,
362    // The project relative file name of the source file.
363    FileName,
364}
365
366#[derive(Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode, PartialEq, Eq)]
367pub enum FreeVarReference {
368    EcmaScriptModule {
369        request: RcStr,
370        lookup_path: Option<FileSystemPath>,
371        export: Option<RcStr>,
372    },
373    Ident(RcStr),
374    Member(RcStr, RcStr),
375    Value(CompileTimeDefineValue),
376    InputRelative(InputRelativeConstant),
377    // Report the replacement of this free var with the given severity and message, and
378    // potentially replace with the `inner` value.
379    ReportUsage {
380        message: RcStr,
381        severity: IssueSeverity,
382        inner: Option<Box<FreeVarReference>>,
383    },
384}
385
386impl From<bool> for FreeVarReference {
387    fn from(value: bool) -> Self {
388        Self::Value(value.into())
389    }
390}
391
392impl From<String> for FreeVarReference {
393    fn from(value: String) -> Self {
394        Self::Value(value.into())
395    }
396}
397impl From<RcStr> for FreeVarReference {
398    fn from(value: RcStr) -> Self {
399        Self::Value(value.into())
400    }
401}
402
403impl From<&str> for FreeVarReference {
404    fn from(value: &str) -> Self {
405        Self::Value(value.into())
406    }
407}
408
409impl From<CompileTimeDefineValue> for FreeVarReference {
410    fn from(value: CompileTimeDefineValue) -> Self {
411        Self::Value(value)
412    }
413}
414
415#[turbo_tasks::value(transparent, cell = "keyed")]
416#[derive(Debug, Clone)]
417pub struct FreeVarReferences(
418    #[bincode(with = "turbo_bincode::indexmap")]
419    pub  FxIndexMap<Vec<DefinableNameSegment>, FreeVarReference>,
420);
421
422#[turbo_tasks::value(transparent, cell = "keyed")]
423pub struct FreeVarReferencesMembers(FxHashSet<RcStr>);
424
425#[turbo_tasks::value_impl]
426impl FreeVarReferences {
427    #[turbo_tasks::function]
428    pub fn empty() -> Vc<Self> {
429        Vc::cell(FxIndexMap::default())
430    }
431
432    #[turbo_tasks::function]
433    pub fn members(&self) -> Vc<FreeVarReferencesMembers> {
434        let mut members = FxHashSet::default();
435        for (key, _) in self.0.iter() {
436            if let Some(name) = key
437                .iter()
438                .rfind(|segment| {
439                    matches!(
440                        segment,
441                        DefinableNameSegment::Name(_) | DefinableNameSegment::Call(_)
442                    )
443                })
444                .and_then(|segment| match segment {
445                    DefinableNameSegment::Name(n) | DefinableNameSegment::Call(n) => Some(n),
446                    _ => None,
447                })
448            {
449                members.insert(name.clone());
450            }
451        }
452        Vc::cell(members)
453    }
454}
455
456impl IntoIterator for FreeVarReferences {
457    type Item = (Vec<DefinableNameSegment>, FreeVarReference);
458    type IntoIter = indexmap::map::IntoIter<Vec<DefinableNameSegment>, FreeVarReference>;
459
460    fn into_iter(self) -> Self::IntoIter {
461        self.0.into_iter()
462    }
463}
464
465#[turbo_tasks::value(shared)]
466#[derive(Debug, Clone)]
467pub struct CompileTimeInfo {
468    pub environment: ResolvedVc<Environment>,
469    pub defines: ResolvedVc<CompileTimeDefines>,
470    pub free_var_references: ResolvedVc<FreeVarReferences>,
471    pub hot_module_replacement_enabled: bool,
472}
473
474impl CompileTimeInfo {
475    pub fn builder(environment: ResolvedVc<Environment>) -> CompileTimeInfoBuilder {
476        CompileTimeInfoBuilder {
477            environment,
478            defines: None,
479            free_var_references: None,
480            hot_module_replacement_enabled: false,
481        }
482    }
483}
484
485#[turbo_tasks::value_impl]
486impl CompileTimeInfo {
487    #[turbo_tasks::function]
488    pub async fn new(environment: ResolvedVc<Environment>) -> Result<Vc<Self>> {
489        Ok(CompileTimeInfo {
490            environment,
491            defines: CompileTimeDefines::empty().to_resolved().await?,
492            free_var_references: FreeVarReferences::empty().to_resolved().await?,
493            hot_module_replacement_enabled: false,
494        }
495        .cell())
496    }
497
498    #[turbo_tasks::function]
499    pub fn environment(&self) -> Vc<Environment> {
500        *self.environment
501    }
502}
503
504pub struct CompileTimeInfoBuilder {
505    environment: ResolvedVc<Environment>,
506    defines: Option<ResolvedVc<CompileTimeDefines>>,
507    free_var_references: Option<ResolvedVc<FreeVarReferences>>,
508    hot_module_replacement_enabled: bool,
509}
510
511impl CompileTimeInfoBuilder {
512    pub fn defines(mut self, defines: ResolvedVc<CompileTimeDefines>) -> Self {
513        self.defines = Some(defines);
514        self
515    }
516
517    pub fn free_var_references(
518        mut self,
519        free_var_references: ResolvedVc<FreeVarReferences>,
520    ) -> Self {
521        self.free_var_references = Some(free_var_references);
522        self
523    }
524
525    pub fn hot_module_replacement_enabled(mut self, enabled: bool) -> Self {
526        self.hot_module_replacement_enabled = enabled;
527        self
528    }
529
530    pub async fn build(self) -> Result<CompileTimeInfo> {
531        Ok(CompileTimeInfo {
532            environment: self.environment,
533            defines: match self.defines {
534                Some(defines) => defines,
535                None => CompileTimeDefines::empty().to_resolved().await?,
536            },
537            free_var_references: match self.free_var_references {
538                Some(free_var_references) => free_var_references,
539                None => FreeVarReferences::empty().to_resolved().await?,
540            },
541            hot_module_replacement_enabled: self.hot_module_replacement_enabled,
542        })
543    }
544
545    pub async fn cell(self) -> Result<Vc<CompileTimeInfo>> {
546        Ok(self.build().await?.cell())
547    }
548}
549
550#[cfg(test)]
551mod test {
552    use std::{
553        collections::hash_map::DefaultHasher,
554        hash::{Hash, Hasher},
555    };
556
557    use smallvec::smallvec;
558    use turbo_rcstr::rcstr;
559    use turbo_tasks::FxIndexMap;
560
561    use crate::compile_time_info::{
562        DefinableNameSegment, DefinableNameSegmentRef, DefinableNameSegmentRefs, FreeVarReference,
563        FreeVarReferences,
564    };
565
566    fn hash_value<T: Hash>(value: &T) -> u64 {
567        let mut hasher = DefaultHasher::new();
568        value.hash(&mut hasher);
569        hasher.finish()
570    }
571
572    #[test]
573    fn hash_segment_name_matches() {
574        let segment = DefinableNameSegment::Name(rcstr!("process"));
575        let segment_ref = DefinableNameSegmentRef::Name("process");
576        assert_eq!(
577            hash_value(&segment),
578            hash_value(&segment_ref),
579            "DefinableNameSegment::Name and DefinableNameSegmentRef::Name must have matching Hash"
580        );
581    }
582
583    #[test]
584    fn hash_segment_call_matches() {
585        let segment = DefinableNameSegment::Call(rcstr!("foo"));
586        let segment_ref = DefinableNameSegmentRef::Call("foo");
587        assert_eq!(
588            hash_value(&segment),
589            hash_value(&segment_ref),
590            "DefinableNameSegment::Call and DefinableNameSegmentRef::Call must have matching Hash"
591        );
592    }
593
594    #[test]
595    fn hash_segment_typeof_matches() {
596        let segment = DefinableNameSegment::TypeOf;
597        let segment_ref = DefinableNameSegmentRef::TypeOf;
598        assert_eq!(
599            hash_value(&segment),
600            hash_value(&segment_ref),
601            "DefinableNameSegment::TypeOf and DefinableNameSegmentRef::TypeOf must have matching \
602             Hash"
603        );
604    }
605
606    #[test]
607    fn hash_segments_vec_matches() {
608        let segments: Vec<DefinableNameSegment> = vec![
609            DefinableNameSegment::Name(rcstr!("process")),
610            DefinableNameSegment::Name(rcstr!("env")),
611            DefinableNameSegment::Name(rcstr!("NODE_ENV")),
612        ];
613        let segments_ref = DefinableNameSegmentRefs(smallvec![
614            DefinableNameSegmentRef::Name("process"),
615            DefinableNameSegmentRef::Name("env"),
616            DefinableNameSegmentRef::Name("NODE_ENV"),
617        ]);
618        assert_eq!(
619            hash_value(&segments),
620            hash_value(&segments_ref),
621            "Vec<DefinableNameSegment> and DefinableNameSegmentRefs must have matching Hash"
622        );
623    }
624
625    #[test]
626    fn hash_segments_with_typeof_matches() {
627        let segments: Vec<DefinableNameSegment> = vec![
628            DefinableNameSegment::Name(rcstr!("process")),
629            DefinableNameSegment::TypeOf,
630        ];
631        let segments_ref = DefinableNameSegmentRefs(smallvec![
632            DefinableNameSegmentRef::Name("process"),
633            DefinableNameSegmentRef::TypeOf,
634        ]);
635        assert_eq!(
636            hash_value(&segments),
637            hash_value(&segments_ref),
638            "Vec<DefinableNameSegment> with TypeOf and DefinableNameSegmentRefs must have \
639             matching Hash"
640        );
641    }
642
643    #[test]
644    fn hash_segments_with_call_matches() {
645        let segments: Vec<DefinableNameSegment> = vec![
646            DefinableNameSegment::Name(rcstr!("foo")),
647            DefinableNameSegment::Call(rcstr!("bar")),
648        ];
649        let segments_ref = DefinableNameSegmentRefs(smallvec![
650            DefinableNameSegmentRef::Name("foo"),
651            DefinableNameSegmentRef::Call("bar"),
652        ]);
653        assert_eq!(
654            hash_value(&segments),
655            hash_value(&segments_ref),
656            "Vec<DefinableNameSegment> with Call and DefinableNameSegmentRefs must have matching \
657             Hash"
658        );
659    }
660
661    #[test]
662    fn macro_parser() {
663        assert_eq!(
664            free_var_references!(
665                FOO = "bar",
666                FOO = false,
667                Buffer = FreeVarReference::EcmaScriptModule {
668                    request: rcstr!("node:buffer"),
669                    lookup_path: None,
670                    export: Some(rcstr!("Buffer")),
671                },
672            ),
673            FreeVarReferences(FxIndexMap::from_iter(vec![
674                (
675                    vec![rcstr!("FOO").into()],
676                    FreeVarReference::Value(rcstr!("bar").into())
677                ),
678                (
679                    vec![rcstr!("FOO").into()],
680                    FreeVarReference::Value(false.into())
681                ),
682                (
683                    vec![rcstr!("Buffer").into()],
684                    FreeVarReference::EcmaScriptModule {
685                        request: rcstr!("node:buffer"),
686                        lookup_path: None,
687                        export: Some(rcstr!("Buffer")),
688                    }
689                ),
690            ]))
691        );
692    }
693
694    #[test]
695    fn macro_parser_typeof() {
696        assert_eq!(
697            free_var_references!(
698                typeof x = "a",
699                typeof x.y = "b",
700                typeof x.y.z = "c"
701            ),
702            FreeVarReferences(FxIndexMap::from_iter(vec![
703                (
704                    vec![rcstr!("x").into(), DefinableNameSegment::TypeOf],
705                    FreeVarReference::Value(rcstr!("a").into())
706                ),
707                (
708                    vec![
709                        rcstr!("x").into(),
710                        rcstr!("y").into(),
711                        DefinableNameSegment::TypeOf
712                    ],
713                    FreeVarReference::Value(rcstr!("b").into())
714                ),
715                (
716                    vec![
717                        rcstr!("x").into(),
718                        rcstr!("y").into(),
719                        rcstr!("z").into(),
720                        DefinableNameSegment::TypeOf
721                    ],
722                    FreeVarReference::Value(rcstr!("b").into())
723                ),
724                (
725                    vec![
726                        rcstr!("x").into(),
727                        rcstr!("y").into(),
728                        rcstr!("z").into(),
729                        DefinableNameSegment::TypeOf
730                    ],
731                    FreeVarReference::Value(rcstr!("c").into())
732                )
733            ]))
734        );
735    }
736
737    #[test]
738    fn indexmap_lookup_with_equivalent() {
739        // Test that DefinableNameSegmentRefs can be used to look up Vec<DefinableNameSegment>
740        // in an IndexMap using the Equivalent trait
741        let mut map: FxIndexMap<Vec<DefinableNameSegment>, &str> = FxIndexMap::default();
742        map.insert(
743            vec![
744                DefinableNameSegment::Name(rcstr!("process")),
745                DefinableNameSegment::Name(rcstr!("env")),
746                DefinableNameSegment::Name(rcstr!("NODE_ENV")),
747            ],
748            "production",
749        );
750        map.insert(
751            vec![
752                DefinableNameSegment::Name(rcstr!("process")),
753                DefinableNameSegment::Name(rcstr!("turbopack")),
754            ],
755            "true",
756        );
757
758        // Lookup using DefinableNameSegmentRefs
759        let key = DefinableNameSegmentRefs(smallvec![
760            DefinableNameSegmentRef::Name("process"),
761            DefinableNameSegmentRef::Name("env"),
762            DefinableNameSegmentRef::Name("NODE_ENV"),
763        ]);
764        assert_eq!(
765            map.get(&key),
766            Some(&"production"),
767            "IndexMap lookup with Equivalent trait should work"
768        );
769
770        let key2 = DefinableNameSegmentRefs(smallvec![
771            DefinableNameSegmentRef::Name("process"),
772            DefinableNameSegmentRef::Name("turbopack"),
773        ]);
774        assert_eq!(
775            map.get(&key2),
776            Some(&"true"),
777            "IndexMap lookup with Equivalent trait should work for shorter keys"
778        );
779
780        let key3 = DefinableNameSegmentRefs(smallvec![
781            DefinableNameSegmentRef::Name("process"),
782            DefinableNameSegmentRef::Name("nonexistent"),
783        ]);
784        assert_eq!(
785            map.get(&key3),
786            None,
787            "IndexMap lookup should return None for nonexistent keys"
788        );
789    }
790
791    #[test]
792    fn fxhashset_rcstr_lookup_with_str() {
793        // Test that &str can be used to look up RcStr in a FxHashSet
794        // This is used by FreeVarReferencesMembers::contains_key
795        use rustc_hash::FxHashSet;
796
797        let mut set: FxHashSet<turbo_rcstr::RcStr> = FxHashSet::default();
798        set.insert(rcstr!("process"));
799        set.insert(rcstr!("env"));
800        set.insert(rcstr!("NODE_ENV"));
801
802        // This tests whether &str can look up RcStr in the set
803        // It requires RcStr: Borrow<str> AND hash(&str) == hash(&RcStr)
804        assert!(
805            set.contains("process"),
806            "FxHashSet<RcStr> lookup with &str should work for 'process'"
807        );
808        assert!(
809            set.contains("env"),
810            "FxHashSet<RcStr> lookup with &str should work for 'env'"
811        );
812        assert!(
813            set.contains("NODE_ENV"),
814            "FxHashSet<RcStr> lookup with &str should work for 'NODE_ENV'"
815        );
816        assert!(
817            !set.contains("nonexistent"),
818            "FxHashSet<RcStr> lookup with &str should return false for nonexistent keys"
819        );
820    }
821}