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;
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
345#[derive(Debug, Clone, Copy, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
346pub enum InputRelativeConstant {
347    // The project relative directory name of the source file
348    DirName,
349    // The project relative file name of the source file.
350    FileName,
351}
352
353#[derive(Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode, PartialEq, Eq)]
354pub enum FreeVarReference {
355    EcmaScriptModule {
356        request: RcStr,
357        lookup_path: Option<FileSystemPath>,
358        export: Option<RcStr>,
359    },
360    Ident(RcStr),
361    Member(RcStr, RcStr),
362    Value(CompileTimeDefineValue),
363    InputRelative(InputRelativeConstant),
364    // Report the replacement of this free var with the given severity and message, and
365    // potentially replace with the `inner` value.
366    ReportUsage {
367        message: RcStr,
368        severity: IssueSeverity,
369        inner: Option<Box<FreeVarReference>>,
370    },
371}
372
373impl From<bool> for FreeVarReference {
374    fn from(value: bool) -> Self {
375        Self::Value(value.into())
376    }
377}
378
379impl From<String> for FreeVarReference {
380    fn from(value: String) -> Self {
381        Self::Value(value.into())
382    }
383}
384impl From<RcStr> for FreeVarReference {
385    fn from(value: RcStr) -> Self {
386        Self::Value(value.into())
387    }
388}
389
390impl From<&str> for FreeVarReference {
391    fn from(value: &str) -> Self {
392        Self::Value(value.into())
393    }
394}
395
396impl From<CompileTimeDefineValue> for FreeVarReference {
397    fn from(value: CompileTimeDefineValue) -> Self {
398        Self::Value(value)
399    }
400}
401
402#[turbo_tasks::value(transparent, cell = "keyed")]
403#[derive(Debug, Clone)]
404pub struct FreeVarReferences(
405    #[bincode(with = "turbo_bincode::indexmap")]
406    pub  FxIndexMap<Vec<DefinableNameSegment>, FreeVarReference>,
407);
408
409#[turbo_tasks::value(transparent, cell = "keyed")]
410pub struct FreeVarReferencesMembers(FxHashSet<RcStr>);
411
412#[turbo_tasks::value_impl]
413impl FreeVarReferences {
414    #[turbo_tasks::function]
415    pub fn empty() -> Vc<Self> {
416        Vc::cell(FxIndexMap::default())
417    }
418
419    #[turbo_tasks::function]
420    pub fn members(&self) -> Vc<FreeVarReferencesMembers> {
421        let mut members = FxHashSet::default();
422        for (key, _) in self.0.iter() {
423            if let Some(name) = key
424                .iter()
425                .rfind(|segment| {
426                    matches!(
427                        segment,
428                        DefinableNameSegment::Name(_) | DefinableNameSegment::Call(_)
429                    )
430                })
431                .and_then(|segment| match segment {
432                    DefinableNameSegment::Name(n) | DefinableNameSegment::Call(n) => Some(n),
433                    _ => None,
434                })
435            {
436                members.insert(name.clone());
437            }
438        }
439        Vc::cell(members)
440    }
441}
442
443impl IntoIterator for FreeVarReferences {
444    type Item = (Vec<DefinableNameSegment>, FreeVarReference);
445    type IntoIter = indexmap::map::IntoIter<Vec<DefinableNameSegment>, FreeVarReference>;
446
447    fn into_iter(self) -> Self::IntoIter {
448        self.0.into_iter()
449    }
450}
451
452#[turbo_tasks::value(shared)]
453#[derive(Debug, Clone)]
454pub struct CompileTimeInfo {
455    pub environment: ResolvedVc<Environment>,
456    pub defines: ResolvedVc<CompileTimeDefines>,
457    pub free_var_references: ResolvedVc<FreeVarReferences>,
458    pub hot_module_replacement_enabled: bool,
459}
460
461impl CompileTimeInfo {
462    pub fn builder(environment: ResolvedVc<Environment>) -> CompileTimeInfoBuilder {
463        CompileTimeInfoBuilder {
464            environment,
465            defines: None,
466            free_var_references: None,
467            hot_module_replacement_enabled: false,
468        }
469    }
470}
471
472#[turbo_tasks::value_impl]
473impl CompileTimeInfo {
474    #[turbo_tasks::function]
475    pub async fn new(environment: ResolvedVc<Environment>) -> Result<Vc<Self>> {
476        Ok(CompileTimeInfo {
477            environment,
478            defines: CompileTimeDefines::empty().to_resolved().await?,
479            free_var_references: FreeVarReferences::empty().to_resolved().await?,
480            hot_module_replacement_enabled: false,
481        }
482        .cell())
483    }
484
485    #[turbo_tasks::function]
486    pub fn environment(&self) -> Vc<Environment> {
487        *self.environment
488    }
489}
490
491pub struct CompileTimeInfoBuilder {
492    environment: ResolvedVc<Environment>,
493    defines: Option<ResolvedVc<CompileTimeDefines>>,
494    free_var_references: Option<ResolvedVc<FreeVarReferences>>,
495    hot_module_replacement_enabled: bool,
496}
497
498impl CompileTimeInfoBuilder {
499    pub fn defines(mut self, defines: ResolvedVc<CompileTimeDefines>) -> Self {
500        self.defines = Some(defines);
501        self
502    }
503
504    pub fn free_var_references(
505        mut self,
506        free_var_references: ResolvedVc<FreeVarReferences>,
507    ) -> Self {
508        self.free_var_references = Some(free_var_references);
509        self
510    }
511
512    pub fn hot_module_replacement_enabled(mut self, enabled: bool) -> Self {
513        self.hot_module_replacement_enabled = enabled;
514        self
515    }
516
517    pub async fn build(self) -> Result<CompileTimeInfo> {
518        Ok(CompileTimeInfo {
519            environment: self.environment,
520            defines: match self.defines {
521                Some(defines) => defines,
522                None => CompileTimeDefines::empty().to_resolved().await?,
523            },
524            free_var_references: match self.free_var_references {
525                Some(free_var_references) => free_var_references,
526                None => FreeVarReferences::empty().to_resolved().await?,
527            },
528            hot_module_replacement_enabled: self.hot_module_replacement_enabled,
529        })
530    }
531
532    pub async fn cell(self) -> Result<Vc<CompileTimeInfo>> {
533        Ok(self.build().await?.cell())
534    }
535}
536
537#[cfg(test)]
538mod test {
539    use std::{
540        collections::hash_map::DefaultHasher,
541        hash::{Hash, Hasher},
542    };
543
544    use smallvec::smallvec;
545    use turbo_rcstr::rcstr;
546    use turbo_tasks::FxIndexMap;
547
548    use crate::compile_time_info::{
549        DefinableNameSegment, DefinableNameSegmentRef, DefinableNameSegmentRefs, FreeVarReference,
550        FreeVarReferences,
551    };
552
553    fn hash_value<T: Hash>(value: &T) -> u64 {
554        let mut hasher = DefaultHasher::new();
555        value.hash(&mut hasher);
556        hasher.finish()
557    }
558
559    #[test]
560    fn hash_segment_name_matches() {
561        let segment = DefinableNameSegment::Name(rcstr!("process"));
562        let segment_ref = DefinableNameSegmentRef::Name("process");
563        assert_eq!(
564            hash_value(&segment),
565            hash_value(&segment_ref),
566            "DefinableNameSegment::Name and DefinableNameSegmentRef::Name must have matching Hash"
567        );
568    }
569
570    #[test]
571    fn hash_segment_call_matches() {
572        let segment = DefinableNameSegment::Call(rcstr!("foo"));
573        let segment_ref = DefinableNameSegmentRef::Call("foo");
574        assert_eq!(
575            hash_value(&segment),
576            hash_value(&segment_ref),
577            "DefinableNameSegment::Call and DefinableNameSegmentRef::Call must have matching Hash"
578        );
579    }
580
581    #[test]
582    fn hash_segment_typeof_matches() {
583        let segment = DefinableNameSegment::TypeOf;
584        let segment_ref = DefinableNameSegmentRef::TypeOf;
585        assert_eq!(
586            hash_value(&segment),
587            hash_value(&segment_ref),
588            "DefinableNameSegment::TypeOf and DefinableNameSegmentRef::TypeOf must have matching \
589             Hash"
590        );
591    }
592
593    #[test]
594    fn hash_segments_vec_matches() {
595        let segments: Vec<DefinableNameSegment> = vec![
596            DefinableNameSegment::Name(rcstr!("process")),
597            DefinableNameSegment::Name(rcstr!("env")),
598            DefinableNameSegment::Name(rcstr!("NODE_ENV")),
599        ];
600        let segments_ref = DefinableNameSegmentRefs(smallvec![
601            DefinableNameSegmentRef::Name("process"),
602            DefinableNameSegmentRef::Name("env"),
603            DefinableNameSegmentRef::Name("NODE_ENV"),
604        ]);
605        assert_eq!(
606            hash_value(&segments),
607            hash_value(&segments_ref),
608            "Vec<DefinableNameSegment> and DefinableNameSegmentRefs must have matching Hash"
609        );
610    }
611
612    #[test]
613    fn hash_segments_with_typeof_matches() {
614        let segments: Vec<DefinableNameSegment> = vec![
615            DefinableNameSegment::Name(rcstr!("process")),
616            DefinableNameSegment::TypeOf,
617        ];
618        let segments_ref = DefinableNameSegmentRefs(smallvec![
619            DefinableNameSegmentRef::Name("process"),
620            DefinableNameSegmentRef::TypeOf,
621        ]);
622        assert_eq!(
623            hash_value(&segments),
624            hash_value(&segments_ref),
625            "Vec<DefinableNameSegment> with TypeOf and DefinableNameSegmentRefs must have \
626             matching Hash"
627        );
628    }
629
630    #[test]
631    fn hash_segments_with_call_matches() {
632        let segments: Vec<DefinableNameSegment> = vec![
633            DefinableNameSegment::Name(rcstr!("foo")),
634            DefinableNameSegment::Call(rcstr!("bar")),
635        ];
636        let segments_ref = DefinableNameSegmentRefs(smallvec![
637            DefinableNameSegmentRef::Name("foo"),
638            DefinableNameSegmentRef::Call("bar"),
639        ]);
640        assert_eq!(
641            hash_value(&segments),
642            hash_value(&segments_ref),
643            "Vec<DefinableNameSegment> with Call and DefinableNameSegmentRefs must have matching \
644             Hash"
645        );
646    }
647
648    #[test]
649    fn macro_parser() {
650        assert_eq!(
651            free_var_references!(
652                FOO = "bar",
653                FOO = false,
654                Buffer = FreeVarReference::EcmaScriptModule {
655                    request: rcstr!("node:buffer"),
656                    lookup_path: None,
657                    export: Some(rcstr!("Buffer")),
658                },
659            ),
660            FreeVarReferences(FxIndexMap::from_iter(vec![
661                (
662                    vec![rcstr!("FOO").into()],
663                    FreeVarReference::Value(rcstr!("bar").into())
664                ),
665                (
666                    vec![rcstr!("FOO").into()],
667                    FreeVarReference::Value(false.into())
668                ),
669                (
670                    vec![rcstr!("Buffer").into()],
671                    FreeVarReference::EcmaScriptModule {
672                        request: rcstr!("node:buffer"),
673                        lookup_path: None,
674                        export: Some(rcstr!("Buffer")),
675                    }
676                ),
677            ]))
678        );
679    }
680
681    #[test]
682    fn macro_parser_typeof() {
683        assert_eq!(
684            free_var_references!(
685                typeof x = "a",
686                typeof x.y = "b",
687                typeof x.y.z = "c"
688            ),
689            FreeVarReferences(FxIndexMap::from_iter(vec![
690                (
691                    vec![rcstr!("x").into(), DefinableNameSegment::TypeOf],
692                    FreeVarReference::Value(rcstr!("a").into())
693                ),
694                (
695                    vec![
696                        rcstr!("x").into(),
697                        rcstr!("y").into(),
698                        DefinableNameSegment::TypeOf
699                    ],
700                    FreeVarReference::Value(rcstr!("b").into())
701                ),
702                (
703                    vec![
704                        rcstr!("x").into(),
705                        rcstr!("y").into(),
706                        rcstr!("z").into(),
707                        DefinableNameSegment::TypeOf
708                    ],
709                    FreeVarReference::Value(rcstr!("b").into())
710                ),
711                (
712                    vec![
713                        rcstr!("x").into(),
714                        rcstr!("y").into(),
715                        rcstr!("z").into(),
716                        DefinableNameSegment::TypeOf
717                    ],
718                    FreeVarReference::Value(rcstr!("c").into())
719                )
720            ]))
721        );
722    }
723
724    #[test]
725    fn indexmap_lookup_with_equivalent() {
726        // Test that DefinableNameSegmentRefs can be used to look up Vec<DefinableNameSegment>
727        // in an IndexMap using the Equivalent trait
728        let mut map: FxIndexMap<Vec<DefinableNameSegment>, &str> = FxIndexMap::default();
729        map.insert(
730            vec![
731                DefinableNameSegment::Name(rcstr!("process")),
732                DefinableNameSegment::Name(rcstr!("env")),
733                DefinableNameSegment::Name(rcstr!("NODE_ENV")),
734            ],
735            "production",
736        );
737        map.insert(
738            vec![
739                DefinableNameSegment::Name(rcstr!("process")),
740                DefinableNameSegment::Name(rcstr!("turbopack")),
741            ],
742            "true",
743        );
744
745        // Lookup using DefinableNameSegmentRefs
746        let key = DefinableNameSegmentRefs(smallvec![
747            DefinableNameSegmentRef::Name("process"),
748            DefinableNameSegmentRef::Name("env"),
749            DefinableNameSegmentRef::Name("NODE_ENV"),
750        ]);
751        assert_eq!(
752            map.get(&key),
753            Some(&"production"),
754            "IndexMap lookup with Equivalent trait should work"
755        );
756
757        let key2 = DefinableNameSegmentRefs(smallvec![
758            DefinableNameSegmentRef::Name("process"),
759            DefinableNameSegmentRef::Name("turbopack"),
760        ]);
761        assert_eq!(
762            map.get(&key2),
763            Some(&"true"),
764            "IndexMap lookup with Equivalent trait should work for shorter keys"
765        );
766
767        let key3 = DefinableNameSegmentRefs(smallvec![
768            DefinableNameSegmentRef::Name("process"),
769            DefinableNameSegmentRef::Name("nonexistent"),
770        ]);
771        assert_eq!(
772            map.get(&key3),
773            None,
774            "IndexMap lookup should return None for nonexistent keys"
775        );
776    }
777
778    #[test]
779    fn fxhashset_rcstr_lookup_with_str() {
780        // Test that &str can be used to look up RcStr in a FxHashSet
781        // This is used by FreeVarReferencesMembers::contains_key
782        use rustc_hash::FxHashSet;
783
784        let mut set: FxHashSet<turbo_rcstr::RcStr> = FxHashSet::default();
785        set.insert(rcstr!("process"));
786        set.insert(rcstr!("env"));
787        set.insert(rcstr!("NODE_ENV"));
788
789        // This tests whether &str can look up RcStr in the set
790        // It requires RcStr: Borrow<str> AND hash(&str) == hash(&RcStr)
791        assert!(
792            set.contains("process"),
793            "FxHashSet<RcStr> lookup with &str should work for 'process'"
794        );
795        assert!(
796            set.contains("env"),
797            "FxHashSet<RcStr> lookup with &str should work for 'env'"
798        );
799        assert!(
800            set.contains("NODE_ENV"),
801            "FxHashSet<RcStr> lookup with &str should work for 'NODE_ENV'"
802        );
803        assert!(
804            !set.contains("nonexistent"),
805            "FxHashSet<RcStr> lookup with &str should return false for nonexistent keys"
806        );
807    }
808}