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 ($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 ($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#[macro_export]
44macro_rules! definable_name_map_internal {
45 ($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 ($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 ($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#[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
166impl 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
214impl 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
249impl 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 DirName,
301 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 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 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 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 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 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}