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;
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}
436
437impl CompileTimeInfo {
438    pub fn builder(environment: ResolvedVc<Environment>) -> CompileTimeInfoBuilder {
439        CompileTimeInfoBuilder {
440            environment,
441            defines: None,
442            free_var_references: None,
443            hot_module_replacement_enabled: false,
444        }
445    }
446}
447
448#[turbo_tasks::value_impl]
449impl CompileTimeInfo {
450    #[turbo_tasks::function]
451    pub async fn new(environment: ResolvedVc<Environment>) -> Result<Vc<Self>> {
452        Ok(CompileTimeInfo {
453            environment,
454            defines: CompileTimeDefines::empty().to_resolved().await?,
455            free_var_references: FreeVarReferences::empty().to_resolved().await?,
456            hot_module_replacement_enabled: false,
457        }
458        .cell())
459    }
460
461    #[turbo_tasks::function]
462    pub fn environment(&self) -> Vc<Environment> {
463        *self.environment
464    }
465}
466
467pub struct CompileTimeInfoBuilder {
468    environment: ResolvedVc<Environment>,
469    defines: Option<ResolvedVc<CompileTimeDefines>>,
470    free_var_references: Option<ResolvedVc<FreeVarReferences>>,
471    hot_module_replacement_enabled: bool,
472}
473
474impl CompileTimeInfoBuilder {
475    pub fn defines(mut self, defines: ResolvedVc<CompileTimeDefines>) -> Self {
476        self.defines = Some(defines);
477        self
478    }
479
480    pub fn free_var_references(
481        mut self,
482        free_var_references: ResolvedVc<FreeVarReferences>,
483    ) -> Self {
484        self.free_var_references = Some(free_var_references);
485        self
486    }
487
488    pub fn hot_module_replacement_enabled(mut self, enabled: bool) -> Self {
489        self.hot_module_replacement_enabled = enabled;
490        self
491    }
492
493    pub async fn build(self) -> Result<CompileTimeInfo> {
494        Ok(CompileTimeInfo {
495            environment: self.environment,
496            defines: match self.defines {
497                Some(defines) => defines,
498                None => CompileTimeDefines::empty().to_resolved().await?,
499            },
500            free_var_references: match self.free_var_references {
501                Some(free_var_references) => free_var_references,
502                None => FreeVarReferences::empty().to_resolved().await?,
503            },
504            hot_module_replacement_enabled: self.hot_module_replacement_enabled,
505        })
506    }
507
508    pub async fn cell(self) -> Result<Vc<CompileTimeInfo>> {
509        Ok(self.build().await?.cell())
510    }
511}
512
513#[cfg(test)]
514mod test {
515    use std::{
516        collections::hash_map::DefaultHasher,
517        hash::{Hash, Hasher},
518    };
519
520    use smallvec::smallvec;
521    use turbo_rcstr::rcstr;
522    use turbo_tasks::FxIndexMap;
523
524    use crate::compile_time_info::{
525        DefinableNameSegment, DefinableNameSegmentRef, DefinableNameSegmentRefs, FreeVarReference,
526        FreeVarReferences,
527    };
528
529    fn hash_value<T: Hash>(value: &T) -> u64 {
530        let mut hasher = DefaultHasher::new();
531        value.hash(&mut hasher);
532        hasher.finish()
533    }
534
535    #[test]
536    fn hash_segment_name_matches() {
537        let segment = DefinableNameSegment::Name(rcstr!("process"));
538        let segment_ref = DefinableNameSegmentRef::Name("process");
539        assert_eq!(
540            hash_value(&segment),
541            hash_value(&segment_ref),
542            "DefinableNameSegment::Name and DefinableNameSegmentRef::Name must have matching Hash"
543        );
544    }
545
546    #[test]
547    fn hash_segment_call_matches() {
548        let segment = DefinableNameSegment::Call(rcstr!("foo"));
549        let segment_ref = DefinableNameSegmentRef::Call("foo");
550        assert_eq!(
551            hash_value(&segment),
552            hash_value(&segment_ref),
553            "DefinableNameSegment::Call and DefinableNameSegmentRef::Call must have matching Hash"
554        );
555    }
556
557    #[test]
558    fn hash_segment_typeof_matches() {
559        let segment = DefinableNameSegment::TypeOf;
560        let segment_ref = DefinableNameSegmentRef::TypeOf;
561        assert_eq!(
562            hash_value(&segment),
563            hash_value(&segment_ref),
564            "DefinableNameSegment::TypeOf and DefinableNameSegmentRef::TypeOf must have matching \
565             Hash"
566        );
567    }
568
569    #[test]
570    fn hash_segments_vec_matches() {
571        let segments: Vec<DefinableNameSegment> = vec![
572            DefinableNameSegment::Name(rcstr!("process")),
573            DefinableNameSegment::Name(rcstr!("env")),
574            DefinableNameSegment::Name(rcstr!("NODE_ENV")),
575        ];
576        let segments_ref = DefinableNameSegmentRefs(smallvec![
577            DefinableNameSegmentRef::Name("process"),
578            DefinableNameSegmentRef::Name("env"),
579            DefinableNameSegmentRef::Name("NODE_ENV"),
580        ]);
581        assert_eq!(
582            hash_value(&segments),
583            hash_value(&segments_ref),
584            "Vec<DefinableNameSegment> and DefinableNameSegmentRefs must have matching Hash"
585        );
586    }
587
588    #[test]
589    fn hash_segments_with_typeof_matches() {
590        let segments: Vec<DefinableNameSegment> = vec![
591            DefinableNameSegment::Name(rcstr!("process")),
592            DefinableNameSegment::TypeOf,
593        ];
594        let segments_ref = DefinableNameSegmentRefs(smallvec![
595            DefinableNameSegmentRef::Name("process"),
596            DefinableNameSegmentRef::TypeOf,
597        ]);
598        assert_eq!(
599            hash_value(&segments),
600            hash_value(&segments_ref),
601            "Vec<DefinableNameSegment> with TypeOf and DefinableNameSegmentRefs must have \
602             matching Hash"
603        );
604    }
605
606    #[test]
607    fn hash_segments_with_call_matches() {
608        let segments: Vec<DefinableNameSegment> = vec![
609            DefinableNameSegment::Name(rcstr!("foo")),
610            DefinableNameSegment::Call(rcstr!("bar")),
611        ];
612        let segments_ref = DefinableNameSegmentRefs(smallvec![
613            DefinableNameSegmentRef::Name("foo"),
614            DefinableNameSegmentRef::Call("bar"),
615        ]);
616        assert_eq!(
617            hash_value(&segments),
618            hash_value(&segments_ref),
619            "Vec<DefinableNameSegment> with Call and DefinableNameSegmentRefs must have matching \
620             Hash"
621        );
622    }
623
624    #[test]
625    fn macro_parser() {
626        assert_eq!(
627            free_var_references!(
628                FOO = "bar",
629                FOO = false,
630                Buffer = FreeVarReference::EcmaScriptModule {
631                    request: rcstr!("node:buffer"),
632                    lookup_path: None,
633                    export: Some(rcstr!("Buffer")),
634                },
635            ),
636            FreeVarReferences(FxIndexMap::from_iter(vec![
637                (
638                    vec![rcstr!("FOO").into()],
639                    FreeVarReference::Value(rcstr!("bar").into())
640                ),
641                (
642                    vec![rcstr!("FOO").into()],
643                    FreeVarReference::Value(false.into())
644                ),
645                (
646                    vec![rcstr!("Buffer").into()],
647                    FreeVarReference::EcmaScriptModule {
648                        request: rcstr!("node:buffer"),
649                        lookup_path: None,
650                        export: Some(rcstr!("Buffer")),
651                    }
652                ),
653            ]))
654        );
655    }
656
657    #[test]
658    fn macro_parser_typeof() {
659        assert_eq!(
660            free_var_references!(
661                typeof x = "a",
662                typeof x.y = "b",
663                typeof x.y.z = "c"
664            ),
665            FreeVarReferences(FxIndexMap::from_iter(vec![
666                (
667                    vec![rcstr!("x").into(), DefinableNameSegment::TypeOf],
668                    FreeVarReference::Value(rcstr!("a").into())
669                ),
670                (
671                    vec![
672                        rcstr!("x").into(),
673                        rcstr!("y").into(),
674                        DefinableNameSegment::TypeOf
675                    ],
676                    FreeVarReference::Value(rcstr!("b").into())
677                ),
678                (
679                    vec![
680                        rcstr!("x").into(),
681                        rcstr!("y").into(),
682                        rcstr!("z").into(),
683                        DefinableNameSegment::TypeOf
684                    ],
685                    FreeVarReference::Value(rcstr!("b").into())
686                ),
687                (
688                    vec![
689                        rcstr!("x").into(),
690                        rcstr!("y").into(),
691                        rcstr!("z").into(),
692                        DefinableNameSegment::TypeOf
693                    ],
694                    FreeVarReference::Value(rcstr!("c").into())
695                )
696            ]))
697        );
698    }
699
700    #[test]
701    fn indexmap_lookup_with_equivalent() {
702        // Test that DefinableNameSegmentRefs can be used to look up Vec<DefinableNameSegment>
703        // in an IndexMap using the Equivalent trait
704        let mut map: FxIndexMap<Vec<DefinableNameSegment>, &str> = FxIndexMap::default();
705        map.insert(
706            vec![
707                DefinableNameSegment::Name(rcstr!("process")),
708                DefinableNameSegment::Name(rcstr!("env")),
709                DefinableNameSegment::Name(rcstr!("NODE_ENV")),
710            ],
711            "production",
712        );
713        map.insert(
714            vec![
715                DefinableNameSegment::Name(rcstr!("process")),
716                DefinableNameSegment::Name(rcstr!("turbopack")),
717            ],
718            "true",
719        );
720
721        // Lookup using DefinableNameSegmentRefs
722        let key = DefinableNameSegmentRefs(smallvec![
723            DefinableNameSegmentRef::Name("process"),
724            DefinableNameSegmentRef::Name("env"),
725            DefinableNameSegmentRef::Name("NODE_ENV"),
726        ]);
727        assert_eq!(
728            map.get(&key),
729            Some(&"production"),
730            "IndexMap lookup with Equivalent trait should work"
731        );
732
733        let key2 = DefinableNameSegmentRefs(smallvec![
734            DefinableNameSegmentRef::Name("process"),
735            DefinableNameSegmentRef::Name("turbopack"),
736        ]);
737        assert_eq!(
738            map.get(&key2),
739            Some(&"true"),
740            "IndexMap lookup with Equivalent trait should work for shorter keys"
741        );
742
743        let key3 = DefinableNameSegmentRefs(smallvec![
744            DefinableNameSegmentRef::Name("process"),
745            DefinableNameSegmentRef::Name("nonexistent"),
746        ]);
747        assert_eq!(
748            map.get(&key3),
749            None,
750            "IndexMap lookup should return None for nonexistent keys"
751        );
752    }
753
754    #[test]
755    fn fxhashset_rcstr_lookup_with_str() {
756        // Test that &str can be used to look up RcStr in a FxHashSet
757        // This is used by FreeVarReferencesMembers::contains_key
758        use rustc_hash::FxHashSet;
759
760        let mut set: FxHashSet<turbo_rcstr::RcStr> = FxHashSet::default();
761        set.insert(rcstr!("process"));
762        set.insert(rcstr!("env"));
763        set.insert(rcstr!("NODE_ENV"));
764
765        // This tests whether &str can look up RcStr in the set
766        // It requires RcStr: Borrow<str> AND hash(&str) == hash(&RcStr)
767        assert!(
768            set.contains("process"),
769            "FxHashSet<RcStr> lookup with &str should work for 'process'"
770        );
771        assert!(
772            set.contains("env"),
773            "FxHashSet<RcStr> lookup with &str should work for 'env'"
774        );
775        assert!(
776            set.contains("NODE_ENV"),
777            "FxHashSet<RcStr> lookup with &str should work for 'NODE_ENV'"
778        );
779        assert!(
780            !set.contains("nonexistent"),
781            "FxHashSet<RcStr> lookup with &str should return false for nonexistent keys"
782        );
783    }
784}