oxur_smap/
node_id.rs

1use std::sync::atomic::{AtomicU32, Ordering};
2
3/// Unique identifier for AST nodes across all compilation stages
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
5pub struct NodeId(u32);
6
7impl NodeId {
8    /// Create a new NodeId from a raw u32 value
9    ///
10    /// # Safety
11    /// The caller must ensure uniqueness across all nodes.
12    /// Typically only used for deserialization or testing.
13    pub const fn from_raw(id: u32) -> Self {
14        NodeId(id)
15    }
16
17    /// Get the raw u32 value
18    pub const fn as_raw(&self) -> u32 {
19        self.0
20    }
21}
22
23impl std::fmt::Display for NodeId {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "NodeId({})", self.0)
26    }
27}
28
29/// Global NodeId generator using atomic counter
30///
31/// Thread-safe generation of unique NodeIds.
32///
33/// # Design Decision: Single Global Counter vs Stage Ranges
34///
35/// We use a single global atomic counter rather than per-stage ranges
36/// (e.g., 100-199 for surface, 200-299 for core) because:
37///
38/// 1. Simpler implementation (single AtomicU32)
39/// 2. No risk of range exhaustion
40/// 3. NodeIds are opaque - internal structure doesn't matter
41/// 4. Debuggability via SourceMap lookup, not NodeId values
42///
43/// If debugging requires it, we can add stage tagging later without
44/// breaking the API.
45pub struct NodeIdGenerator {
46    next_id: AtomicU32,
47}
48
49impl NodeIdGenerator {
50    /// Create a new generator starting from 1 (0 reserved for invalid)
51    pub const fn new() -> Self {
52        Self { next_id: AtomicU32::new(1) }
53    }
54
55    /// Generate the next unique NodeId
56    ///
57    /// # Panics
58    /// Panics if we exceed u32::MAX nodes (2^32 - 1).
59    /// This is unlikely in practice (~4 billion nodes).
60    pub fn next(&self) -> NodeId {
61        let id = self.next_id.fetch_add(1, Ordering::SeqCst);
62        assert!(id < u32::MAX, "NodeId counter overflow");
63        NodeId(id)
64    }
65
66    /// Reset the generator (for testing only)
67    #[cfg(test)]
68    pub fn reset(&self) {
69        self.next_id.store(1, Ordering::SeqCst);
70    }
71}
72
73impl Default for NodeIdGenerator {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79/// Global singleton generator
80///
81/// Thread-safe access to NodeId generation.
82static GLOBAL_GENERATOR: NodeIdGenerator = NodeIdGenerator::new();
83
84/// Generate a new unique NodeId
85///
86/// This is the primary API for obtaining NodeIds during compilation.
87pub fn new_node_id() -> NodeId {
88    GLOBAL_GENERATOR.next()
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_node_id_basic() {
97        let id1 = NodeId::from_raw(42);
98        let id2 = NodeId::from_raw(42);
99        assert_eq!(id1, id2);
100        assert_eq!(id1.as_raw(), 42);
101    }
102
103    #[test]
104    fn test_node_id_display() {
105        let id = NodeId::from_raw(123);
106        assert_eq!(format!("{}", id), "NodeId(123)");
107    }
108
109    #[test]
110    fn test_node_id_ordering() {
111        let id1 = NodeId::from_raw(10);
112        let id2 = NodeId::from_raw(20);
113        assert!(id1 < id2);
114        assert!(id2 > id1);
115    }
116
117    #[test]
118    fn test_node_id_generator() {
119        let gen = NodeIdGenerator::new();
120        let id1 = gen.next();
121        let id2 = gen.next();
122        assert_ne!(id1, id2);
123        assert_eq!(id1.as_raw() + 1, id2.as_raw());
124    }
125
126    #[test]
127    fn test_global_generator_thread_safe() {
128        use std::sync::Arc;
129        use std::thread;
130
131        let ids = Arc::new(std::sync::Mutex::new(Vec::new()));
132        let mut handles = vec![];
133
134        // Spawn 10 threads, each generating 100 NodeIds
135        for _ in 0..10 {
136            let ids_clone = Arc::clone(&ids);
137            let handle = thread::spawn(move || {
138                for _ in 0..100 {
139                    let id = new_node_id();
140                    ids_clone.lock().unwrap().push(id);
141                }
142            });
143            handles.push(handle);
144        }
145
146        for handle in handles {
147            handle.join().unwrap();
148        }
149
150        // All 1000 IDs should be unique
151        let mut id_set = std::collections::HashSet::new();
152        for id in ids.lock().unwrap().iter() {
153            assert!(id_set.insert(*id), "Duplicate NodeId generated");
154        }
155        assert_eq!(id_set.len(), 1000);
156    }
157}