turbopack_core/
compile_time_info.rs

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