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}
89
90const NODE_METADATA_MACRO: u8 = 0;
92const NODE_METADATA_CLASSPATH: u8 = 1;
93
94#[derive(Debug, Clone, Default)]
118pub struct NodeMetadataStore {
119 entries: HashMap<(u32, u64), NodeMetadata>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125struct NodeMetadataEntryV7 {
126 index: u32,
127 generation: u64,
128 kind: u8,
130 macro_data: Option<MacroNodeMetadata>,
132 classpath_data: Option<ClasspathNodeMetadata>,
134}
135
136impl Serialize for NodeMetadataStore {
137 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
138 let entries: Vec<NodeMetadataEntryV7> = self
139 .entries
140 .iter()
141 .map(|(&(index, generation), metadata)| match metadata {
142 NodeMetadata::Macro(m) => NodeMetadataEntryV7 {
143 index,
144 generation,
145 kind: NODE_METADATA_MACRO,
146 macro_data: Some(m.clone()),
147 classpath_data: None,
148 },
149 NodeMetadata::Classpath(c) => NodeMetadataEntryV7 {
150 index,
151 generation,
152 kind: NODE_METADATA_CLASSPATH,
153 macro_data: None,
154 classpath_data: Some(c.clone()),
155 },
156 })
157 .collect();
158 entries.serialize(serializer)
159 }
160}
161
162impl<'de> Deserialize<'de> for NodeMetadataStore {
163 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
164 let entries: Vec<NodeMetadataEntryV7> = Vec::deserialize(deserializer)?;
170 let mut map = HashMap::with_capacity(entries.len());
171 for e in entries {
172 let metadata = if e.kind == NODE_METADATA_CLASSPATH {
173 let data = e.classpath_data.ok_or_else(|| {
174 serde::de::Error::custom("missing classpath_data for Classpath metadata entry")
175 })?;
176 NodeMetadata::Classpath(data)
177 } else {
178 let data = e.macro_data.unwrap_or_default();
180 NodeMetadata::Macro(data)
181 };
182 map.insert((e.index, e.generation), metadata);
183 }
184 Ok(Self { entries: map })
185 }
186}
187
188impl NodeMetadataStore {
189 #[must_use]
191 pub fn new() -> Self {
192 Self::default()
193 }
194
195 #[must_use]
200 pub fn get(&self, node_id: NodeId) -> Option<&MacroNodeMetadata> {
201 match self.entries.get(&(node_id.index(), node_id.generation()))? {
202 NodeMetadata::Macro(m) => Some(m),
203 NodeMetadata::Classpath(_) => None,
204 }
205 }
206
207 #[must_use]
212 pub fn get_metadata(&self, node_id: NodeId) -> Option<&NodeMetadata> {
213 self.entries.get(&(node_id.index(), node_id.generation()))
214 }
215
216 #[must_use]
218 pub fn get_mut(&mut self, node_id: NodeId) -> Option<&mut MacroNodeMetadata> {
219 match self
220 .entries
221 .get_mut(&(node_id.index(), node_id.generation()))?
222 {
223 NodeMetadata::Macro(m) => Some(m),
224 NodeMetadata::Classpath(_) => None,
225 }
226 }
227
228 pub fn insert(&mut self, node_id: NodeId, metadata: MacroNodeMetadata) {
232 self.entries.insert(
233 (node_id.index(), node_id.generation()),
234 NodeMetadata::Macro(metadata),
235 );
236 }
237
238 pub fn insert_metadata(&mut self, node_id: NodeId, metadata: NodeMetadata) {
240 self.entries
241 .insert((node_id.index(), node_id.generation()), metadata);
242 }
243
244 pub fn get_or_insert_default(&mut self, node_id: NodeId) -> &mut MacroNodeMetadata {
252 let entry = self
253 .entries
254 .entry((node_id.index(), node_id.generation()))
255 .or_insert_with(|| NodeMetadata::Macro(MacroNodeMetadata::default()));
256 match entry {
257 NodeMetadata::Macro(m) => m,
258 NodeMetadata::Classpath(_) => {
262 panic!("get_or_insert_default called on a Classpath metadata entry")
263 }
264 }
265 }
266
267 pub fn remove(&mut self, node_id: NodeId) -> Option<MacroNodeMetadata> {
269 match self
270 .entries
271 .remove(&(node_id.index(), node_id.generation()))?
272 {
273 NodeMetadata::Macro(m) => Some(m),
274 NodeMetadata::Classpath(_) => None,
275 }
276 }
277
278 pub fn remove_metadata(&mut self, node_id: NodeId) -> Option<NodeMetadata> {
280 self.entries
281 .remove(&(node_id.index(), node_id.generation()))
282 }
283
284 #[must_use]
286 pub fn len(&self) -> usize {
287 self.entries.len()
288 }
289
290 #[must_use]
292 pub fn is_empty(&self) -> bool {
293 self.entries.is_empty()
294 }
295
296 pub fn iter(&self) -> impl Iterator<Item = ((u32, u64), &MacroNodeMetadata)> {
298 self.entries.iter().filter_map(|(&k, v)| match v {
299 NodeMetadata::Macro(m) => Some((k, m)),
300 NodeMetadata::Classpath(_) => None,
301 })
302 }
303
304 pub fn iter_all(&self) -> impl Iterator<Item = ((u32, u64), &NodeMetadata)> {
306 self.entries.iter().map(|(&k, v)| (k, v))
307 }
308
309 pub fn merge(&mut self, other: &NodeMetadataStore) {
313 for (&key, value) in &other.entries {
314 self.entries.insert(key, value.clone());
315 }
316 }
317
318 #[allow(dead_code)]
339 pub(crate) fn retain_entries<F>(&mut self, mut keep: F)
340 where
341 F: FnMut(u32, u64) -> bool,
342 {
343 self.entries
344 .retain(|&(index, generation), _meta| keep(index, generation));
345 }
346}
347
348impl PartialEq for NodeMetadataStore {
349 fn eq(&self, other: &Self) -> bool {
350 self.entries == other.entries
351 }
352}
353
354impl Eq for NodeMetadataStore {}
355
356impl crate::graph::unified::memory::GraphMemorySize for NodeMetadataStore {
357 fn heap_bytes(&self) -> usize {
358 use crate::graph::unified::memory::HASHMAP_ENTRY_OVERHEAD;
359
360 let base = self.entries.capacity()
361 * (std::mem::size_of::<(u32, u64)>()
362 + std::mem::size_of::<NodeMetadata>()
363 + HASHMAP_ENTRY_OVERHEAD);
364 let inner: usize = self
366 .entries
367 .values()
368 .map(|meta| match meta {
369 NodeMetadata::Macro(m) => {
370 m.macro_source.as_ref().map_or(0, String::capacity)
371 + m.cfg_condition.as_ref().map_or(0, String::capacity)
372 + m.unresolved_attributes
373 .iter()
374 .map(String::capacity)
375 .sum::<usize>()
376 + m.unresolved_attributes.capacity() * std::mem::size_of::<String>()
377 }
378 NodeMetadata::Classpath(c) => {
379 c.coordinates.as_ref().map_or(0, String::capacity)
380 + c.jar_path.capacity()
381 + c.fqn.capacity()
382 }
383 })
384 .sum();
385 base + inner
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392
393 #[test]
394 fn test_metadata_store_basic_operations() {
395 let mut store = NodeMetadataStore::new();
396 assert!(store.is_empty());
397 assert_eq!(store.len(), 0);
398
399 let node = NodeId::new(5, 1);
400 let metadata = MacroNodeMetadata {
401 macro_generated: Some(true),
402 macro_source: Some("derive_Debug".to_string()),
403 ..Default::default()
404 };
405
406 store.insert(node, metadata.clone());
407 assert_eq!(store.len(), 1);
408 assert!(!store.is_empty());
409
410 let retrieved = store.get(node).unwrap();
411 assert_eq!(retrieved.macro_generated, Some(true));
412 assert_eq!(retrieved.macro_source.as_deref(), Some("derive_Debug"));
413 }
414
415 #[test]
416 fn test_metadata_full_nodeid_key() {
417 let mut store = NodeMetadataStore::new();
418
419 let node_gen1 = NodeId::new(5, 1);
420 let node_gen2 = NodeId::new(5, 2);
421
422 store.insert(
423 node_gen1,
424 MacroNodeMetadata {
425 macro_generated: Some(true),
426 ..Default::default()
427 },
428 );
429
430 assert!(store.get(node_gen2).is_none());
432
433 assert!(store.get(node_gen1).is_some());
435 }
436
437 #[test]
438 fn test_metadata_slot_reuse_no_stale_data() {
439 let mut store = NodeMetadataStore::new();
440
441 let old_node = NodeId::new(5, 1);
443 store.insert(
444 old_node,
445 MacroNodeMetadata {
446 cfg_condition: Some("test".to_string()),
447 ..Default::default()
448 },
449 );
450
451 let new_node = NodeId::new(5, 2);
453
454 assert!(store.get(new_node).is_none());
456
457 assert_eq!(
459 store.get(old_node).unwrap().cfg_condition.as_deref(),
460 Some("test")
461 );
462 }
463
464 #[test]
465 fn test_metadata_store_postcard_roundtrip() {
466 let mut store = NodeMetadataStore::new();
467
468 store.insert(
469 NodeId::new(1, 0),
470 MacroNodeMetadata {
471 macro_generated: Some(true),
472 macro_source: Some("derive_Debug".to_string()),
473 cfg_condition: Some("test".to_string()),
474 cfg_active: Some(true),
475 proc_macro_kind: Some(ProcMacroFunctionKind::Derive),
476 expansion_cached: Some(false),
477 unresolved_attributes: vec!["my_attr".to_string()],
478 },
479 );
480
481 store.insert(
482 NodeId::new(42, 3),
483 MacroNodeMetadata {
484 cfg_condition: Some("feature = \"serde\"".to_string()),
485 ..Default::default()
486 },
487 );
488
489 let bytes = postcard::to_allocvec(&store).expect("serialize");
490 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
491
492 assert_eq!(store, deserialized);
493 }
494
495 #[test]
496 fn test_empty_metadata_store_zero_overhead() {
497 let store = NodeMetadataStore::new();
498 let bytes = postcard::to_allocvec(&store).expect("serialize");
499
500 assert!(
502 bytes.len() <= 2,
503 "Empty store should serialize to minimal bytes, got {} bytes",
504 bytes.len()
505 );
506 }
507
508 #[test]
509 fn test_metadata_store_merge() {
510 let mut store1 = NodeMetadataStore::new();
511 let mut store2 = NodeMetadataStore::new();
512
513 store1.insert(
514 NodeId::new(1, 0),
515 MacroNodeMetadata {
516 macro_generated: Some(true),
517 ..Default::default()
518 },
519 );
520
521 store2.insert(
522 NodeId::new(2, 0),
523 MacroNodeMetadata {
524 cfg_condition: Some("test".to_string()),
525 ..Default::default()
526 },
527 );
528
529 store1.merge(&store2);
530 assert_eq!(store1.len(), 2);
531 assert!(store1.get(NodeId::new(1, 0)).is_some());
532 assert!(store1.get(NodeId::new(2, 0)).is_some());
533 }
534
535 #[test]
536 fn test_proc_macro_function_kind_serde() {
537 let kinds = [
538 ProcMacroFunctionKind::Derive,
539 ProcMacroFunctionKind::Attribute,
540 ProcMacroFunctionKind::FunctionLike,
541 ];
542
543 for kind in kinds {
544 let bytes = postcard::to_allocvec(&kind).expect("serialize");
545 let deserialized: ProcMacroFunctionKind =
546 postcard::from_bytes(&bytes).expect("deserialize");
547 assert_eq!(kind, deserialized);
548 }
549 }
550
551 #[test]
552 fn test_metadata_get_or_insert_default() {
553 let mut store = NodeMetadataStore::new();
554 let node = NodeId::new(10, 0);
555
556 let meta = store.get_or_insert_default(node);
558 meta.cfg_condition = Some("test".to_string());
559
560 let meta = store.get(node).unwrap();
562 assert_eq!(meta.cfg_condition.as_deref(), Some("test"));
563 }
564
565 #[test]
566 fn test_metadata_remove() {
567 let mut store = NodeMetadataStore::new();
568 let node = NodeId::new(1, 0);
569
570 store.insert(
571 node,
572 MacroNodeMetadata {
573 macro_generated: Some(true),
574 ..Default::default()
575 },
576 );
577
578 assert!(store.get(node).is_some());
579 let removed = store.remove(node);
580 assert!(removed.is_some());
581 assert!(store.get(node).is_none());
582 assert!(store.is_empty());
583 }
584
585 #[test]
586 fn test_metadata_store_large_scale() {
587 let mut store = NodeMetadataStore::new();
588
589 for i in 0..10_000u32 {
591 store.insert(
592 NodeId::new(i, 0),
593 MacroNodeMetadata {
594 cfg_condition: Some(format!("feature_{i}")),
595 ..Default::default()
596 },
597 );
598 }
599
600 assert_eq!(store.len(), 10_000);
601
602 assert!(store.get(NodeId::new(0, 0)).is_some());
604 assert!(store.get(NodeId::new(5_000, 0)).is_some());
605 assert!(store.get(NodeId::new(9_999, 0)).is_some());
606 assert!(store.get(NodeId::new(10_000, 0)).is_none());
607
608 let bytes = postcard::to_allocvec(&store).expect("serialize");
610 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
611 assert_eq!(store, deserialized);
612 }
613
614 #[test]
615 fn test_classpath_metadata_insert_and_get() {
616 let mut store = NodeMetadataStore::new();
617 let node = NodeId::new(100, 0);
618
619 let cp_meta = ClasspathNodeMetadata {
620 coordinates: Some("com.google.guava:guava:33.0.0".to_string()),
621 jar_path: "/home/user/.m2/repository/guava-33.0.0.jar".to_string(),
622 fqn: "com.google.common.collect.ImmutableList".to_string(),
623 is_direct_dependency: true,
624 };
625
626 store.insert_metadata(node, NodeMetadata::Classpath(cp_meta.clone()));
627 assert_eq!(store.len(), 1);
628
629 assert!(store.get(node).is_none());
631
632 let retrieved = store.get_metadata(node).unwrap();
634 match retrieved {
635 NodeMetadata::Classpath(cp) => {
636 assert_eq!(cp.fqn, "com.google.common.collect.ImmutableList");
637 assert_eq!(
638 cp.coordinates.as_deref(),
639 Some("com.google.guava:guava:33.0.0")
640 );
641 assert!(cp.is_direct_dependency);
642 }
643 NodeMetadata::Macro(_) => panic!("expected Classpath variant"),
644 }
645 }
646
647 #[test]
648 fn test_classpath_metadata_postcard_roundtrip() {
649 let mut store = NodeMetadataStore::new();
650
651 store.insert(
653 NodeId::new(1, 0),
654 MacroNodeMetadata {
655 macro_generated: Some(true),
656 ..Default::default()
657 },
658 );
659
660 store.insert_metadata(
661 NodeId::new(2, 0),
662 NodeMetadata::Classpath(ClasspathNodeMetadata {
663 coordinates: Some("org.slf4j:slf4j-api:2.0.0".to_string()),
664 jar_path: "slf4j-api-2.0.0.jar".to_string(),
665 fqn: "org.slf4j.Logger".to_string(),
666 is_direct_dependency: false,
667 }),
668 );
669
670 let bytes = postcard::to_allocvec(&store).expect("serialize");
671 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
672 assert_eq!(store, deserialized);
673 assert_eq!(deserialized.len(), 2);
674
675 assert!(deserialized.get(NodeId::new(1, 0)).is_some());
677
678 let cp = deserialized.get_metadata(NodeId::new(2, 0)).unwrap();
680 assert!(matches!(cp, NodeMetadata::Classpath(_)));
681 }
682
683 #[test]
684 fn test_node_metadata_store_json_roundtrip() {
685 let mut store = NodeMetadataStore::new();
686
687 store.insert(
688 NodeId::new(1, 0),
689 MacroNodeMetadata {
690 macro_generated: Some(true),
691 macro_source: Some("serde_derive".to_string()),
692 ..Default::default()
693 },
694 );
695
696 store.insert_metadata(
697 NodeId::new(2, 0),
698 NodeMetadata::Classpath(ClasspathNodeMetadata {
699 coordinates: None,
700 jar_path: "rt.jar".to_string(),
701 fqn: "java.lang.String".to_string(),
702 is_direct_dependency: true,
703 }),
704 );
705
706 let json = serde_json::to_string(&store).unwrap();
707 let deserialized: NodeMetadataStore = serde_json::from_str(&json).unwrap();
708 assert_eq!(store, deserialized);
709 }
710
711 #[test]
712 fn test_iter_all_includes_both_types() {
713 let mut store = NodeMetadataStore::new();
714
715 store.insert(
716 NodeId::new(1, 0),
717 MacroNodeMetadata {
718 macro_generated: Some(true),
719 ..Default::default()
720 },
721 );
722
723 store.insert_metadata(
724 NodeId::new(2, 0),
725 NodeMetadata::Classpath(ClasspathNodeMetadata {
726 coordinates: None,
727 jar_path: "test.jar".to_string(),
728 fqn: "com.example.Test".to_string(),
729 is_direct_dependency: true,
730 }),
731 );
732
733 let macro_entries: Vec<_> = store.iter().collect();
735 assert_eq!(macro_entries.len(), 1);
736
737 let all_entries: Vec<_> = store.iter_all().collect();
739 assert_eq!(all_entries.len(), 2);
740 }
741
742 #[test]
743 fn test_remove_metadata_classpath() {
744 let mut store = NodeMetadataStore::new();
745 let node = NodeId::new(50, 0);
746
747 store.insert_metadata(
748 node,
749 NodeMetadata::Classpath(ClasspathNodeMetadata {
750 coordinates: None,
751 jar_path: "test.jar".to_string(),
752 fqn: "Test".to_string(),
753 is_direct_dependency: true,
754 }),
755 );
756
757 assert_eq!(store.len(), 1);
758
759 let removed = store.remove(node);
761 assert!(removed.is_none());
762 assert!(store.is_empty());
764 }
765
766 #[test]
767 fn test_remove_metadata_typed() {
768 let mut store = NodeMetadataStore::new();
769 let node = NodeId::new(50, 0);
770
771 store.insert_metadata(
772 node,
773 NodeMetadata::Classpath(ClasspathNodeMetadata {
774 coordinates: None,
775 jar_path: "test.jar".to_string(),
776 fqn: "Test".to_string(),
777 is_direct_dependency: true,
778 }),
779 );
780
781 let removed = store.remove_metadata(node);
783 assert!(matches!(removed, Some(NodeMetadata::Classpath(_))));
784 assert!(store.is_empty());
785 }
786}