Skip to main content

meta_ast/model/
ids.rs

1//! Newtyped ID types with thread-safe generation.
2//!
3//! `FileId`, `SymbolId`, and `SnapshotId` are newtyped `u32` values
4//! generated by `IdGenerator<T>` which wraps an `AtomicU32` for
5//! lock-free allocation across rayon worker threads.
6
7use std::sync::atomic::{AtomicU32, Ordering};
8
9use serde::{Deserialize, Serialize};
10
11macro_rules! define_id_type {
12    ($name:ident) => {
13        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14        #[serde(transparent)]
15        pub struct $name(pub u32);
16
17        impl $name {
18            pub fn to_raw(self) -> u32 {
19                self.0
20            }
21        }
22    };
23}
24
25define_id_type!(FileId);
26define_id_type!(SymbolId);
27define_id_type!(SnapshotId);
28
29#[derive(Debug)]
30pub struct IdGenerator<T> {
31    counter: AtomicU32,
32    _marker: std::marker::PhantomData<T>,
33}
34
35impl<T> IdGenerator<T> {
36    pub fn new() -> Self {
37        Self {
38            counter: AtomicU32::new(0),
39            _marker: std::marker::PhantomData,
40        }
41    }
42
43    pub fn next(&self) -> T
44    where
45        T: From<u32>,
46    {
47        let val = self.counter.fetch_add(1, Ordering::SeqCst);
48        T::from(val)
49    }
50}
51
52impl<T> Default for IdGenerator<T> {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58impl From<u32> for FileId {
59    fn from(val: u32) -> Self {
60        Self(val)
61    }
62}
63
64impl From<u32> for SymbolId {
65    fn from(val: u32) -> Self {
66        Self(val)
67    }
68}
69
70impl From<u32> for SnapshotId {
71    fn from(val: u32) -> Self {
72        Self(val)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use std::collections::HashSet;
80    use std::sync::Arc;
81
82    #[test]
83    fn file_id_sequential() {
84        let idgen = IdGenerator::<FileId>::new();
85        assert_eq!(idgen.next(), FileId(0));
86        assert_eq!(idgen.next(), FileId(1));
87        assert_eq!(idgen.next(), FileId(2));
88    }
89
90    #[test]
91    fn symbol_id_sequential() {
92        let idgen = IdGenerator::<SymbolId>::new();
93        assert_eq!(idgen.next(), SymbolId(0));
94        assert_eq!(idgen.next(), SymbolId(1));
95        assert_eq!(idgen.next(), SymbolId(2));
96    }
97
98    #[test]
99    fn snapshot_id_sequential() {
100        let idgen = IdGenerator::<SnapshotId>::new();
101        assert_eq!(idgen.next(), SnapshotId(0));
102        assert_eq!(idgen.next(), SnapshotId(1));
103        assert_eq!(idgen.next(), SnapshotId(2));
104    }
105
106    #[test]
107    fn id_generator_thread_safe() {
108        let idgen = Arc::new(IdGenerator::<SymbolId>::new());
109        let mut handles = Vec::new();
110
111        for _ in 0..4 {
112            let idgen = Arc::clone(&idgen);
113            handles.push(std::thread::spawn(move || {
114                let mut ids = Vec::with_capacity(250);
115                for _ in 0..250 {
116                    ids.push(idgen.next());
117                }
118                ids
119            }));
120        }
121
122        let all_ids: Vec<SymbolId> = handles
123            .into_iter()
124            .flat_map(|h| h.join().unwrap_or_default())
125            .collect();
126
127        let unique: HashSet<SymbolId> = all_ids.iter().copied().collect();
128        assert_eq!(unique.len(), 1000, "expected 1000 unique IDs");
129        assert!(
130            unique.iter().all(|id| id.to_raw() < 1000),
131            "all IDs must be in range 0..1000"
132        );
133    }
134
135    #[test]
136    fn file_id_serde_roundtrip() {
137        let original = FileId(42);
138        let json = serde_json::to_string(&original).unwrap();
139        let roundtrip: FileId = serde_json::from_str(&json).unwrap();
140        assert_eq!(original, roundtrip);
141    }
142
143    #[test]
144    fn symbol_id_serde_roundtrip() {
145        let original = SymbolId(99);
146        let json = serde_json::to_string(&original).unwrap();
147        let roundtrip: SymbolId = serde_json::from_str(&json).unwrap();
148        assert_eq!(original, roundtrip);
149    }
150
151    #[test]
152    fn file_id_to_raw() {
153        assert_eq!(FileId(7).to_raw(), 7);
154    }
155
156    #[test]
157    fn id_generator_default() {
158        let idgen = IdGenerator::<FileId>::default();
159        assert_eq!(idgen.next(), FileId(0));
160    }
161}