1use std::collections::HashMap;
11
12use serde::{Deserialize, Serialize};
13
14use super::super::node::id::NodeId;
15
16#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
24pub struct MacroNodeMetadata {
25 pub macro_generated: Option<bool>,
27
28 pub macro_source: Option<String>,
30
31 pub cfg_condition: Option<String>,
33
34 pub cfg_active: Option<bool>,
36
37 pub proc_macro_kind: Option<ProcMacroFunctionKind>,
39
40 pub expansion_cached: Option<bool>,
42
43 pub unresolved_attributes: Vec<String>,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum ProcMacroFunctionKind {
52 Derive,
54 Attribute,
56 FunctionLike,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct ClasspathNodeMetadata {
63 pub coordinates: Option<String>,
65 pub jar_path: String,
67 pub fqn: String,
69 pub is_direct_dependency: bool,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum NodeMetadata {
84 Macro(MacroNodeMetadata),
86 Classpath(ClasspathNodeMetadata),
88 Synthetic,
112}
113
114const NODE_METADATA_MACRO: u8 = 0;
116const NODE_METADATA_CLASSPATH: u8 = 1;
117const NODE_METADATA_SYNTHETIC: u8 = 2;
118
119#[derive(Debug, Clone, Default)]
143pub struct NodeMetadataStore {
144 entries: HashMap<(u32, u64), NodeMetadata>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150struct NodeMetadataEntryV7 {
151 index: u32,
152 generation: u64,
153 kind: u8,
155 macro_data: Option<MacroNodeMetadata>,
157 classpath_data: Option<ClasspathNodeMetadata>,
159}
160
161impl Serialize for NodeMetadataStore {
162 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
163 let entries: Vec<NodeMetadataEntryV7> = self
164 .entries
165 .iter()
166 .map(|(&(index, generation), metadata)| match metadata {
167 NodeMetadata::Macro(m) => NodeMetadataEntryV7 {
168 index,
169 generation,
170 kind: NODE_METADATA_MACRO,
171 macro_data: Some(m.clone()),
172 classpath_data: None,
173 },
174 NodeMetadata::Classpath(c) => NodeMetadataEntryV7 {
175 index,
176 generation,
177 kind: NODE_METADATA_CLASSPATH,
178 macro_data: None,
179 classpath_data: Some(c.clone()),
180 },
181 NodeMetadata::Synthetic => NodeMetadataEntryV7 {
187 index,
188 generation,
189 kind: NODE_METADATA_SYNTHETIC,
190 macro_data: None,
191 classpath_data: None,
192 },
193 })
194 .collect();
195 entries.serialize(serializer)
196 }
197}
198
199impl<'de> Deserialize<'de> for NodeMetadataStore {
200 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
201 let entries: Vec<NodeMetadataEntryV7> = Vec::deserialize(deserializer)?;
207 let mut map = HashMap::with_capacity(entries.len());
208 for e in entries {
209 let metadata = match e.kind {
210 NODE_METADATA_CLASSPATH => {
211 let data = e.classpath_data.ok_or_else(|| {
212 serde::de::Error::custom(
213 "missing classpath_data for Classpath metadata entry",
214 )
215 })?;
216 NodeMetadata::Classpath(data)
217 }
218 NODE_METADATA_SYNTHETIC => NodeMetadata::Synthetic,
219 _ => {
220 let data = e.macro_data.unwrap_or_default();
222 NodeMetadata::Macro(data)
223 }
224 };
225 map.insert((e.index, e.generation), metadata);
226 }
227 Ok(Self { entries: map })
228 }
229}
230
231impl NodeMetadataStore {
232 #[must_use]
234 pub fn new() -> Self {
235 Self::default()
236 }
237
238 #[must_use]
243 pub fn get(&self, node_id: NodeId) -> Option<&MacroNodeMetadata> {
244 match self.entries.get(&(node_id.index(), node_id.generation()))? {
245 NodeMetadata::Macro(m) => Some(m),
246 NodeMetadata::Classpath(_) | NodeMetadata::Synthetic => None,
247 }
248 }
249
250 #[must_use]
255 pub fn get_metadata(&self, node_id: NodeId) -> Option<&NodeMetadata> {
256 self.entries.get(&(node_id.index(), node_id.generation()))
257 }
258
259 #[must_use]
261 pub fn get_mut(&mut self, node_id: NodeId) -> Option<&mut MacroNodeMetadata> {
262 match self
263 .entries
264 .get_mut(&(node_id.index(), node_id.generation()))?
265 {
266 NodeMetadata::Macro(m) => Some(m),
267 NodeMetadata::Classpath(_) | NodeMetadata::Synthetic => None,
268 }
269 }
270
271 pub fn insert(&mut self, node_id: NodeId, metadata: MacroNodeMetadata) {
275 self.entries.insert(
276 (node_id.index(), node_id.generation()),
277 NodeMetadata::Macro(metadata),
278 );
279 }
280
281 pub fn insert_metadata(&mut self, node_id: NodeId, metadata: NodeMetadata) {
283 self.entries
284 .insert((node_id.index(), node_id.generation()), metadata);
285 }
286
287 pub fn mark_synthetic(&mut self, node_id: NodeId) {
305 self.entries.insert(
306 (node_id.index(), node_id.generation()),
307 NodeMetadata::Synthetic,
308 );
309 }
310
311 #[must_use]
317 pub fn is_synthetic(&self, node_id: NodeId) -> bool {
318 matches!(
319 self.entries.get(&(node_id.index(), node_id.generation())),
320 Some(NodeMetadata::Synthetic)
321 )
322 }
323
324 pub fn get_or_insert_default(&mut self, node_id: NodeId) -> &mut MacroNodeMetadata {
332 let entry = self
333 .entries
334 .entry((node_id.index(), node_id.generation()))
335 .or_insert_with(|| NodeMetadata::Macro(MacroNodeMetadata::default()));
336 match entry {
337 NodeMetadata::Macro(m) => m,
338 NodeMetadata::Classpath(_) => {
342 panic!("get_or_insert_default called on a Classpath metadata entry")
343 }
344 NodeMetadata::Synthetic => {
345 panic!("get_or_insert_default called on a Synthetic metadata entry")
346 }
347 }
348 }
349
350 pub fn remove(&mut self, node_id: NodeId) -> Option<MacroNodeMetadata> {
352 match self
353 .entries
354 .remove(&(node_id.index(), node_id.generation()))?
355 {
356 NodeMetadata::Macro(m) => Some(m),
357 NodeMetadata::Classpath(_) | NodeMetadata::Synthetic => None,
358 }
359 }
360
361 pub fn remove_metadata(&mut self, node_id: NodeId) -> Option<NodeMetadata> {
363 self.entries
364 .remove(&(node_id.index(), node_id.generation()))
365 }
366
367 #[must_use]
369 pub fn len(&self) -> usize {
370 self.entries.len()
371 }
372
373 #[must_use]
375 pub fn is_empty(&self) -> bool {
376 self.entries.is_empty()
377 }
378
379 pub fn iter(&self) -> impl Iterator<Item = ((u32, u64), &MacroNodeMetadata)> {
381 self.entries.iter().filter_map(|(&k, v)| match v {
382 NodeMetadata::Macro(m) => Some((k, m)),
383 NodeMetadata::Classpath(_) | NodeMetadata::Synthetic => None,
384 })
385 }
386
387 pub fn iter_all(&self) -> impl Iterator<Item = ((u32, u64), &NodeMetadata)> {
389 self.entries.iter().map(|(&k, v)| (k, v))
390 }
391
392 pub fn merge(&mut self, other: &NodeMetadataStore) {
396 for (&key, value) in &other.entries {
397 self.entries.insert(key, value.clone());
398 }
399 }
400
401 #[allow(dead_code)]
422 pub(crate) fn retain_entries<F>(&mut self, mut keep: F)
423 where
424 F: FnMut(u32, u64) -> bool,
425 {
426 self.entries
427 .retain(|&(index, generation), _meta| keep(index, generation));
428 }
429}
430
431impl PartialEq for NodeMetadataStore {
432 fn eq(&self, other: &Self) -> bool {
433 self.entries == other.entries
434 }
435}
436
437impl Eq for NodeMetadataStore {}
438
439impl crate::graph::unified::memory::GraphMemorySize for NodeMetadataStore {
440 fn heap_bytes(&self) -> usize {
441 use crate::graph::unified::memory::HASHMAP_ENTRY_OVERHEAD;
442
443 let base = self.entries.capacity()
444 * (std::mem::size_of::<(u32, u64)>()
445 + std::mem::size_of::<NodeMetadata>()
446 + HASHMAP_ENTRY_OVERHEAD);
447 let inner: usize = self
449 .entries
450 .values()
451 .map(|meta| match meta {
452 NodeMetadata::Macro(m) => {
453 m.macro_source.as_ref().map_or(0, String::capacity)
454 + m.cfg_condition.as_ref().map_or(0, String::capacity)
455 + m.unresolved_attributes
456 .iter()
457 .map(String::capacity)
458 .sum::<usize>()
459 + m.unresolved_attributes.capacity() * std::mem::size_of::<String>()
460 }
461 NodeMetadata::Classpath(c) => {
462 c.coordinates.as_ref().map_or(0, String::capacity)
463 + c.jar_path.capacity()
464 + c.fqn.capacity()
465 }
466 NodeMetadata::Synthetic => 0,
470 })
471 .sum();
472 base + inner
473 }
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 #[test]
481 fn test_metadata_store_basic_operations() {
482 let mut store = NodeMetadataStore::new();
483 assert!(store.is_empty());
484 assert_eq!(store.len(), 0);
485
486 let node = NodeId::new(5, 1);
487 let metadata = MacroNodeMetadata {
488 macro_generated: Some(true),
489 macro_source: Some("derive_Debug".to_string()),
490 ..Default::default()
491 };
492
493 store.insert(node, metadata.clone());
494 assert_eq!(store.len(), 1);
495 assert!(!store.is_empty());
496
497 let retrieved = store.get(node).unwrap();
498 assert_eq!(retrieved.macro_generated, Some(true));
499 assert_eq!(retrieved.macro_source.as_deref(), Some("derive_Debug"));
500 }
501
502 #[test]
503 fn test_metadata_full_nodeid_key() {
504 let mut store = NodeMetadataStore::new();
505
506 let node_gen1 = NodeId::new(5, 1);
507 let node_gen2 = NodeId::new(5, 2);
508
509 store.insert(
510 node_gen1,
511 MacroNodeMetadata {
512 macro_generated: Some(true),
513 ..Default::default()
514 },
515 );
516
517 assert!(store.get(node_gen2).is_none());
519
520 assert!(store.get(node_gen1).is_some());
522 }
523
524 #[test]
525 fn test_metadata_slot_reuse_no_stale_data() {
526 let mut store = NodeMetadataStore::new();
527
528 let old_node = NodeId::new(5, 1);
530 store.insert(
531 old_node,
532 MacroNodeMetadata {
533 cfg_condition: Some("test".to_string()),
534 ..Default::default()
535 },
536 );
537
538 let new_node = NodeId::new(5, 2);
540
541 assert!(store.get(new_node).is_none());
543
544 assert_eq!(
546 store.get(old_node).unwrap().cfg_condition.as_deref(),
547 Some("test")
548 );
549 }
550
551 #[test]
552 fn test_metadata_store_postcard_roundtrip() {
553 let mut store = NodeMetadataStore::new();
554
555 store.insert(
556 NodeId::new(1, 0),
557 MacroNodeMetadata {
558 macro_generated: Some(true),
559 macro_source: Some("derive_Debug".to_string()),
560 cfg_condition: Some("test".to_string()),
561 cfg_active: Some(true),
562 proc_macro_kind: Some(ProcMacroFunctionKind::Derive),
563 expansion_cached: Some(false),
564 unresolved_attributes: vec!["my_attr".to_string()],
565 },
566 );
567
568 store.insert(
569 NodeId::new(42, 3),
570 MacroNodeMetadata {
571 cfg_condition: Some("feature = \"serde\"".to_string()),
572 ..Default::default()
573 },
574 );
575
576 let bytes = postcard::to_allocvec(&store).expect("serialize");
577 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
578
579 assert_eq!(store, deserialized);
580 }
581
582 #[test]
583 fn test_empty_metadata_store_zero_overhead() {
584 let store = NodeMetadataStore::new();
585 let bytes = postcard::to_allocvec(&store).expect("serialize");
586
587 assert!(
589 bytes.len() <= 2,
590 "Empty store should serialize to minimal bytes, got {} bytes",
591 bytes.len()
592 );
593 }
594
595 #[test]
596 fn test_metadata_store_merge() {
597 let mut store1 = NodeMetadataStore::new();
598 let mut store2 = NodeMetadataStore::new();
599
600 store1.insert(
601 NodeId::new(1, 0),
602 MacroNodeMetadata {
603 macro_generated: Some(true),
604 ..Default::default()
605 },
606 );
607
608 store2.insert(
609 NodeId::new(2, 0),
610 MacroNodeMetadata {
611 cfg_condition: Some("test".to_string()),
612 ..Default::default()
613 },
614 );
615
616 store1.merge(&store2);
617 assert_eq!(store1.len(), 2);
618 assert!(store1.get(NodeId::new(1, 0)).is_some());
619 assert!(store1.get(NodeId::new(2, 0)).is_some());
620 }
621
622 #[test]
623 fn test_proc_macro_function_kind_serde() {
624 let kinds = [
625 ProcMacroFunctionKind::Derive,
626 ProcMacroFunctionKind::Attribute,
627 ProcMacroFunctionKind::FunctionLike,
628 ];
629
630 for kind in kinds {
631 let bytes = postcard::to_allocvec(&kind).expect("serialize");
632 let deserialized: ProcMacroFunctionKind =
633 postcard::from_bytes(&bytes).expect("deserialize");
634 assert_eq!(kind, deserialized);
635 }
636 }
637
638 #[test]
639 fn test_metadata_get_or_insert_default() {
640 let mut store = NodeMetadataStore::new();
641 let node = NodeId::new(10, 0);
642
643 let meta = store.get_or_insert_default(node);
645 meta.cfg_condition = Some("test".to_string());
646
647 let meta = store.get(node).unwrap();
649 assert_eq!(meta.cfg_condition.as_deref(), Some("test"));
650 }
651
652 #[test]
653 fn test_metadata_remove() {
654 let mut store = NodeMetadataStore::new();
655 let node = NodeId::new(1, 0);
656
657 store.insert(
658 node,
659 MacroNodeMetadata {
660 macro_generated: Some(true),
661 ..Default::default()
662 },
663 );
664
665 assert!(store.get(node).is_some());
666 let removed = store.remove(node);
667 assert!(removed.is_some());
668 assert!(store.get(node).is_none());
669 assert!(store.is_empty());
670 }
671
672 #[test]
673 fn test_metadata_store_large_scale() {
674 let mut store = NodeMetadataStore::new();
675
676 for i in 0..10_000u32 {
678 store.insert(
679 NodeId::new(i, 0),
680 MacroNodeMetadata {
681 cfg_condition: Some(format!("feature_{i}")),
682 ..Default::default()
683 },
684 );
685 }
686
687 assert_eq!(store.len(), 10_000);
688
689 assert!(store.get(NodeId::new(0, 0)).is_some());
691 assert!(store.get(NodeId::new(5_000, 0)).is_some());
692 assert!(store.get(NodeId::new(9_999, 0)).is_some());
693 assert!(store.get(NodeId::new(10_000, 0)).is_none());
694
695 let bytes = postcard::to_allocvec(&store).expect("serialize");
697 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
698 assert_eq!(store, deserialized);
699 }
700
701 #[test]
702 fn test_classpath_metadata_insert_and_get() {
703 let mut store = NodeMetadataStore::new();
704 let node = NodeId::new(100, 0);
705
706 let cp_meta = ClasspathNodeMetadata {
707 coordinates: Some("com.google.guava:guava:33.0.0".to_string()),
708 jar_path: "/home/user/.m2/repository/guava-33.0.0.jar".to_string(),
709 fqn: "com.google.common.collect.ImmutableList".to_string(),
710 is_direct_dependency: true,
711 };
712
713 store.insert_metadata(node, NodeMetadata::Classpath(cp_meta.clone()));
714 assert_eq!(store.len(), 1);
715
716 assert!(store.get(node).is_none());
718
719 let retrieved = store.get_metadata(node).unwrap();
721 match retrieved {
722 NodeMetadata::Classpath(cp) => {
723 assert_eq!(cp.fqn, "com.google.common.collect.ImmutableList");
724 assert_eq!(
725 cp.coordinates.as_deref(),
726 Some("com.google.guava:guava:33.0.0")
727 );
728 assert!(cp.is_direct_dependency);
729 }
730 NodeMetadata::Macro(_) => panic!("expected Classpath variant"),
731 NodeMetadata::Synthetic => panic!("expected Classpath variant, got Synthetic"),
732 }
733 }
734
735 #[test]
736 fn test_classpath_metadata_postcard_roundtrip() {
737 let mut store = NodeMetadataStore::new();
738
739 store.insert(
741 NodeId::new(1, 0),
742 MacroNodeMetadata {
743 macro_generated: Some(true),
744 ..Default::default()
745 },
746 );
747
748 store.insert_metadata(
749 NodeId::new(2, 0),
750 NodeMetadata::Classpath(ClasspathNodeMetadata {
751 coordinates: Some("org.slf4j:slf4j-api:2.0.0".to_string()),
752 jar_path: "slf4j-api-2.0.0.jar".to_string(),
753 fqn: "org.slf4j.Logger".to_string(),
754 is_direct_dependency: false,
755 }),
756 );
757
758 let bytes = postcard::to_allocvec(&store).expect("serialize");
759 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
760 assert_eq!(store, deserialized);
761 assert_eq!(deserialized.len(), 2);
762
763 assert!(deserialized.get(NodeId::new(1, 0)).is_some());
765
766 let cp = deserialized.get_metadata(NodeId::new(2, 0)).unwrap();
768 assert!(matches!(cp, NodeMetadata::Classpath(_)));
769 }
770
771 #[test]
772 fn test_node_metadata_store_json_roundtrip() {
773 let mut store = NodeMetadataStore::new();
774
775 store.insert(
776 NodeId::new(1, 0),
777 MacroNodeMetadata {
778 macro_generated: Some(true),
779 macro_source: Some("serde_derive".to_string()),
780 ..Default::default()
781 },
782 );
783
784 store.insert_metadata(
785 NodeId::new(2, 0),
786 NodeMetadata::Classpath(ClasspathNodeMetadata {
787 coordinates: None,
788 jar_path: "rt.jar".to_string(),
789 fqn: "java.lang.String".to_string(),
790 is_direct_dependency: true,
791 }),
792 );
793
794 let json = serde_json::to_string(&store).unwrap();
795 let deserialized: NodeMetadataStore = serde_json::from_str(&json).unwrap();
796 assert_eq!(store, deserialized);
797 }
798
799 #[test]
800 fn test_iter_all_includes_both_types() {
801 let mut store = NodeMetadataStore::new();
802
803 store.insert(
804 NodeId::new(1, 0),
805 MacroNodeMetadata {
806 macro_generated: Some(true),
807 ..Default::default()
808 },
809 );
810
811 store.insert_metadata(
812 NodeId::new(2, 0),
813 NodeMetadata::Classpath(ClasspathNodeMetadata {
814 coordinates: None,
815 jar_path: "test.jar".to_string(),
816 fqn: "com.example.Test".to_string(),
817 is_direct_dependency: true,
818 }),
819 );
820
821 let macro_entries: Vec<_> = store.iter().collect();
823 assert_eq!(macro_entries.len(), 1);
824
825 let all_entries: Vec<_> = store.iter_all().collect();
827 assert_eq!(all_entries.len(), 2);
828 }
829
830 #[test]
831 fn test_remove_metadata_classpath() {
832 let mut store = NodeMetadataStore::new();
833 let node = NodeId::new(50, 0);
834
835 store.insert_metadata(
836 node,
837 NodeMetadata::Classpath(ClasspathNodeMetadata {
838 coordinates: None,
839 jar_path: "test.jar".to_string(),
840 fqn: "Test".to_string(),
841 is_direct_dependency: true,
842 }),
843 );
844
845 assert_eq!(store.len(), 1);
846
847 let removed = store.remove(node);
849 assert!(removed.is_none());
850 assert!(store.is_empty());
852 }
853
854 #[test]
859 fn synthetic_mark_and_query() {
860 let mut store = NodeMetadataStore::new();
861 let node = NodeId::new(7, 1);
862
863 assert!(!store.is_synthetic(node), "missing entry must report false");
864
865 store.mark_synthetic(node);
866 assert!(store.is_synthetic(node));
867 assert_eq!(store.len(), 1);
868
869 assert!(store.get(node).is_none());
871
872 assert!(matches!(
874 store.get_metadata(node),
875 Some(NodeMetadata::Synthetic),
876 ));
877
878 let stale = NodeId::new(7, 2);
880 assert!(!store.is_synthetic(stale));
881 }
882
883 #[test]
884 fn synthetic_replaces_other_variants() {
885 let mut store = NodeMetadataStore::new();
886 let node = NodeId::new(11, 1);
887
888 store.insert(
889 node,
890 MacroNodeMetadata {
891 cfg_condition: Some("test".to_string()),
892 ..Default::default()
893 },
894 );
895 assert!(store.get(node).is_some());
896
897 store.mark_synthetic(node);
898 assert!(store.is_synthetic(node));
899 assert!(store.get(node).is_none());
901 assert_eq!(store.len(), 1);
902 }
903
904 #[test]
905 fn synthetic_postcard_roundtrip_preserves_v10_wire_shape() {
906 let mut store = NodeMetadataStore::new();
909
910 store.insert(
911 NodeId::new(1, 0),
912 MacroNodeMetadata {
913 macro_generated: Some(true),
914 ..Default::default()
915 },
916 );
917 store.insert_metadata(
918 NodeId::new(2, 0),
919 NodeMetadata::Classpath(ClasspathNodeMetadata {
920 coordinates: None,
921 jar_path: "x.jar".to_string(),
922 fqn: "com.example.X".to_string(),
923 is_direct_dependency: true,
924 }),
925 );
926 store.mark_synthetic(NodeId::new(3, 0));
927 store.mark_synthetic(NodeId::new(99, 5));
928
929 let bytes = postcard::to_allocvec(&store).expect("serialize");
930 let decoded: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
931
932 assert_eq!(store, decoded);
933 assert_eq!(decoded.len(), 4);
934 assert!(decoded.is_synthetic(NodeId::new(3, 0)));
935 assert!(decoded.is_synthetic(NodeId::new(99, 5)));
936 assert!(matches!(
937 decoded.get_metadata(NodeId::new(2, 0)),
938 Some(NodeMetadata::Classpath(_))
939 ));
940 assert!(matches!(
941 decoded.get_metadata(NodeId::new(1, 0)),
942 Some(NodeMetadata::Macro(_))
943 ));
944 }
945
946 #[test]
947 fn synthetic_v10_legacy_snapshot_decodes_without_synthetic_entries() {
948 let mut legacy = NodeMetadataStore::new();
951 legacy.insert(
952 NodeId::new(1, 0),
953 MacroNodeMetadata {
954 macro_generated: Some(true),
955 ..Default::default()
956 },
957 );
958 legacy.insert_metadata(
959 NodeId::new(2, 0),
960 NodeMetadata::Classpath(ClasspathNodeMetadata {
961 coordinates: None,
962 jar_path: "y.jar".to_string(),
963 fqn: "com.example.Y".to_string(),
964 is_direct_dependency: false,
965 }),
966 );
967
968 let bytes = postcard::to_allocvec(&legacy).expect("serialize");
972 let decoded: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
973
974 assert_eq!(legacy, decoded);
975 assert!(!decoded.is_synthetic(NodeId::new(1, 0)));
976 assert!(!decoded.is_synthetic(NodeId::new(2, 0)));
977 assert_eq!(decoded.len(), 2);
978 }
979
980 #[test]
981 fn test_remove_metadata_typed() {
982 let mut store = NodeMetadataStore::new();
983 let node = NodeId::new(50, 0);
984
985 store.insert_metadata(
986 node,
987 NodeMetadata::Classpath(ClasspathNodeMetadata {
988 coordinates: None,
989 jar_path: "test.jar".to_string(),
990 fqn: "Test".to_string(),
991 is_direct_dependency: true,
992 }),
993 );
994
995 let removed = store.remove_metadata(node);
997 assert!(matches!(removed, Some(NodeMetadata::Classpath(_))));
998 assert!(store.is_empty());
999 }
1000}