Skip to main content

turbopack_core/
compile_time_info.rs

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