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
319impl PartialEq for NodeMetadataStore {
320 fn eq(&self, other: &Self) -> bool {
321 self.entries == other.entries
322 }
323}
324
325impl Eq for NodeMetadataStore {}
326
327impl crate::graph::unified::memory::GraphMemorySize for NodeMetadataStore {
328 fn heap_bytes(&self) -> usize {
329 use crate::graph::unified::memory::HASHMAP_ENTRY_OVERHEAD;
330
331 let base = self.entries.capacity()
332 * (std::mem::size_of::<(u32, u64)>()
333 + std::mem::size_of::<NodeMetadata>()
334 + HASHMAP_ENTRY_OVERHEAD);
335 let inner: usize = self
337 .entries
338 .values()
339 .map(|meta| match meta {
340 NodeMetadata::Macro(m) => {
341 m.macro_source.as_ref().map_or(0, String::capacity)
342 + m.cfg_condition.as_ref().map_or(0, String::capacity)
343 + m.unresolved_attributes
344 .iter()
345 .map(String::capacity)
346 .sum::<usize>()
347 + m.unresolved_attributes.capacity() * std::mem::size_of::<String>()
348 }
349 NodeMetadata::Classpath(c) => {
350 c.coordinates.as_ref().map_or(0, String::capacity)
351 + c.jar_path.capacity()
352 + c.fqn.capacity()
353 }
354 })
355 .sum();
356 base + inner
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_metadata_store_basic_operations() {
366 let mut store = NodeMetadataStore::new();
367 assert!(store.is_empty());
368 assert_eq!(store.len(), 0);
369
370 let node = NodeId::new(5, 1);
371 let metadata = MacroNodeMetadata {
372 macro_generated: Some(true),
373 macro_source: Some("derive_Debug".to_string()),
374 ..Default::default()
375 };
376
377 store.insert(node, metadata.clone());
378 assert_eq!(store.len(), 1);
379 assert!(!store.is_empty());
380
381 let retrieved = store.get(node).unwrap();
382 assert_eq!(retrieved.macro_generated, Some(true));
383 assert_eq!(retrieved.macro_source.as_deref(), Some("derive_Debug"));
384 }
385
386 #[test]
387 fn test_metadata_full_nodeid_key() {
388 let mut store = NodeMetadataStore::new();
389
390 let node_gen1 = NodeId::new(5, 1);
391 let node_gen2 = NodeId::new(5, 2);
392
393 store.insert(
394 node_gen1,
395 MacroNodeMetadata {
396 macro_generated: Some(true),
397 ..Default::default()
398 },
399 );
400
401 assert!(store.get(node_gen2).is_none());
403
404 assert!(store.get(node_gen1).is_some());
406 }
407
408 #[test]
409 fn test_metadata_slot_reuse_no_stale_data() {
410 let mut store = NodeMetadataStore::new();
411
412 let old_node = NodeId::new(5, 1);
414 store.insert(
415 old_node,
416 MacroNodeMetadata {
417 cfg_condition: Some("test".to_string()),
418 ..Default::default()
419 },
420 );
421
422 let new_node = NodeId::new(5, 2);
424
425 assert!(store.get(new_node).is_none());
427
428 assert_eq!(
430 store.get(old_node).unwrap().cfg_condition.as_deref(),
431 Some("test")
432 );
433 }
434
435 #[test]
436 fn test_metadata_store_postcard_roundtrip() {
437 let mut store = NodeMetadataStore::new();
438
439 store.insert(
440 NodeId::new(1, 0),
441 MacroNodeMetadata {
442 macro_generated: Some(true),
443 macro_source: Some("derive_Debug".to_string()),
444 cfg_condition: Some("test".to_string()),
445 cfg_active: Some(true),
446 proc_macro_kind: Some(ProcMacroFunctionKind::Derive),
447 expansion_cached: Some(false),
448 unresolved_attributes: vec!["my_attr".to_string()],
449 },
450 );
451
452 store.insert(
453 NodeId::new(42, 3),
454 MacroNodeMetadata {
455 cfg_condition: Some("feature = \"serde\"".to_string()),
456 ..Default::default()
457 },
458 );
459
460 let bytes = postcard::to_allocvec(&store).expect("serialize");
461 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
462
463 assert_eq!(store, deserialized);
464 }
465
466 #[test]
467 fn test_empty_metadata_store_zero_overhead() {
468 let store = NodeMetadataStore::new();
469 let bytes = postcard::to_allocvec(&store).expect("serialize");
470
471 assert!(
473 bytes.len() <= 2,
474 "Empty store should serialize to minimal bytes, got {} bytes",
475 bytes.len()
476 );
477 }
478
479 #[test]
480 fn test_metadata_store_merge() {
481 let mut store1 = NodeMetadataStore::new();
482 let mut store2 = NodeMetadataStore::new();
483
484 store1.insert(
485 NodeId::new(1, 0),
486 MacroNodeMetadata {
487 macro_generated: Some(true),
488 ..Default::default()
489 },
490 );
491
492 store2.insert(
493 NodeId::new(2, 0),
494 MacroNodeMetadata {
495 cfg_condition: Some("test".to_string()),
496 ..Default::default()
497 },
498 );
499
500 store1.merge(&store2);
501 assert_eq!(store1.len(), 2);
502 assert!(store1.get(NodeId::new(1, 0)).is_some());
503 assert!(store1.get(NodeId::new(2, 0)).is_some());
504 }
505
506 #[test]
507 fn test_proc_macro_function_kind_serde() {
508 let kinds = [
509 ProcMacroFunctionKind::Derive,
510 ProcMacroFunctionKind::Attribute,
511 ProcMacroFunctionKind::FunctionLike,
512 ];
513
514 for kind in kinds {
515 let bytes = postcard::to_allocvec(&kind).expect("serialize");
516 let deserialized: ProcMacroFunctionKind =
517 postcard::from_bytes(&bytes).expect("deserialize");
518 assert_eq!(kind, deserialized);
519 }
520 }
521
522 #[test]
523 fn test_metadata_get_or_insert_default() {
524 let mut store = NodeMetadataStore::new();
525 let node = NodeId::new(10, 0);
526
527 let meta = store.get_or_insert_default(node);
529 meta.cfg_condition = Some("test".to_string());
530
531 let meta = store.get(node).unwrap();
533 assert_eq!(meta.cfg_condition.as_deref(), Some("test"));
534 }
535
536 #[test]
537 fn test_metadata_remove() {
538 let mut store = NodeMetadataStore::new();
539 let node = NodeId::new(1, 0);
540
541 store.insert(
542 node,
543 MacroNodeMetadata {
544 macro_generated: Some(true),
545 ..Default::default()
546 },
547 );
548
549 assert!(store.get(node).is_some());
550 let removed = store.remove(node);
551 assert!(removed.is_some());
552 assert!(store.get(node).is_none());
553 assert!(store.is_empty());
554 }
555
556 #[test]
557 fn test_metadata_store_large_scale() {
558 let mut store = NodeMetadataStore::new();
559
560 for i in 0..10_000u32 {
562 store.insert(
563 NodeId::new(i, 0),
564 MacroNodeMetadata {
565 cfg_condition: Some(format!("feature_{i}")),
566 ..Default::default()
567 },
568 );
569 }
570
571 assert_eq!(store.len(), 10_000);
572
573 assert!(store.get(NodeId::new(0, 0)).is_some());
575 assert!(store.get(NodeId::new(5_000, 0)).is_some());
576 assert!(store.get(NodeId::new(9_999, 0)).is_some());
577 assert!(store.get(NodeId::new(10_000, 0)).is_none());
578
579 let bytes = postcard::to_allocvec(&store).expect("serialize");
581 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
582 assert_eq!(store, deserialized);
583 }
584
585 #[test]
586 fn test_classpath_metadata_insert_and_get() {
587 let mut store = NodeMetadataStore::new();
588 let node = NodeId::new(100, 0);
589
590 let cp_meta = ClasspathNodeMetadata {
591 coordinates: Some("com.google.guava:guava:33.0.0".to_string()),
592 jar_path: "/home/user/.m2/repository/guava-33.0.0.jar".to_string(),
593 fqn: "com.google.common.collect.ImmutableList".to_string(),
594 is_direct_dependency: true,
595 };
596
597 store.insert_metadata(node, NodeMetadata::Classpath(cp_meta.clone()));
598 assert_eq!(store.len(), 1);
599
600 assert!(store.get(node).is_none());
602
603 let retrieved = store.get_metadata(node).unwrap();
605 match retrieved {
606 NodeMetadata::Classpath(cp) => {
607 assert_eq!(cp.fqn, "com.google.common.collect.ImmutableList");
608 assert_eq!(
609 cp.coordinates.as_deref(),
610 Some("com.google.guava:guava:33.0.0")
611 );
612 assert!(cp.is_direct_dependency);
613 }
614 NodeMetadata::Macro(_) => panic!("expected Classpath variant"),
615 }
616 }
617
618 #[test]
619 fn test_classpath_metadata_postcard_roundtrip() {
620 let mut store = NodeMetadataStore::new();
621
622 store.insert(
624 NodeId::new(1, 0),
625 MacroNodeMetadata {
626 macro_generated: Some(true),
627 ..Default::default()
628 },
629 );
630
631 store.insert_metadata(
632 NodeId::new(2, 0),
633 NodeMetadata::Classpath(ClasspathNodeMetadata {
634 coordinates: Some("org.slf4j:slf4j-api:2.0.0".to_string()),
635 jar_path: "slf4j-api-2.0.0.jar".to_string(),
636 fqn: "org.slf4j.Logger".to_string(),
637 is_direct_dependency: false,
638 }),
639 );
640
641 let bytes = postcard::to_allocvec(&store).expect("serialize");
642 let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
643 assert_eq!(store, deserialized);
644 assert_eq!(deserialized.len(), 2);
645
646 assert!(deserialized.get(NodeId::new(1, 0)).is_some());
648
649 let cp = deserialized.get_metadata(NodeId::new(2, 0)).unwrap();
651 assert!(matches!(cp, NodeMetadata::Classpath(_)));
652 }
653
654 #[test]
655 fn test_node_metadata_store_json_roundtrip() {
656 let mut store = NodeMetadataStore::new();
657
658 store.insert(
659 NodeId::new(1, 0),
660 MacroNodeMetadata {
661 macro_generated: Some(true),
662 macro_source: Some("serde_derive".to_string()),
663 ..Default::default()
664 },
665 );
666
667 store.insert_metadata(
668 NodeId::new(2, 0),
669 NodeMetadata::Classpath(ClasspathNodeMetadata {
670 coordinates: None,
671 jar_path: "rt.jar".to_string(),
672 fqn: "java.lang.String".to_string(),
673 is_direct_dependency: true,
674 }),
675 );
676
677 let json = serde_json::to_string(&store).unwrap();
678 let deserialized: NodeMetadataStore = serde_json::from_str(&json).unwrap();
679 assert_eq!(store, deserialized);
680 }
681
682 #[test]
683 fn test_iter_all_includes_both_types() {
684 let mut store = NodeMetadataStore::new();
685
686 store.insert(
687 NodeId::new(1, 0),
688 MacroNodeMetadata {
689 macro_generated: Some(true),
690 ..Default::default()
691 },
692 );
693
694 store.insert_metadata(
695 NodeId::new(2, 0),
696 NodeMetadata::Classpath(ClasspathNodeMetadata {
697 coordinates: None,
698 jar_path: "test.jar".to_string(),
699 fqn: "com.example.Test".to_string(),
700 is_direct_dependency: true,
701 }),
702 );
703
704 let macro_entries: Vec<_> = store.iter().collect();
706 assert_eq!(macro_entries.len(), 1);
707
708 let all_entries: Vec<_> = store.iter_all().collect();
710 assert_eq!(all_entries.len(), 2);
711 }
712
713 #[test]
714 fn test_remove_metadata_classpath() {
715 let mut store = NodeMetadataStore::new();
716 let node = NodeId::new(50, 0);
717
718 store.insert_metadata(
719 node,
720 NodeMetadata::Classpath(ClasspathNodeMetadata {
721 coordinates: None,
722 jar_path: "test.jar".to_string(),
723 fqn: "Test".to_string(),
724 is_direct_dependency: true,
725 }),
726 );
727
728 assert_eq!(store.len(), 1);
729
730 let removed = store.remove(node);
732 assert!(removed.is_none());
733 assert!(store.is_empty());
735 }
736
737 #[test]
738 fn test_remove_metadata_typed() {
739 let mut store = NodeMetadataStore::new();
740 let node = NodeId::new(50, 0);
741
742 store.insert_metadata(
743 node,
744 NodeMetadata::Classpath(ClasspathNodeMetadata {
745 coordinates: None,
746 jar_path: "test.jar".to_string(),
747 fqn: "Test".to_string(),
748 is_direct_dependency: true,
749 }),
750 );
751
752 let removed = store.remove_metadata(node);
754 assert!(matches!(removed, Some(NodeMetadata::Classpath(_))));
755 assert!(store.is_empty());
756 }
757}