1use std::collections::HashMap;
26
27use serde::{Deserialize, Serialize};
28
29use super::super::node::id::NodeId;
30
31#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
39pub struct MacroNodeMetadata {
40 pub macro_generated: Option<bool>,
42
43 pub macro_source: Option<String>,
45
46 pub cfg_condition: Option<String>,
48
49 pub cfg_active: Option<bool>,
51
52 pub proc_macro_kind: Option<ProcMacroFunctionKind>,
54
55 pub expansion_cached: Option<bool>,
57
58 pub unresolved_attributes: Vec<String>,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum ProcMacroFunctionKind {
67 Derive,
69 Attribute,
71 FunctionLike,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct ClasspathNodeMetadata {
78 pub coordinates: Option<String>,
80 pub jar_path: String,
82 pub fqn: String,
84 pub is_direct_dependency: bool,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum TypedMetadata {
95 Macro(MacroNodeMetadata),
97 Classpath(ClasspathNodeMetadata),
99}
100
101#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
108#[repr(transparent)]
109pub struct NodeFlags(u8);
110
111impl NodeFlags {
112 pub const SYNTHETIC: NodeFlags = NodeFlags(1 << 0);
122
123 pub const ADDRESS_TAKEN: NodeFlags = NodeFlags(1 << 1);
128
129 pub const CALLSITE_PROMISCUOUS: NodeFlags = NodeFlags(1 << 2);
134
135 pub const EMPTY: NodeFlags = NodeFlags(0);
137
138 #[must_use]
140 pub const fn contains(self, other: NodeFlags) -> bool {
141 (self.0 & other.0) == other.0
142 }
143
144 pub fn insert(&mut self, other: NodeFlags) {
146 self.0 |= other.0;
147 }
148
149 pub fn remove(&mut self, other: NodeFlags) {
151 self.0 &= !other.0;
152 }
153
154 #[must_use]
156 pub const fn is_empty(self) -> bool {
157 self.0 == 0
158 }
159
160 #[must_use]
162 pub const fn bits(self) -> u8 {
163 self.0
164 }
165
166 #[must_use]
168 pub const fn from_bits(bits: u8) -> NodeFlags {
169 NodeFlags(bits)
170 }
171}
172
173impl std::ops::BitOr for NodeFlags {
174 type Output = NodeFlags;
175 fn bitor(self, rhs: NodeFlags) -> NodeFlags {
176 NodeFlags(self.0 | rhs.0)
177 }
178}
179
180impl std::ops::BitOrAssign for NodeFlags {
181 fn bitor_assign(&mut self, rhs: NodeFlags) {
182 self.0 |= rhs.0;
183 }
184}
185
186#[derive(Debug, Clone, Default, PartialEq, Eq)]
194pub struct StoredEntry {
195 pub typed: Option<TypedMetadata>,
198 pub flags: NodeFlags,
200}
201
202impl StoredEntry {
203 #[must_use]
205 pub fn with_typed(typed: TypedMetadata) -> StoredEntry {
206 StoredEntry {
207 typed: Some(typed),
208 flags: NodeFlags::EMPTY,
209 }
210 }
211
212 #[must_use]
214 pub fn with_flags(flags: NodeFlags) -> StoredEntry {
215 StoredEntry { typed: None, flags }
216 }
217
218 #[must_use]
220 pub fn is_vacant(&self) -> bool {
221 self.typed.is_none() && self.flags.is_empty()
222 }
223}
224
225#[derive(Debug, Clone, Default)]
246pub struct NodeMetadataStore {
247 entries: HashMap<(u32, u64), StoredEntry>,
249}
250
251const TYPED_KIND_NONE: u8 = 0;
253const TYPED_KIND_MACRO: u8 = 1;
254const TYPED_KIND_CLASSPATH: u8 = 2;
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
261struct NodeMetadataEntryV11 {
262 index: u32,
263 generation: u64,
264 kind: u8,
266 macro_data: Option<MacroNodeMetadata>,
268 classpath_data: Option<ClasspathNodeMetadata>,
270 flags: u8,
272}
273
274impl Serialize for NodeMetadataStore {
284 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
285 let entries: Vec<NodeMetadataEntryV11> = self
286 .entries
287 .iter()
288 .map(|(&(index, generation), stored)| {
289 let (kind, macro_data, classpath_data) = match &stored.typed {
290 None => (TYPED_KIND_NONE, None, None),
291 Some(TypedMetadata::Macro(m)) => (TYPED_KIND_MACRO, Some(m.clone()), None),
292 Some(TypedMetadata::Classpath(c)) => {
293 (TYPED_KIND_CLASSPATH, None, Some(c.clone()))
294 }
295 };
296 NodeMetadataEntryV11 {
297 index,
298 generation,
299 kind,
300 macro_data,
301 classpath_data,
302 flags: stored.flags.bits(),
303 }
304 })
305 .collect();
306 entries.serialize(serializer)
307 }
308}
309
310impl<'de> Deserialize<'de> for NodeMetadataStore {
311 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
312 let entries: Vec<NodeMetadataEntryV11> = Vec::deserialize(deserializer)?;
313 let mut map = HashMap::with_capacity(entries.len());
314 for e in entries {
315 let typed = match e.kind {
316 TYPED_KIND_NONE => None,
317 TYPED_KIND_MACRO => Some(TypedMetadata::Macro(e.macro_data.unwrap_or_default())),
318 TYPED_KIND_CLASSPATH => {
319 let data = e.classpath_data.ok_or_else(|| {
320 serde::de::Error::custom(
321 "missing classpath_data for Classpath typed metadata entry",
322 )
323 })?;
324 Some(TypedMetadata::Classpath(data))
325 }
326 other => {
327 return Err(serde::de::Error::custom(format!(
328 "unknown typed-metadata kind discriminant {other}"
329 )));
330 }
331 };
332 let stored = StoredEntry {
333 typed,
334 flags: NodeFlags::from_bits(e.flags),
335 };
336 map.insert((e.index, e.generation), stored);
337 }
338 Ok(Self { entries: map })
339 }
340}
341
342impl NodeMetadataStore {
343 #[must_use]
345 pub fn new() -> Self {
346 Self::default()
347 }
348
349 #[must_use]
358 pub fn get_typed(&self, node_id: NodeId) -> Option<&TypedMetadata> {
359 self.entries
360 .get(&(node_id.index(), node_id.generation()))?
361 .typed
362 .as_ref()
363 }
364
365 pub fn get_typed_mut(&mut self, node_id: NodeId) -> Option<&mut TypedMetadata> {
367 self.entries
368 .get_mut(&(node_id.index(), node_id.generation()))?
369 .typed
370 .as_mut()
371 }
372
373 #[must_use]
378 pub fn get_macro(&self, node_id: NodeId) -> Option<&MacroNodeMetadata> {
379 match self.get_typed(node_id)? {
380 TypedMetadata::Macro(m) => Some(m),
381 TypedMetadata::Classpath(_) => None,
382 }
383 }
384
385 pub fn get_macro_mut(&mut self, node_id: NodeId) -> Option<&mut MacroNodeMetadata> {
387 match self.get_typed_mut(node_id)? {
388 TypedMetadata::Macro(m) => Some(m),
389 TypedMetadata::Classpath(_) => None,
390 }
391 }
392
393 #[must_use]
402 pub fn get_flags(&self, node_id: NodeId) -> NodeFlags {
403 self.entries
404 .get(&(node_id.index(), node_id.generation()))
405 .map(|e| e.flags)
406 .unwrap_or(NodeFlags::EMPTY)
407 }
408
409 #[must_use]
411 pub fn is_synthetic(&self, node_id: NodeId) -> bool {
412 self.get_flags(node_id).contains(NodeFlags::SYNTHETIC)
413 }
414
415 #[must_use]
417 pub fn is_address_taken(&self, node_id: NodeId) -> bool {
418 self.get_flags(node_id).contains(NodeFlags::ADDRESS_TAKEN)
419 }
420
421 #[must_use]
423 pub fn is_callsite_promiscuous(&self, node_id: NodeId) -> bool {
424 self.get_flags(node_id)
425 .contains(NodeFlags::CALLSITE_PROMISCUOUS)
426 }
427
428 pub fn mark_synthetic(&mut self, node_id: NodeId) {
433 self.set_flag(node_id, NodeFlags::SYNTHETIC);
434 }
435
436 pub fn mark_address_taken(&mut self, node_id: NodeId) {
442 self.set_flag(node_id, NodeFlags::ADDRESS_TAKEN);
443 }
444
445 pub fn mark_callsite_promiscuous(&mut self, node_id: NodeId) {
449 self.set_flag(node_id, NodeFlags::CALLSITE_PROMISCUOUS);
450 }
451
452 fn set_flag(&mut self, node_id: NodeId, flag: NodeFlags) {
453 self.entries
454 .entry((node_id.index(), node_id.generation()))
455 .or_default()
456 .flags
457 .insert(flag);
458 }
459
460 pub fn insert(&mut self, node_id: NodeId, metadata: MacroNodeMetadata) {
468 self.insert_typed(node_id, TypedMetadata::Macro(metadata));
469 }
470
471 pub fn insert_typed(&mut self, node_id: NodeId, typed: TypedMetadata) {
474 let slot = self
475 .entries
476 .entry((node_id.index(), node_id.generation()))
477 .or_default();
478 slot.typed = Some(typed);
479 }
480
481 pub fn insert_entry(&mut self, node_id: NodeId, entry: StoredEntry) {
485 self.entries
486 .insert((node_id.index(), node_id.generation()), entry);
487 }
488
489 pub fn get_or_insert_default(&mut self, node_id: NodeId) -> &mut MacroNodeMetadata {
500 let slot = self
501 .entries
502 .entry((node_id.index(), node_id.generation()))
503 .or_insert_with(|| {
504 StoredEntry::with_typed(TypedMetadata::Macro(MacroNodeMetadata::default()))
505 });
506 if slot.typed.is_none() {
507 slot.typed = Some(TypedMetadata::Macro(MacroNodeMetadata::default()));
508 }
509 match slot.typed.as_mut() {
510 Some(TypedMetadata::Macro(m)) => m,
511 Some(TypedMetadata::Classpath(_)) => {
512 panic!("get_or_insert_default called on a Classpath typed metadata entry")
513 }
514 None => unreachable!("just populated above"),
515 }
516 }
517
518 pub fn remove(&mut self, node_id: NodeId) -> Option<MacroNodeMetadata> {
524 match self
525 .entries
526 .remove(&(node_id.index(), node_id.generation()))?
527 .typed
528 {
529 Some(TypedMetadata::Macro(m)) => Some(m),
530 Some(TypedMetadata::Classpath(_)) | None => None,
531 }
532 }
533
534 pub fn remove_entry(&mut self, node_id: NodeId) -> Option<StoredEntry> {
536 self.entries
537 .remove(&(node_id.index(), node_id.generation()))
538 }
539
540 #[must_use]
546 pub fn len(&self) -> usize {
547 self.entries.len()
548 }
549
550 #[must_use]
552 pub fn is_empty(&self) -> bool {
553 self.entries.is_empty()
554 }
555
556 pub fn iter(&self) -> impl Iterator<Item = ((u32, u64), &MacroNodeMetadata)> {
560 self.entries.iter().filter_map(|(&k, v)| match &v.typed {
561 Some(TypedMetadata::Macro(m)) => Some((k, m)),
562 Some(TypedMetadata::Classpath(_)) | None => None,
563 })
564 }
565
566 pub fn iter_entries(&self) -> impl Iterator<Item = ((u32, u64), &StoredEntry)> {
570 self.entries.iter().map(|(&k, v)| (k, v))
571 }
572
573 pub fn merge(&mut self, other: &NodeMetadataStore) {
577 for (&key, value) in &other.entries {
578 self.entries.insert(key, value.clone());
579 }
580 }
581
582 #[allow(dead_code)]
603 pub(crate) fn retain_entries<F>(&mut self, mut keep: F)
604 where
605 F: FnMut(u32, u64) -> bool,
606 {
607 self.entries
608 .retain(|&(index, generation), _entry| keep(index, generation));
609 }
610
611 #[cfg(any(test, feature = "test-support"))]
624 pub fn clear_phase_a_flags_for_test(&mut self) {
625 let mask = NodeFlags::ADDRESS_TAKEN | NodeFlags::CALLSITE_PROMISCUOUS;
626 for slot in self.entries.values_mut() {
627 slot.flags.remove(mask);
628 }
629 }
630}
631
632impl PartialEq for NodeMetadataStore {
633 fn eq(&self, other: &Self) -> bool {
634 self.entries == other.entries
635 }
636}
637
638impl Eq for NodeMetadataStore {}
639
640impl crate::graph::unified::memory::GraphMemorySize for NodeMetadataStore {
641 fn heap_bytes(&self) -> usize {
642 use crate::graph::unified::memory::HASHMAP_ENTRY_OVERHEAD;
643
644 let base = self.entries.capacity()
645 * (std::mem::size_of::<(u32, u64)>()
646 + std::mem::size_of::<StoredEntry>()
647 + HASHMAP_ENTRY_OVERHEAD);
648 let inner: usize = self
652 .entries
653 .values()
654 .map(|entry| match &entry.typed {
655 None => 0,
656 Some(TypedMetadata::Macro(m)) => {
657 m.macro_source.as_ref().map_or(0, String::capacity)
658 + m.cfg_condition.as_ref().map_or(0, String::capacity)
659 + m.unresolved_attributes
660 .iter()
661 .map(String::capacity)
662 .sum::<usize>()
663 + m.unresolved_attributes.capacity() * std::mem::size_of::<String>()
664 }
665 Some(TypedMetadata::Classpath(c)) => {
666 c.coordinates.as_ref().map_or(0, String::capacity)
667 + c.jar_path.capacity()
668 + c.fqn.capacity()
669 }
670 })
671 .sum();
672 base + inner
673 }
674}
675
676#[cfg(test)]
677mod tests {
678 use super::*;
679
680 #[test]
681 fn test_metadata_store_basic_operations() {
682 let mut store = NodeMetadataStore::new();
683 assert!(store.is_empty());
684 assert_eq!(store.len(), 0);
685
686 let node = NodeId::new(5, 1);
687 let metadata = MacroNodeMetadata {
688 macro_generated: Some(true),
689 macro_source: Some("derive_Debug".to_string()),
690 ..Default::default()
691 };
692
693 store.insert(node, metadata.clone());
694 assert_eq!(store.len(), 1);
695 assert!(!store.is_empty());
696
697 let retrieved = store.get_macro(node).unwrap();
698 assert_eq!(retrieved.macro_generated, Some(true));
699 assert_eq!(retrieved.macro_source.as_deref(), Some("derive_Debug"));
700 }
701
702 #[test]
703 fn test_metadata_full_nodeid_key() {
704 let mut store = NodeMetadataStore::new();
705
706 let node_gen1 = NodeId::new(5, 1);
707 let node_gen2 = NodeId::new(5, 2);
708
709 store.insert(
710 node_gen1,
711 MacroNodeMetadata {
712 macro_generated: Some(true),
713 ..Default::default()
714 },
715 );
716
717 assert!(store.get_macro(node_gen2).is_none());
719
720 assert!(store.get_macro(node_gen1).is_some());
722 }
723
724 #[test]
725 fn test_metadata_slot_reuse_no_stale_data() {
726 let mut store = NodeMetadataStore::new();
727
728 let old_node = NodeId::new(5, 1);
730 store.insert(
731 old_node,
732 MacroNodeMetadata {
733 cfg_condition: Some("test".to_string()),
734 ..Default::default()
735 },
736 );
737
738 let new_node = NodeId::new(5, 2);
740
741 assert!(store.get_macro(new_node).is_none());
743
744 assert_eq!(
746 store.get_macro(old_node).unwrap().cfg_condition.as_deref(),
747 Some("test")
748 );
749 }
750
751 #[test]
752 fn test_metadata_store_postcard_roundtrip() {
753 let mut store = NodeMetadataStore::new();
754
755 store.insert(
756 NodeId::new(1, 0),
757 MacroNodeMetadata {
758 macro_generated: Some(true),
759 macro_source: Some("derive_Debug".to_string()),
760 cfg_condition: Some("test".to_string()),
761 cfg_active: Some(true),
762 proc_macro_kind: Some(ProcMacroFunctionKind::Derive),
763 expansion_cached: Some(false),
764 unresolved_attributes: vec!["my_attr".to_string()],
765 },
766 );
767
768 store.insert(
769 NodeId::new(42, 3),
770 MacroNodeMetadata {
771 cfg_condition: Some("feature = \"serde\"".to_string()),
772 ..Default::default()
773 },
774 );
775
776 let bytes = postcard::to_allocvec(&store).expect("serialize");
777 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
778
779 assert_eq!(store, deserialized);
780 }
781
782 #[test]
783 fn test_empty_metadata_store_zero_overhead() {
784 let store = NodeMetadataStore::new();
785 let bytes = postcard::to_allocvec(&store).expect("serialize");
786
787 assert!(
789 bytes.len() <= 2,
790 "Empty store should serialize to minimal bytes, got {} bytes",
791 bytes.len()
792 );
793 }
794
795 #[test]
796 fn test_metadata_store_merge() {
797 let mut store1 = NodeMetadataStore::new();
798 let mut store2 = NodeMetadataStore::new();
799
800 store1.insert(
801 NodeId::new(1, 0),
802 MacroNodeMetadata {
803 macro_generated: Some(true),
804 ..Default::default()
805 },
806 );
807
808 store2.insert(
809 NodeId::new(2, 0),
810 MacroNodeMetadata {
811 cfg_condition: Some("test".to_string()),
812 ..Default::default()
813 },
814 );
815
816 store1.merge(&store2);
817 assert_eq!(store1.len(), 2);
818 assert!(store1.get_macro(NodeId::new(1, 0)).is_some());
819 assert!(store1.get_macro(NodeId::new(2, 0)).is_some());
820 }
821
822 #[test]
823 fn test_proc_macro_function_kind_serde() {
824 let kinds = [
825 ProcMacroFunctionKind::Derive,
826 ProcMacroFunctionKind::Attribute,
827 ProcMacroFunctionKind::FunctionLike,
828 ];
829
830 for kind in kinds {
831 let bytes = postcard::to_allocvec(&kind).expect("serialize");
832 let deserialized: ProcMacroFunctionKind =
833 postcard::from_bytes(&bytes).expect("deserialize");
834 assert_eq!(kind, deserialized);
835 }
836 }
837
838 #[test]
839 fn test_metadata_get_or_insert_default() {
840 let mut store = NodeMetadataStore::new();
841 let node = NodeId::new(10, 0);
842
843 let meta = store.get_or_insert_default(node);
845 meta.cfg_condition = Some("test".to_string());
846
847 let meta = store.get_macro(node).unwrap();
849 assert_eq!(meta.cfg_condition.as_deref(), Some("test"));
850 }
851
852 #[test]
853 fn test_metadata_remove() {
854 let mut store = NodeMetadataStore::new();
855 let node = NodeId::new(1, 0);
856
857 store.insert(
858 node,
859 MacroNodeMetadata {
860 macro_generated: Some(true),
861 ..Default::default()
862 },
863 );
864
865 assert!(store.get_macro(node).is_some());
866 let removed = store.remove(node);
867 assert!(removed.is_some());
868 assert!(store.get_macro(node).is_none());
869 assert!(store.is_empty());
870 }
871
872 #[test]
873 fn test_metadata_store_large_scale() {
874 let mut store = NodeMetadataStore::new();
875
876 for i in 0..10_000u32 {
878 store.insert(
879 NodeId::new(i, 0),
880 MacroNodeMetadata {
881 cfg_condition: Some(format!("feature_{i}")),
882 ..Default::default()
883 },
884 );
885 }
886
887 assert_eq!(store.len(), 10_000);
888
889 assert!(store.get_macro(NodeId::new(0, 0)).is_some());
891 assert!(store.get_macro(NodeId::new(5_000, 0)).is_some());
892 assert!(store.get_macro(NodeId::new(9_999, 0)).is_some());
893 assert!(store.get_macro(NodeId::new(10_000, 0)).is_none());
894
895 let bytes = postcard::to_allocvec(&store).expect("serialize");
897 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
898 assert_eq!(store, deserialized);
899 }
900
901 #[test]
902 fn test_classpath_metadata_insert_and_get() {
903 let mut store = NodeMetadataStore::new();
904 let node = NodeId::new(100, 0);
905
906 let cp_meta = ClasspathNodeMetadata {
907 coordinates: Some("com.google.guava:guava:33.0.0".to_string()),
908 jar_path: "/home/user/.m2/repository/guava-33.0.0.jar".to_string(),
909 fqn: "com.google.common.collect.ImmutableList".to_string(),
910 is_direct_dependency: true,
911 };
912
913 store.insert_typed(node, TypedMetadata::Classpath(cp_meta.clone()));
914 assert_eq!(store.len(), 1);
915
916 assert!(store.get_macro(node).is_none());
918
919 let retrieved = store.get_typed(node).unwrap();
921 match retrieved {
922 TypedMetadata::Classpath(cp) => {
923 assert_eq!(cp.fqn, "com.google.common.collect.ImmutableList");
924 assert_eq!(
925 cp.coordinates.as_deref(),
926 Some("com.google.guava:guava:33.0.0")
927 );
928 assert!(cp.is_direct_dependency);
929 }
930 TypedMetadata::Macro(_) => panic!("expected Classpath variant"),
931 }
932 }
933
934 #[test]
935 fn test_classpath_metadata_postcard_roundtrip() {
936 let mut store = NodeMetadataStore::new();
937
938 store.insert(
940 NodeId::new(1, 0),
941 MacroNodeMetadata {
942 macro_generated: Some(true),
943 ..Default::default()
944 },
945 );
946
947 store.insert_typed(
948 NodeId::new(2, 0),
949 TypedMetadata::Classpath(ClasspathNodeMetadata {
950 coordinates: Some("org.slf4j:slf4j-api:2.0.0".to_string()),
951 jar_path: "slf4j-api-2.0.0.jar".to_string(),
952 fqn: "org.slf4j.Logger".to_string(),
953 is_direct_dependency: false,
954 }),
955 );
956
957 let bytes = postcard::to_allocvec(&store).expect("serialize");
958 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
959 assert_eq!(store, deserialized);
960 assert_eq!(deserialized.len(), 2);
961
962 assert!(deserialized.get_macro(NodeId::new(1, 0)).is_some());
964
965 let cp = deserialized.get_typed(NodeId::new(2, 0)).unwrap();
967 assert!(matches!(cp, TypedMetadata::Classpath(_)));
968 }
969
970 #[test]
971 fn test_node_metadata_store_json_roundtrip() {
972 let mut store = NodeMetadataStore::new();
973
974 store.insert(
975 NodeId::new(1, 0),
976 MacroNodeMetadata {
977 macro_generated: Some(true),
978 macro_source: Some("serde_derive".to_string()),
979 ..Default::default()
980 },
981 );
982
983 store.insert_typed(
984 NodeId::new(2, 0),
985 TypedMetadata::Classpath(ClasspathNodeMetadata {
986 coordinates: None,
987 jar_path: "rt.jar".to_string(),
988 fqn: "java.lang.String".to_string(),
989 is_direct_dependency: true,
990 }),
991 );
992
993 let json = serde_json::to_string(&store).unwrap();
994 let deserialized: NodeMetadataStore = serde_json::from_str(&json).unwrap();
995 assert_eq!(store, deserialized);
996 }
997
998 #[test]
999 fn test_iter_entries_includes_both_types() {
1000 let mut store = NodeMetadataStore::new();
1001
1002 store.insert(
1003 NodeId::new(1, 0),
1004 MacroNodeMetadata {
1005 macro_generated: Some(true),
1006 ..Default::default()
1007 },
1008 );
1009
1010 store.insert_typed(
1011 NodeId::new(2, 0),
1012 TypedMetadata::Classpath(ClasspathNodeMetadata {
1013 coordinates: None,
1014 jar_path: "test.jar".to_string(),
1015 fqn: "com.example.Test".to_string(),
1016 is_direct_dependency: true,
1017 }),
1018 );
1019
1020 let macro_entries: Vec<_> = store.iter().collect();
1022 assert_eq!(macro_entries.len(), 1);
1023
1024 let all_entries: Vec<_> = store.iter_entries().collect();
1026 assert_eq!(all_entries.len(), 2);
1027 }
1028
1029 #[test]
1030 fn test_remove_entry_classpath() {
1031 let mut store = NodeMetadataStore::new();
1032 let node = NodeId::new(50, 0);
1033
1034 store.insert_typed(
1035 node,
1036 TypedMetadata::Classpath(ClasspathNodeMetadata {
1037 coordinates: None,
1038 jar_path: "test.jar".to_string(),
1039 fqn: "Test".to_string(),
1040 is_direct_dependency: true,
1041 }),
1042 );
1043
1044 assert_eq!(store.len(), 1);
1045
1046 let removed = store.remove(node);
1048 assert!(removed.is_none());
1049 assert!(store.is_empty());
1050 }
1051
1052 #[test]
1053 fn test_remove_entry_typed() {
1054 let mut store = NodeMetadataStore::new();
1055 let node = NodeId::new(50, 0);
1056
1057 store.insert_typed(
1058 node,
1059 TypedMetadata::Classpath(ClasspathNodeMetadata {
1060 coordinates: None,
1061 jar_path: "test.jar".to_string(),
1062 fqn: "Test".to_string(),
1063 is_direct_dependency: true,
1064 }),
1065 );
1066
1067 let removed = store.remove_entry(node);
1069 assert!(matches!(
1070 removed.as_ref().and_then(|e| e.typed.as_ref()),
1071 Some(TypedMetadata::Classpath(_))
1072 ));
1073 assert!(store.is_empty());
1074 }
1075
1076 mod node_flags_tests {
1081 use super::*;
1082
1083 #[test]
1084 fn node_flags_bit_composition() {
1085 let mut f = NodeFlags::EMPTY;
1087 assert!(f.is_empty());
1088 f.insert(NodeFlags::SYNTHETIC);
1089 assert!(f.contains(NodeFlags::SYNTHETIC));
1090 assert!(!f.contains(NodeFlags::ADDRESS_TAKEN));
1091
1092 f.insert(NodeFlags::ADDRESS_TAKEN);
1093 assert!(
1094 f.contains(NodeFlags::SYNTHETIC),
1095 "ADDRESS_TAKEN insert must not clear SYNTHETIC"
1096 );
1097 assert!(f.contains(NodeFlags::ADDRESS_TAKEN));
1098
1099 assert!(f.contains(NodeFlags::EMPTY));
1101
1102 f.remove(NodeFlags::SYNTHETIC);
1104 assert!(!f.contains(NodeFlags::SYNTHETIC));
1105 assert!(f.contains(NodeFlags::ADDRESS_TAKEN));
1106
1107 let combined = NodeFlags::SYNTHETIC | NodeFlags::CALLSITE_PROMISCUOUS;
1109 assert!(combined.contains(NodeFlags::SYNTHETIC));
1110 assert!(combined.contains(NodeFlags::CALLSITE_PROMISCUOUS));
1111 assert!(!combined.contains(NodeFlags::ADDRESS_TAKEN));
1112
1113 let mut acc = NodeFlags::EMPTY;
1114 acc |= NodeFlags::ADDRESS_TAKEN;
1115 acc |= NodeFlags::CALLSITE_PROMISCUOUS;
1116 assert!(acc.contains(NodeFlags::ADDRESS_TAKEN | NodeFlags::CALLSITE_PROMISCUOUS));
1117 }
1118
1119 #[test]
1120 fn stored_entry_flags_only_no_typed_payload() {
1121 let entry = StoredEntry::with_flags(NodeFlags::ADDRESS_TAKEN);
1123 assert!(entry.typed.is_none());
1124 assert!(entry.flags.contains(NodeFlags::ADDRESS_TAKEN));
1125 assert!(!entry.flags.contains(NodeFlags::SYNTHETIC));
1126 assert!(!entry.is_vacant());
1127
1128 assert!(StoredEntry::default().is_vacant());
1130 }
1131
1132 #[test]
1133 fn co_occurrence_macro_and_address_taken() {
1134 let mut store = NodeMetadataStore::new();
1138 let node = NodeId::new(42, 1);
1139
1140 store.insert(
1141 node,
1142 MacroNodeMetadata {
1143 macro_generated: Some(true),
1144 macro_source: Some("DEFINE_HANDLER".to_string()),
1145 ..Default::default()
1146 },
1147 );
1148 store.mark_address_taken(node);
1149
1150 let typed = store.get_typed(node).expect("typed entry present");
1152 assert!(matches!(typed, TypedMetadata::Macro(_)));
1153 let macro_meta = store.get_macro(node).expect("macro payload present");
1154 assert_eq!(macro_meta.macro_source.as_deref(), Some("DEFINE_HANDLER"));
1155
1156 assert!(store.is_address_taken(node));
1158 assert!(!store.is_synthetic(node));
1159 assert!(!store.is_callsite_promiscuous(node));
1160
1161 store.mark_synthetic(node);
1163 assert!(store.is_synthetic(node));
1164 assert!(store.is_address_taken(node));
1165 assert!(
1166 store.get_macro(node).is_some(),
1167 "mark_synthetic must not clobber Macro payload"
1168 );
1169 }
1170
1171 #[test]
1172 fn synthetic_via_flag_not_typed() {
1173 let mut store = NodeMetadataStore::new();
1176 let node = NodeId::new(7, 1);
1177
1178 assert!(!store.is_synthetic(node), "missing entry must report false");
1179
1180 store.mark_synthetic(node);
1181 assert!(store.is_synthetic(node));
1182 assert!(
1183 store.get_typed(node).is_none(),
1184 "mark_synthetic must not populate the typed slot"
1185 );
1186 assert!(store.get_macro(node).is_none());
1187
1188 let stale = NodeId::new(7, 2);
1190 assert!(!store.is_synthetic(stale));
1191 }
1192
1193 #[test]
1194 fn mark_address_taken_preserves_typed_payload() {
1195 let mut store = NodeMetadataStore::new();
1198 let node = NodeId::new(101, 3);
1199 let macro_meta = MacroNodeMetadata {
1200 macro_generated: Some(true),
1201 macro_source: Some("foo_macro".to_string()),
1202 cfg_condition: Some("feature = \"x\"".to_string()),
1203 ..Default::default()
1204 };
1205 store.insert(node, macro_meta.clone());
1206
1207 store.mark_address_taken(node);
1208
1209 assert!(store.is_address_taken(node));
1210 let retrieved = store.get_macro(node).expect("Macro payload preserved");
1211 assert_eq!(retrieved, ¯o_meta);
1212 }
1213
1214 #[test]
1215 fn mark_callsite_promiscuous_independent_of_typed() {
1216 let mut store = NodeMetadataStore::new();
1219 let node = NodeId::new(202, 0);
1220
1221 store.mark_callsite_promiscuous(node);
1222 assert!(store.is_callsite_promiscuous(node));
1223 assert!(!store.is_synthetic(node));
1224 assert!(!store.is_address_taken(node));
1225 assert!(store.get_typed(node).is_none());
1226
1227 store.mark_synthetic(node);
1229 assert!(store.is_callsite_promiscuous(node));
1230 assert!(store.is_synthetic(node));
1231 }
1232
1233 #[test]
1234 fn get_flags_returns_empty_for_missing_entry() {
1235 let store = NodeMetadataStore::new();
1236 let missing = NodeId::new(999, 0);
1237 assert!(store.get_flags(missing).is_empty());
1238 assert!(!store.is_synthetic(missing));
1239 assert!(!store.is_address_taken(missing));
1240 assert!(!store.is_callsite_promiscuous(missing));
1241 }
1242
1243 #[test]
1244 fn get_macro_roundtrips_typed_macro_storage() {
1245 let mut store = NodeMetadataStore::new();
1248 let node_a = NodeId::new(1, 0);
1249 let node_b = NodeId::new(2, 0);
1250 let payload_a = MacroNodeMetadata {
1251 cfg_condition: Some("a".to_string()),
1252 ..Default::default()
1253 };
1254 let payload_b = MacroNodeMetadata {
1255 cfg_condition: Some("b".to_string()),
1256 ..Default::default()
1257 };
1258
1259 store.insert(node_a, payload_a.clone());
1260 store.insert_typed(node_b, TypedMetadata::Macro(payload_b.clone()));
1261
1262 assert_eq!(store.get_macro(node_a), Some(&payload_a));
1263 assert_eq!(store.get_macro(node_b), Some(&payload_b));
1264 }
1265
1266 #[test]
1267 fn serialize_deserialize_preserves_typed_and_flags() {
1268 let mut store = NodeMetadataStore::new();
1271
1272 store.insert(
1273 NodeId::new(1, 0),
1274 MacroNodeMetadata {
1275 macro_generated: Some(true),
1276 ..Default::default()
1277 },
1278 );
1279 store.mark_address_taken(NodeId::new(1, 0));
1280
1281 store.insert_typed(
1282 NodeId::new(2, 0),
1283 TypedMetadata::Classpath(ClasspathNodeMetadata {
1284 coordinates: None,
1285 jar_path: "x.jar".to_string(),
1286 fqn: "com.example.X".to_string(),
1287 is_direct_dependency: true,
1288 }),
1289 );
1290
1291 store.mark_synthetic(NodeId::new(3, 0));
1292 store.mark_address_taken(NodeId::new(4, 9));
1293 store.mark_callsite_promiscuous(NodeId::new(4, 9));
1294
1295 let bytes = postcard::to_allocvec(&store).expect("serialize");
1296 let decoded: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
1297
1298 assert_eq!(store, decoded);
1299
1300 assert!(decoded.get_macro(NodeId::new(1, 0)).is_some());
1302 assert!(decoded.is_address_taken(NodeId::new(1, 0)));
1303 assert!(matches!(
1304 decoded.get_typed(NodeId::new(2, 0)),
1305 Some(TypedMetadata::Classpath(_))
1306 ));
1307 assert!(decoded.is_synthetic(NodeId::new(3, 0)));
1308 assert!(decoded.is_address_taken(NodeId::new(4, 9)));
1309 assert!(decoded.is_callsite_promiscuous(NodeId::new(4, 9)));
1310 }
1311
1312 #[test]
1313 fn json_serialize_deserialize_preserves_typed_and_flags() {
1314 let mut store = NodeMetadataStore::new();
1318 store.insert(
1319 NodeId::new(5, 5),
1320 MacroNodeMetadata {
1321 macro_generated: Some(true),
1322 ..Default::default()
1323 },
1324 );
1325 store.mark_address_taken(NodeId::new(5, 5));
1326 store.mark_synthetic(NodeId::new(9, 0));
1327
1328 let json = serde_json::to_string(&store).expect("json serialize");
1329 let decoded: NodeMetadataStore = serde_json::from_str(&json).expect("json deserialize");
1330 assert_eq!(store, decoded);
1331 }
1332
1333 #[test]
1334 fn insert_entry_bulk_remap_path() {
1335 let mut original = NodeMetadataStore::new();
1339 original.insert_typed(
1340 NodeId::new(10, 0),
1341 TypedMetadata::Classpath(ClasspathNodeMetadata {
1342 coordinates: Some("g:a:1".to_string()),
1343 jar_path: "j.jar".to_string(),
1344 fqn: "F".to_string(),
1345 is_direct_dependency: true,
1346 }),
1347 );
1348 original.mark_address_taken(NodeId::new(10, 0));
1349 original.mark_synthetic(NodeId::new(11, 0));
1350
1351 let mut remapped = NodeMetadataStore::new();
1353 for (key, entry) in original.iter_entries() {
1354 let nid = NodeId::new(key.0, key.1);
1355 remapped.insert_entry(nid, entry.clone());
1356 }
1357
1358 assert_eq!(original, remapped);
1359 assert!(remapped.is_address_taken(NodeId::new(10, 0)));
1360 assert!(matches!(
1361 remapped.get_typed(NodeId::new(10, 0)),
1362 Some(TypedMetadata::Classpath(_))
1363 ));
1364 assert!(remapped.is_synthetic(NodeId::new(11, 0)));
1365 }
1366 }
1367}