1use std::collections::HashMap;
26
27use serde::{Deserialize, Serialize};
28
29use super::super::node::id::NodeId;
30use super::dispatch_tables::DispatchTables;
31use super::framework_routes::{FrameworkRouteMetadata, FrameworkRoutesMap};
32
33#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
41pub struct MacroNodeMetadata {
42 pub macro_generated: Option<bool>,
44
45 pub macro_source: Option<String>,
47
48 pub cfg_condition: Option<String>,
74
75 pub cfg_active: Option<bool>,
77
78 pub proc_macro_kind: Option<ProcMacroFunctionKind>,
80
81 pub expansion_cached: Option<bool>,
83
84 pub unresolved_attributes: Vec<String>,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
92pub enum ProcMacroFunctionKind {
93 Derive,
95 Attribute,
97 FunctionLike,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103pub struct ClasspathNodeMetadata {
104 pub coordinates: Option<String>,
106 pub jar_path: String,
108 pub fqn: String,
110 pub is_direct_dependency: bool,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
120pub enum TypedMetadata {
121 Macro(MacroNodeMetadata),
123 Classpath(ClasspathNodeMetadata),
125}
126
127#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
134#[repr(transparent)]
135pub struct NodeFlags(u8);
136
137impl NodeFlags {
138 pub const SYNTHETIC: NodeFlags = NodeFlags(1 << 0);
148
149 pub const ADDRESS_TAKEN: NodeFlags = NodeFlags(1 << 1);
154
155 pub const CALLSITE_PROMISCUOUS: NodeFlags = NodeFlags(1 << 2);
160
161 pub const EMPTY: NodeFlags = NodeFlags(0);
163
164 #[must_use]
166 pub const fn contains(self, other: NodeFlags) -> bool {
167 (self.0 & other.0) == other.0
168 }
169
170 pub fn insert(&mut self, other: NodeFlags) {
172 self.0 |= other.0;
173 }
174
175 pub fn remove(&mut self, other: NodeFlags) {
177 self.0 &= !other.0;
178 }
179
180 #[must_use]
182 pub const fn is_empty(self) -> bool {
183 self.0 == 0
184 }
185
186 #[must_use]
188 pub const fn bits(self) -> u8 {
189 self.0
190 }
191
192 #[must_use]
194 pub const fn from_bits(bits: u8) -> NodeFlags {
195 NodeFlags(bits)
196 }
197}
198
199impl std::ops::BitOr for NodeFlags {
200 type Output = NodeFlags;
201 fn bitor(self, rhs: NodeFlags) -> NodeFlags {
202 NodeFlags(self.0 | rhs.0)
203 }
204}
205
206impl std::ops::BitOrAssign for NodeFlags {
207 fn bitor_assign(&mut self, rhs: NodeFlags) {
208 self.0 |= rhs.0;
209 }
210}
211
212#[derive(Debug, Clone, Default, PartialEq, Eq)]
220pub struct StoredEntry {
221 pub typed: Option<TypedMetadata>,
224 pub flags: NodeFlags,
226}
227
228impl StoredEntry {
229 #[must_use]
231 pub fn with_typed(typed: TypedMetadata) -> StoredEntry {
232 StoredEntry {
233 typed: Some(typed),
234 flags: NodeFlags::EMPTY,
235 }
236 }
237
238 #[must_use]
240 pub fn with_flags(flags: NodeFlags) -> StoredEntry {
241 StoredEntry { typed: None, flags }
242 }
243
244 #[must_use]
246 pub fn is_vacant(&self) -> bool {
247 self.typed.is_none() && self.flags.is_empty()
248 }
249}
250
251#[derive(Debug, Clone, Default)]
280pub struct NodeMetadataStore {
281 entries: HashMap<(u32, u64), StoredEntry>,
283 framework_routes: FrameworkRoutesMap,
286 dispatch_tables: DispatchTables,
289}
290
291const TYPED_KIND_NONE: u8 = 0;
293const TYPED_KIND_MACRO: u8 = 1;
294const TYPED_KIND_CLASSPATH: u8 = 2;
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
301struct NodeMetadataEntryV11 {
302 index: u32,
303 generation: u64,
304 kind: u8,
306 macro_data: Option<MacroNodeMetadata>,
308 classpath_data: Option<ClasspathNodeMetadata>,
310 flags: u8,
312}
313
314impl Serialize for NodeMetadataStore {
324 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
325 let entries: Vec<NodeMetadataEntryV11> = self
326 .entries
327 .iter()
328 .map(|(&(index, generation), stored)| {
329 let (kind, macro_data, classpath_data) = match &stored.typed {
330 None => (TYPED_KIND_NONE, None, None),
331 Some(TypedMetadata::Macro(m)) => (TYPED_KIND_MACRO, Some(m.clone()), None),
332 Some(TypedMetadata::Classpath(c)) => {
333 (TYPED_KIND_CLASSPATH, None, Some(c.clone()))
334 }
335 };
336 NodeMetadataEntryV11 {
337 index,
338 generation,
339 kind,
340 macro_data,
341 classpath_data,
342 flags: stored.flags.bits(),
343 }
344 })
345 .collect();
346 entries.serialize(serializer)
347 }
348}
349
350impl<'de> Deserialize<'de> for NodeMetadataStore {
351 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
352 let entries: Vec<NodeMetadataEntryV11> = Vec::deserialize(deserializer)?;
353 let mut map = HashMap::with_capacity(entries.len());
354 for e in entries {
355 let typed = match e.kind {
356 TYPED_KIND_NONE => None,
357 TYPED_KIND_MACRO => Some(TypedMetadata::Macro(e.macro_data.unwrap_or_default())),
358 TYPED_KIND_CLASSPATH => {
359 let data = e.classpath_data.ok_or_else(|| {
360 serde::de::Error::custom(
361 "missing classpath_data for Classpath typed metadata entry",
362 )
363 })?;
364 Some(TypedMetadata::Classpath(data))
365 }
366 other => {
367 return Err(serde::de::Error::custom(format!(
368 "unknown typed-metadata kind discriminant {other}"
369 )));
370 }
371 };
372 let stored = StoredEntry {
373 typed,
374 flags: NodeFlags::from_bits(e.flags),
375 };
376 map.insert((e.index, e.generation), stored);
377 }
378 Ok(Self {
386 entries: map,
387 framework_routes: FrameworkRoutesMap::default(),
388 dispatch_tables: DispatchTables::default(),
389 })
390 }
391}
392
393impl NodeMetadataStore {
394 #[must_use]
396 pub fn new() -> Self {
397 Self::default()
398 }
399
400 #[must_use]
409 pub fn get_typed(&self, node_id: NodeId) -> Option<&TypedMetadata> {
410 self.entries
411 .get(&(node_id.index(), node_id.generation()))?
412 .typed
413 .as_ref()
414 }
415
416 pub fn get_typed_mut(&mut self, node_id: NodeId) -> Option<&mut TypedMetadata> {
418 self.entries
419 .get_mut(&(node_id.index(), node_id.generation()))?
420 .typed
421 .as_mut()
422 }
423
424 #[must_use]
429 pub fn get_macro(&self, node_id: NodeId) -> Option<&MacroNodeMetadata> {
430 match self.get_typed(node_id)? {
431 TypedMetadata::Macro(m) => Some(m),
432 TypedMetadata::Classpath(_) => None,
433 }
434 }
435
436 pub fn get_macro_mut(&mut self, node_id: NodeId) -> Option<&mut MacroNodeMetadata> {
438 match self.get_typed_mut(node_id)? {
439 TypedMetadata::Macro(m) => Some(m),
440 TypedMetadata::Classpath(_) => None,
441 }
442 }
443
444 #[must_use]
453 pub fn get_flags(&self, node_id: NodeId) -> NodeFlags {
454 self.entries
455 .get(&(node_id.index(), node_id.generation()))
456 .map(|e| e.flags)
457 .unwrap_or(NodeFlags::EMPTY)
458 }
459
460 #[must_use]
462 pub fn is_synthetic(&self, node_id: NodeId) -> bool {
463 self.get_flags(node_id).contains(NodeFlags::SYNTHETIC)
464 }
465
466 #[must_use]
468 pub fn is_address_taken(&self, node_id: NodeId) -> bool {
469 self.get_flags(node_id).contains(NodeFlags::ADDRESS_TAKEN)
470 }
471
472 #[must_use]
474 pub fn is_callsite_promiscuous(&self, node_id: NodeId) -> bool {
475 self.get_flags(node_id)
476 .contains(NodeFlags::CALLSITE_PROMISCUOUS)
477 }
478
479 pub fn mark_synthetic(&mut self, node_id: NodeId) {
484 self.set_flag(node_id, NodeFlags::SYNTHETIC);
485 }
486
487 pub fn mark_address_taken(&mut self, node_id: NodeId) {
493 self.set_flag(node_id, NodeFlags::ADDRESS_TAKEN);
494 }
495
496 pub fn mark_callsite_promiscuous(&mut self, node_id: NodeId) {
500 self.set_flag(node_id, NodeFlags::CALLSITE_PROMISCUOUS);
501 }
502
503 fn set_flag(&mut self, node_id: NodeId, flag: NodeFlags) {
504 self.entries
505 .entry((node_id.index(), node_id.generation()))
506 .or_default()
507 .flags
508 .insert(flag);
509 }
510
511 pub fn insert(&mut self, node_id: NodeId, metadata: MacroNodeMetadata) {
519 self.insert_typed(node_id, TypedMetadata::Macro(metadata));
520 }
521
522 pub fn insert_typed(&mut self, node_id: NodeId, typed: TypedMetadata) {
525 let slot = self
526 .entries
527 .entry((node_id.index(), node_id.generation()))
528 .or_default();
529 slot.typed = Some(typed);
530 }
531
532 pub fn insert_entry(&mut self, node_id: NodeId, entry: StoredEntry) {
536 self.entries
537 .insert((node_id.index(), node_id.generation()), entry);
538 }
539
540 pub fn get_or_insert_default(&mut self, node_id: NodeId) -> &mut MacroNodeMetadata {
551 let slot = self
552 .entries
553 .entry((node_id.index(), node_id.generation()))
554 .or_insert_with(|| {
555 StoredEntry::with_typed(TypedMetadata::Macro(MacroNodeMetadata::default()))
556 });
557 if slot.typed.is_none() {
558 slot.typed = Some(TypedMetadata::Macro(MacroNodeMetadata::default()));
559 }
560 match slot.typed.as_mut() {
561 Some(TypedMetadata::Macro(m)) => m,
562 Some(TypedMetadata::Classpath(_)) => {
563 panic!("get_or_insert_default called on a Classpath typed metadata entry")
564 }
565 None => unreachable!("just populated above"),
566 }
567 }
568
569 pub fn remove(&mut self, node_id: NodeId) -> Option<MacroNodeMetadata> {
575 match self
576 .entries
577 .remove(&(node_id.index(), node_id.generation()))?
578 .typed
579 {
580 Some(TypedMetadata::Macro(m)) => Some(m),
581 Some(TypedMetadata::Classpath(_)) | None => None,
582 }
583 }
584
585 pub fn remove_entry(&mut self, node_id: NodeId) -> Option<StoredEntry> {
587 self.entries
588 .remove(&(node_id.index(), node_id.generation()))
589 }
590
591 #[must_use]
597 pub fn len(&self) -> usize {
598 self.entries.len()
599 }
600
601 #[must_use]
603 pub fn is_empty(&self) -> bool {
604 self.entries.is_empty()
605 }
606
607 pub fn iter(&self) -> impl Iterator<Item = ((u32, u64), &MacroNodeMetadata)> {
611 self.entries.iter().filter_map(|(&k, v)| match &v.typed {
612 Some(TypedMetadata::Macro(m)) => Some((k, m)),
613 Some(TypedMetadata::Classpath(_)) | None => None,
614 })
615 }
616
617 pub fn iter_entries(&self) -> impl Iterator<Item = ((u32, u64), &StoredEntry)> {
621 self.entries.iter().map(|(&k, v)| (k, v))
622 }
623
624 pub fn merge(&mut self, other: &NodeMetadataStore) {
628 for (&key, value) in &other.entries {
629 self.entries.insert(key, value.clone());
630 }
631 }
632
633 #[allow(dead_code)]
654 pub(crate) fn retain_entries<F>(&mut self, mut keep: F)
655 where
656 F: FnMut(u32, u64) -> bool,
657 {
658 self.entries
659 .retain(|&(index, generation), _entry| keep(index, generation));
660 }
661
662 #[cfg(any(test, feature = "test-support"))]
675 pub fn clear_phase_a_flags_for_test(&mut self) {
676 let mask = NodeFlags::ADDRESS_TAKEN | NodeFlags::CALLSITE_PROMISCUOUS;
677 for slot in self.entries.values_mut() {
678 slot.flags.remove(mask);
679 }
680 }
681
682 #[must_use]
698 pub fn framework_routes(&self) -> &FrameworkRoutesMap {
699 &self.framework_routes
700 }
701
702 pub fn framework_routes_mut(&mut self) -> &mut FrameworkRoutesMap {
708 &mut self.framework_routes
709 }
710
711 pub fn set_framework_routes(&mut self, routes: FrameworkRoutesMap) {
716 self.framework_routes = routes;
717 }
718
719 #[must_use]
722 pub fn framework_route(&self, node_id: NodeId) -> Option<&FrameworkRouteMetadata> {
723 self.framework_routes.get(&node_id)
724 }
725
726 #[must_use]
732 pub fn dispatch_tables(&self) -> &DispatchTables {
733 &self.dispatch_tables
734 }
735
736 pub fn dispatch_tables_mut(&mut self) -> &mut DispatchTables {
738 &mut self.dispatch_tables
739 }
740
741 pub fn set_dispatch_tables(&mut self, tables: DispatchTables) {
746 self.dispatch_tables = tables;
747 }
748}
749
750impl PartialEq for NodeMetadataStore {
751 fn eq(&self, other: &Self) -> bool {
752 self.entries == other.entries
753 && self.framework_routes == other.framework_routes
754 && self.dispatch_tables == other.dispatch_tables
755 }
756}
757
758impl Eq for NodeMetadataStore {}
759
760impl crate::graph::unified::memory::GraphMemorySize for NodeMetadataStore {
761 fn heap_bytes(&self) -> usize {
762 use crate::graph::unified::memory::HASHMAP_ENTRY_OVERHEAD;
763
764 let base = self.entries.capacity()
765 * (std::mem::size_of::<(u32, u64)>()
766 + std::mem::size_of::<StoredEntry>()
767 + HASHMAP_ENTRY_OVERHEAD);
768 let inner: usize = self
772 .entries
773 .values()
774 .map(|entry| match &entry.typed {
775 None => 0,
776 Some(TypedMetadata::Macro(m)) => {
777 m.macro_source.as_ref().map_or(0, String::capacity)
778 + m.cfg_condition.as_ref().map_or(0, String::capacity)
779 + m.unresolved_attributes
780 .iter()
781 .map(String::capacity)
782 .sum::<usize>()
783 + m.unresolved_attributes.capacity() * std::mem::size_of::<String>()
784 }
785 Some(TypedMetadata::Classpath(c)) => {
786 c.coordinates.as_ref().map_or(0, String::capacity)
787 + c.jar_path.capacity()
788 + c.fqn.capacity()
789 }
790 })
791 .sum();
792 let framework_routes_bytes = self.framework_routes.len()
797 * (std::mem::size_of::<NodeId>() + std::mem::size_of::<FrameworkRouteMetadata>());
798 let dt = &self.dispatch_tables;
803 let dispatch_tables_bytes = dt.jvm_virtual.len()
804 * (std::mem::size_of::<NodeId>()
805 + std::mem::size_of::<super::dispatch_tables::JvmDispatchEntry>())
806 + dt.go_interface.len()
807 * (std::mem::size_of::<NodeId>()
808 + std::mem::size_of::<super::dispatch_tables::GoDispatchEntry>())
809 + dt.python_duck.len()
810 * (std::mem::size_of::<NodeId>()
811 + std::mem::size_of::<super::dispatch_tables::PythonDispatchEntry>())
812 + dt.ts_structural.len()
813 * (std::mem::size_of::<NodeId>()
814 + std::mem::size_of::<super::dispatch_tables::TsDispatchEntry>())
815 + dt.cap_hits.len() * std::mem::size_of::<super::dispatch_tables::CapHit>();
816 base + inner + framework_routes_bytes + dispatch_tables_bytes
817 }
818}
819
820#[cfg(test)]
821mod tests {
822 use super::*;
823
824 #[test]
825 fn test_metadata_store_basic_operations() {
826 let mut store = NodeMetadataStore::new();
827 assert!(store.is_empty());
828 assert_eq!(store.len(), 0);
829
830 let node = NodeId::new(5, 1);
831 let metadata = MacroNodeMetadata {
832 macro_generated: Some(true),
833 macro_source: Some("derive_Debug".to_string()),
834 ..Default::default()
835 };
836
837 store.insert(node, metadata.clone());
838 assert_eq!(store.len(), 1);
839 assert!(!store.is_empty());
840
841 let retrieved = store.get_macro(node).unwrap();
842 assert_eq!(retrieved.macro_generated, Some(true));
843 assert_eq!(retrieved.macro_source.as_deref(), Some("derive_Debug"));
844 }
845
846 #[test]
847 fn test_metadata_full_nodeid_key() {
848 let mut store = NodeMetadataStore::new();
849
850 let node_gen1 = NodeId::new(5, 1);
851 let node_gen2 = NodeId::new(5, 2);
852
853 store.insert(
854 node_gen1,
855 MacroNodeMetadata {
856 macro_generated: Some(true),
857 ..Default::default()
858 },
859 );
860
861 assert!(store.get_macro(node_gen2).is_none());
863
864 assert!(store.get_macro(node_gen1).is_some());
866 }
867
868 #[test]
869 fn test_metadata_slot_reuse_no_stale_data() {
870 let mut store = NodeMetadataStore::new();
871
872 let old_node = NodeId::new(5, 1);
874 store.insert(
875 old_node,
876 MacroNodeMetadata {
877 cfg_condition: Some("test".to_string()),
878 ..Default::default()
879 },
880 );
881
882 let new_node = NodeId::new(5, 2);
884
885 assert!(store.get_macro(new_node).is_none());
887
888 assert_eq!(
890 store.get_macro(old_node).unwrap().cfg_condition.as_deref(),
891 Some("test")
892 );
893 }
894
895 #[test]
896 fn test_metadata_store_postcard_roundtrip() {
897 let mut store = NodeMetadataStore::new();
898
899 store.insert(
900 NodeId::new(1, 0),
901 MacroNodeMetadata {
902 macro_generated: Some(true),
903 macro_source: Some("derive_Debug".to_string()),
904 cfg_condition: Some("test".to_string()),
905 cfg_active: Some(true),
906 proc_macro_kind: Some(ProcMacroFunctionKind::Derive),
907 expansion_cached: Some(false),
908 unresolved_attributes: vec!["my_attr".to_string()],
909 },
910 );
911
912 store.insert(
913 NodeId::new(42, 3),
914 MacroNodeMetadata {
915 cfg_condition: Some("feature = \"serde\"".to_string()),
916 ..Default::default()
917 },
918 );
919
920 let bytes = postcard::to_allocvec(&store).expect("serialize");
921 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
922
923 assert_eq!(store, deserialized);
924 }
925
926 #[test]
927 fn test_empty_metadata_store_zero_overhead() {
928 let store = NodeMetadataStore::new();
929 let bytes = postcard::to_allocvec(&store).expect("serialize");
930
931 assert!(
933 bytes.len() <= 2,
934 "Empty store should serialize to minimal bytes, got {} bytes",
935 bytes.len()
936 );
937 }
938
939 #[test]
940 fn test_metadata_store_merge() {
941 let mut store1 = NodeMetadataStore::new();
942 let mut store2 = NodeMetadataStore::new();
943
944 store1.insert(
945 NodeId::new(1, 0),
946 MacroNodeMetadata {
947 macro_generated: Some(true),
948 ..Default::default()
949 },
950 );
951
952 store2.insert(
953 NodeId::new(2, 0),
954 MacroNodeMetadata {
955 cfg_condition: Some("test".to_string()),
956 ..Default::default()
957 },
958 );
959
960 store1.merge(&store2);
961 assert_eq!(store1.len(), 2);
962 assert!(store1.get_macro(NodeId::new(1, 0)).is_some());
963 assert!(store1.get_macro(NodeId::new(2, 0)).is_some());
964 }
965
966 #[test]
967 fn test_proc_macro_function_kind_serde() {
968 let kinds = [
969 ProcMacroFunctionKind::Derive,
970 ProcMacroFunctionKind::Attribute,
971 ProcMacroFunctionKind::FunctionLike,
972 ];
973
974 for kind in kinds {
975 let bytes = postcard::to_allocvec(&kind).expect("serialize");
976 let deserialized: ProcMacroFunctionKind =
977 postcard::from_bytes(&bytes).expect("deserialize");
978 assert_eq!(kind, deserialized);
979 }
980 }
981
982 #[test]
983 fn test_metadata_get_or_insert_default() {
984 let mut store = NodeMetadataStore::new();
985 let node = NodeId::new(10, 0);
986
987 let meta = store.get_or_insert_default(node);
989 meta.cfg_condition = Some("test".to_string());
990
991 let meta = store.get_macro(node).unwrap();
993 assert_eq!(meta.cfg_condition.as_deref(), Some("test"));
994 }
995
996 #[test]
997 fn test_metadata_remove() {
998 let mut store = NodeMetadataStore::new();
999 let node = NodeId::new(1, 0);
1000
1001 store.insert(
1002 node,
1003 MacroNodeMetadata {
1004 macro_generated: Some(true),
1005 ..Default::default()
1006 },
1007 );
1008
1009 assert!(store.get_macro(node).is_some());
1010 let removed = store.remove(node);
1011 assert!(removed.is_some());
1012 assert!(store.get_macro(node).is_none());
1013 assert!(store.is_empty());
1014 }
1015
1016 #[test]
1017 fn test_metadata_store_large_scale() {
1018 let mut store = NodeMetadataStore::new();
1019
1020 for i in 0..10_000u32 {
1022 store.insert(
1023 NodeId::new(i, 0),
1024 MacroNodeMetadata {
1025 cfg_condition: Some(format!("feature_{i}")),
1026 ..Default::default()
1027 },
1028 );
1029 }
1030
1031 assert_eq!(store.len(), 10_000);
1032
1033 assert!(store.get_macro(NodeId::new(0, 0)).is_some());
1035 assert!(store.get_macro(NodeId::new(5_000, 0)).is_some());
1036 assert!(store.get_macro(NodeId::new(9_999, 0)).is_some());
1037 assert!(store.get_macro(NodeId::new(10_000, 0)).is_none());
1038
1039 let bytes = postcard::to_allocvec(&store).expect("serialize");
1041 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
1042 assert_eq!(store, deserialized);
1043 }
1044
1045 #[test]
1046 fn test_classpath_metadata_insert_and_get() {
1047 let mut store = NodeMetadataStore::new();
1048 let node = NodeId::new(100, 0);
1049
1050 let cp_meta = ClasspathNodeMetadata {
1051 coordinates: Some("com.google.guava:guava:33.0.0".to_string()),
1052 jar_path: "/home/user/.m2/repository/guava-33.0.0.jar".to_string(),
1053 fqn: "com.google.common.collect.ImmutableList".to_string(),
1054 is_direct_dependency: true,
1055 };
1056
1057 store.insert_typed(node, TypedMetadata::Classpath(cp_meta.clone()));
1058 assert_eq!(store.len(), 1);
1059
1060 assert!(store.get_macro(node).is_none());
1062
1063 let retrieved = store.get_typed(node).unwrap();
1065 match retrieved {
1066 TypedMetadata::Classpath(cp) => {
1067 assert_eq!(cp.fqn, "com.google.common.collect.ImmutableList");
1068 assert_eq!(
1069 cp.coordinates.as_deref(),
1070 Some("com.google.guava:guava:33.0.0")
1071 );
1072 assert!(cp.is_direct_dependency);
1073 }
1074 TypedMetadata::Macro(_) => panic!("expected Classpath variant"),
1075 }
1076 }
1077
1078 #[test]
1079 fn test_classpath_metadata_postcard_roundtrip() {
1080 let mut store = NodeMetadataStore::new();
1081
1082 store.insert(
1084 NodeId::new(1, 0),
1085 MacroNodeMetadata {
1086 macro_generated: Some(true),
1087 ..Default::default()
1088 },
1089 );
1090
1091 store.insert_typed(
1092 NodeId::new(2, 0),
1093 TypedMetadata::Classpath(ClasspathNodeMetadata {
1094 coordinates: Some("org.slf4j:slf4j-api:2.0.0".to_string()),
1095 jar_path: "slf4j-api-2.0.0.jar".to_string(),
1096 fqn: "org.slf4j.Logger".to_string(),
1097 is_direct_dependency: false,
1098 }),
1099 );
1100
1101 let bytes = postcard::to_allocvec(&store).expect("serialize");
1102 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
1103 assert_eq!(store, deserialized);
1104 assert_eq!(deserialized.len(), 2);
1105
1106 assert!(deserialized.get_macro(NodeId::new(1, 0)).is_some());
1108
1109 let cp = deserialized.get_typed(NodeId::new(2, 0)).unwrap();
1111 assert!(matches!(cp, TypedMetadata::Classpath(_)));
1112 }
1113
1114 #[test]
1115 fn test_node_metadata_store_json_roundtrip() {
1116 let mut store = NodeMetadataStore::new();
1117
1118 store.insert(
1119 NodeId::new(1, 0),
1120 MacroNodeMetadata {
1121 macro_generated: Some(true),
1122 macro_source: Some("serde_derive".to_string()),
1123 ..Default::default()
1124 },
1125 );
1126
1127 store.insert_typed(
1128 NodeId::new(2, 0),
1129 TypedMetadata::Classpath(ClasspathNodeMetadata {
1130 coordinates: None,
1131 jar_path: "rt.jar".to_string(),
1132 fqn: "java.lang.String".to_string(),
1133 is_direct_dependency: true,
1134 }),
1135 );
1136
1137 let json = serde_json::to_string(&store).unwrap();
1138 let deserialized: NodeMetadataStore = serde_json::from_str(&json).unwrap();
1139 assert_eq!(store, deserialized);
1140 }
1141
1142 #[test]
1143 fn test_iter_entries_includes_both_types() {
1144 let mut store = NodeMetadataStore::new();
1145
1146 store.insert(
1147 NodeId::new(1, 0),
1148 MacroNodeMetadata {
1149 macro_generated: Some(true),
1150 ..Default::default()
1151 },
1152 );
1153
1154 store.insert_typed(
1155 NodeId::new(2, 0),
1156 TypedMetadata::Classpath(ClasspathNodeMetadata {
1157 coordinates: None,
1158 jar_path: "test.jar".to_string(),
1159 fqn: "com.example.Test".to_string(),
1160 is_direct_dependency: true,
1161 }),
1162 );
1163
1164 let macro_entries: Vec<_> = store.iter().collect();
1166 assert_eq!(macro_entries.len(), 1);
1167
1168 let all_entries: Vec<_> = store.iter_entries().collect();
1170 assert_eq!(all_entries.len(), 2);
1171 }
1172
1173 #[test]
1174 fn test_remove_entry_classpath() {
1175 let mut store = NodeMetadataStore::new();
1176 let node = NodeId::new(50, 0);
1177
1178 store.insert_typed(
1179 node,
1180 TypedMetadata::Classpath(ClasspathNodeMetadata {
1181 coordinates: None,
1182 jar_path: "test.jar".to_string(),
1183 fqn: "Test".to_string(),
1184 is_direct_dependency: true,
1185 }),
1186 );
1187
1188 assert_eq!(store.len(), 1);
1189
1190 let removed = store.remove(node);
1192 assert!(removed.is_none());
1193 assert!(store.is_empty());
1194 }
1195
1196 #[test]
1197 fn test_remove_entry_typed() {
1198 let mut store = NodeMetadataStore::new();
1199 let node = NodeId::new(50, 0);
1200
1201 store.insert_typed(
1202 node,
1203 TypedMetadata::Classpath(ClasspathNodeMetadata {
1204 coordinates: None,
1205 jar_path: "test.jar".to_string(),
1206 fqn: "Test".to_string(),
1207 is_direct_dependency: true,
1208 }),
1209 );
1210
1211 let removed = store.remove_entry(node);
1213 assert!(matches!(
1214 removed.as_ref().and_then(|e| e.typed.as_ref()),
1215 Some(TypedMetadata::Classpath(_))
1216 ));
1217 assert!(store.is_empty());
1218 }
1219
1220 mod node_flags_tests {
1225 use super::*;
1226
1227 #[test]
1228 fn node_flags_bit_composition() {
1229 let mut f = NodeFlags::EMPTY;
1231 assert!(f.is_empty());
1232 f.insert(NodeFlags::SYNTHETIC);
1233 assert!(f.contains(NodeFlags::SYNTHETIC));
1234 assert!(!f.contains(NodeFlags::ADDRESS_TAKEN));
1235
1236 f.insert(NodeFlags::ADDRESS_TAKEN);
1237 assert!(
1238 f.contains(NodeFlags::SYNTHETIC),
1239 "ADDRESS_TAKEN insert must not clear SYNTHETIC"
1240 );
1241 assert!(f.contains(NodeFlags::ADDRESS_TAKEN));
1242
1243 assert!(f.contains(NodeFlags::EMPTY));
1245
1246 f.remove(NodeFlags::SYNTHETIC);
1248 assert!(!f.contains(NodeFlags::SYNTHETIC));
1249 assert!(f.contains(NodeFlags::ADDRESS_TAKEN));
1250
1251 let combined = NodeFlags::SYNTHETIC | NodeFlags::CALLSITE_PROMISCUOUS;
1253 assert!(combined.contains(NodeFlags::SYNTHETIC));
1254 assert!(combined.contains(NodeFlags::CALLSITE_PROMISCUOUS));
1255 assert!(!combined.contains(NodeFlags::ADDRESS_TAKEN));
1256
1257 let mut acc = NodeFlags::EMPTY;
1258 acc |= NodeFlags::ADDRESS_TAKEN;
1259 acc |= NodeFlags::CALLSITE_PROMISCUOUS;
1260 assert!(acc.contains(NodeFlags::ADDRESS_TAKEN | NodeFlags::CALLSITE_PROMISCUOUS));
1261 }
1262
1263 #[test]
1264 fn stored_entry_flags_only_no_typed_payload() {
1265 let entry = StoredEntry::with_flags(NodeFlags::ADDRESS_TAKEN);
1267 assert!(entry.typed.is_none());
1268 assert!(entry.flags.contains(NodeFlags::ADDRESS_TAKEN));
1269 assert!(!entry.flags.contains(NodeFlags::SYNTHETIC));
1270 assert!(!entry.is_vacant());
1271
1272 assert!(StoredEntry::default().is_vacant());
1274 }
1275
1276 #[test]
1277 fn co_occurrence_macro_and_address_taken() {
1278 let mut store = NodeMetadataStore::new();
1282 let node = NodeId::new(42, 1);
1283
1284 store.insert(
1285 node,
1286 MacroNodeMetadata {
1287 macro_generated: Some(true),
1288 macro_source: Some("DEFINE_HANDLER".to_string()),
1289 ..Default::default()
1290 },
1291 );
1292 store.mark_address_taken(node);
1293
1294 let typed = store.get_typed(node).expect("typed entry present");
1296 assert!(matches!(typed, TypedMetadata::Macro(_)));
1297 let macro_meta = store.get_macro(node).expect("macro payload present");
1298 assert_eq!(macro_meta.macro_source.as_deref(), Some("DEFINE_HANDLER"));
1299
1300 assert!(store.is_address_taken(node));
1302 assert!(!store.is_synthetic(node));
1303 assert!(!store.is_callsite_promiscuous(node));
1304
1305 store.mark_synthetic(node);
1307 assert!(store.is_synthetic(node));
1308 assert!(store.is_address_taken(node));
1309 assert!(
1310 store.get_macro(node).is_some(),
1311 "mark_synthetic must not clobber Macro payload"
1312 );
1313 }
1314
1315 #[test]
1316 fn synthetic_via_flag_not_typed() {
1317 let mut store = NodeMetadataStore::new();
1320 let node = NodeId::new(7, 1);
1321
1322 assert!(!store.is_synthetic(node), "missing entry must report false");
1323
1324 store.mark_synthetic(node);
1325 assert!(store.is_synthetic(node));
1326 assert!(
1327 store.get_typed(node).is_none(),
1328 "mark_synthetic must not populate the typed slot"
1329 );
1330 assert!(store.get_macro(node).is_none());
1331
1332 let stale = NodeId::new(7, 2);
1334 assert!(!store.is_synthetic(stale));
1335 }
1336
1337 #[test]
1338 fn mark_address_taken_preserves_typed_payload() {
1339 let mut store = NodeMetadataStore::new();
1342 let node = NodeId::new(101, 3);
1343 let macro_meta = MacroNodeMetadata {
1344 macro_generated: Some(true),
1345 macro_source: Some("foo_macro".to_string()),
1346 cfg_condition: Some("feature = \"x\"".to_string()),
1347 ..Default::default()
1348 };
1349 store.insert(node, macro_meta.clone());
1350
1351 store.mark_address_taken(node);
1352
1353 assert!(store.is_address_taken(node));
1354 let retrieved = store.get_macro(node).expect("Macro payload preserved");
1355 assert_eq!(retrieved, ¯o_meta);
1356 }
1357
1358 #[test]
1359 fn mark_callsite_promiscuous_independent_of_typed() {
1360 let mut store = NodeMetadataStore::new();
1363 let node = NodeId::new(202, 0);
1364
1365 store.mark_callsite_promiscuous(node);
1366 assert!(store.is_callsite_promiscuous(node));
1367 assert!(!store.is_synthetic(node));
1368 assert!(!store.is_address_taken(node));
1369 assert!(store.get_typed(node).is_none());
1370
1371 store.mark_synthetic(node);
1373 assert!(store.is_callsite_promiscuous(node));
1374 assert!(store.is_synthetic(node));
1375 }
1376
1377 #[test]
1378 fn get_flags_returns_empty_for_missing_entry() {
1379 let store = NodeMetadataStore::new();
1380 let missing = NodeId::new(999, 0);
1381 assert!(store.get_flags(missing).is_empty());
1382 assert!(!store.is_synthetic(missing));
1383 assert!(!store.is_address_taken(missing));
1384 assert!(!store.is_callsite_promiscuous(missing));
1385 }
1386
1387 #[test]
1388 fn get_macro_roundtrips_typed_macro_storage() {
1389 let mut store = NodeMetadataStore::new();
1392 let node_a = NodeId::new(1, 0);
1393 let node_b = NodeId::new(2, 0);
1394 let payload_a = MacroNodeMetadata {
1395 cfg_condition: Some("a".to_string()),
1396 ..Default::default()
1397 };
1398 let payload_b = MacroNodeMetadata {
1399 cfg_condition: Some("b".to_string()),
1400 ..Default::default()
1401 };
1402
1403 store.insert(node_a, payload_a.clone());
1404 store.insert_typed(node_b, TypedMetadata::Macro(payload_b.clone()));
1405
1406 assert_eq!(store.get_macro(node_a), Some(&payload_a));
1407 assert_eq!(store.get_macro(node_b), Some(&payload_b));
1408 }
1409
1410 #[test]
1411 fn serialize_deserialize_preserves_typed_and_flags() {
1412 let mut store = NodeMetadataStore::new();
1415
1416 store.insert(
1417 NodeId::new(1, 0),
1418 MacroNodeMetadata {
1419 macro_generated: Some(true),
1420 ..Default::default()
1421 },
1422 );
1423 store.mark_address_taken(NodeId::new(1, 0));
1424
1425 store.insert_typed(
1426 NodeId::new(2, 0),
1427 TypedMetadata::Classpath(ClasspathNodeMetadata {
1428 coordinates: None,
1429 jar_path: "x.jar".to_string(),
1430 fqn: "com.example.X".to_string(),
1431 is_direct_dependency: true,
1432 }),
1433 );
1434
1435 store.mark_synthetic(NodeId::new(3, 0));
1436 store.mark_address_taken(NodeId::new(4, 9));
1437 store.mark_callsite_promiscuous(NodeId::new(4, 9));
1438
1439 let bytes = postcard::to_allocvec(&store).expect("serialize");
1440 let decoded: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
1441
1442 assert_eq!(store, decoded);
1443
1444 assert!(decoded.get_macro(NodeId::new(1, 0)).is_some());
1446 assert!(decoded.is_address_taken(NodeId::new(1, 0)));
1447 assert!(matches!(
1448 decoded.get_typed(NodeId::new(2, 0)),
1449 Some(TypedMetadata::Classpath(_))
1450 ));
1451 assert!(decoded.is_synthetic(NodeId::new(3, 0)));
1452 assert!(decoded.is_address_taken(NodeId::new(4, 9)));
1453 assert!(decoded.is_callsite_promiscuous(NodeId::new(4, 9)));
1454 }
1455
1456 #[test]
1457 fn json_serialize_deserialize_preserves_typed_and_flags() {
1458 let mut store = NodeMetadataStore::new();
1462 store.insert(
1463 NodeId::new(5, 5),
1464 MacroNodeMetadata {
1465 macro_generated: Some(true),
1466 ..Default::default()
1467 },
1468 );
1469 store.mark_address_taken(NodeId::new(5, 5));
1470 store.mark_synthetic(NodeId::new(9, 0));
1471
1472 let json = serde_json::to_string(&store).expect("json serialize");
1473 let decoded: NodeMetadataStore = serde_json::from_str(&json).expect("json deserialize");
1474 assert_eq!(store, decoded);
1475 }
1476
1477 #[test]
1478 fn insert_entry_bulk_remap_path() {
1479 let mut original = NodeMetadataStore::new();
1483 original.insert_typed(
1484 NodeId::new(10, 0),
1485 TypedMetadata::Classpath(ClasspathNodeMetadata {
1486 coordinates: Some("g:a:1".to_string()),
1487 jar_path: "j.jar".to_string(),
1488 fqn: "F".to_string(),
1489 is_direct_dependency: true,
1490 }),
1491 );
1492 original.mark_address_taken(NodeId::new(10, 0));
1493 original.mark_synthetic(NodeId::new(11, 0));
1494
1495 let mut remapped = NodeMetadataStore::new();
1497 for (key, entry) in original.iter_entries() {
1498 let nid = NodeId::new(key.0, key.1);
1499 remapped.insert_entry(nid, entry.clone());
1500 }
1501
1502 assert_eq!(original, remapped);
1503 assert!(remapped.is_address_taken(NodeId::new(10, 0)));
1504 assert!(matches!(
1505 remapped.get_typed(NodeId::new(10, 0)),
1506 Some(TypedMetadata::Classpath(_))
1507 ));
1508 assert!(remapped.is_synthetic(NodeId::new(11, 0)));
1509 }
1510 }
1511}