Skip to main content

ryo_analysis/query/
var_id.rs

1//! VarId - Lightweight variable identifier for DataFlowGraph.
2//!
3//! VarId is a local identifier for variables within DataFlowGraph,
4//! providing O(1) access while maintaining a mapping to SymbolId
5//! for integration with the broader symbol system.
6//!
7//! Uses SlotMap for stable identifiers that support deletion.
8
9use crate::symbol::SymbolId;
10use slotmap::{new_key_type, SecondaryMap, SlotMap};
11
12new_key_type! {
13    /// Lightweight variable identifier for DataFlowGraph internal use.
14    ///
15    /// VarId is a SlotMap key optimized for:
16    /// - O(1) comparison and hashing
17    /// - Stable identity across insertions/deletions
18    /// - Safe deletion without index invalidation
19    ///
20    /// For full symbol information, convert to SymbolId via VarSymbolMapping.
21    pub struct VarId;
22}
23
24/// Bidirectional mapping between VarId and SymbolId.
25///
26/// Uses SlotMap for stable VarId generation that supports deletion.
27///
28/// This provides:
29/// - VarId → SymbolId: O(1) via SlotMap
30/// - SymbolId → VarId: O(1) via SecondaryMap
31///
32/// # Usage
33///
34/// ```ignore
35/// let mut mapping = VarSymbolMapping::new();
36///
37/// // Register a variable
38/// let var_id = mapping.register(symbol_id);
39///
40/// // Lookup
41/// let sym = mapping.to_symbol(var_id);
42/// let var = mapping.to_var(symbol_id);
43///
44/// // Remove (for incremental updates)
45/// mapping.remove(var_id);
46/// ```
47#[derive(Debug, Clone, Default)]
48pub struct VarSymbolMapping {
49    /// VarId → `Option<SymbolId>` (forward mapping via SlotMap)
50    /// None for local variables without a SymbolId in the registry.
51    var_to_symbol: SlotMap<VarId, Option<SymbolId>>,
52
53    /// SymbolId → VarId (reverse mapping, only for registered variables)
54    symbol_to_var: SecondaryMap<SymbolId, VarId>,
55}
56
57impl VarSymbolMapping {
58    /// Create a new empty mapping.
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Create with pre-allocated capacity.
64    pub fn with_capacity(capacity: usize) -> Self {
65        Self {
66            var_to_symbol: SlotMap::with_capacity_and_key(capacity),
67            symbol_to_var: SecondaryMap::with_capacity(capacity),
68        }
69    }
70
71    /// Register a named variable (with SymbolId) and return its VarId.
72    ///
73    /// If the SymbolId is already registered, returns the existing VarId.
74    pub fn register(&mut self, symbol_id: SymbolId) -> VarId {
75        // Check if already registered
76        if let Some(&var_id) = self.symbol_to_var.get(symbol_id) {
77            // Verify forward mapping still exists (wasn't removed)
78            if self.var_to_symbol.contains_key(var_id) {
79                return var_id;
80            }
81            // Stale reverse mapping, will be overwritten below
82        }
83
84        // Create new VarId
85        let var_id = self.var_to_symbol.insert(Some(symbol_id));
86        self.symbol_to_var.insert(symbol_id, var_id);
87        var_id
88    }
89
90    /// Allocate a fresh VarId for a local variable without a SymbolId.
91    ///
92    /// Always creates a new unique VarId. No reverse lookup is registered.
93    pub fn allocate(&mut self) -> VarId {
94        self.var_to_symbol.insert(None)
95    }
96
97    /// Remove a variable by VarId.
98    ///
99    /// Returns the associated SymbolId if it existed.
100    pub fn remove(&mut self, var_id: VarId) -> Option<SymbolId> {
101        if let Some(Some(symbol_id)) = self.var_to_symbol.remove(var_id) {
102            self.symbol_to_var.remove(symbol_id);
103            return Some(symbol_id);
104        }
105        None
106    }
107
108    /// Remove a variable by SymbolId.
109    ///
110    /// Returns the VarId if it existed.
111    pub fn remove_by_symbol(&mut self, symbol_id: SymbolId) -> Option<VarId> {
112        if let Some(var_id) = self.symbol_to_var.remove(symbol_id) {
113            self.var_to_symbol.remove(var_id);
114            Some(var_id)
115        } else {
116            None
117        }
118    }
119
120    /// Get the SymbolId for a VarId.
121    ///
122    /// Returns None if the VarId doesn't exist or has no associated SymbolId.
123    #[inline]
124    pub fn to_symbol(&self, var_id: VarId) -> Option<SymbolId> {
125        self.var_to_symbol.get(var_id).copied().flatten()
126    }
127
128    /// Get the VarId for a SymbolId.
129    ///
130    /// Returns None if the SymbolId is not registered.
131    #[inline]
132    pub fn to_var(&self, symbol_id: SymbolId) -> Option<VarId> {
133        self.symbol_to_var.get(symbol_id).copied()
134    }
135
136    /// Check if a SymbolId is registered.
137    #[inline]
138    pub fn contains_symbol(&self, symbol_id: SymbolId) -> bool {
139        self.symbol_to_var.contains_key(symbol_id)
140    }
141
142    /// Check if a VarId is valid.
143    #[inline]
144    pub fn contains_var(&self, var_id: VarId) -> bool {
145        self.var_to_symbol.contains_key(var_id)
146    }
147
148    /// Get the number of registered variables.
149    #[inline]
150    pub fn len(&self) -> usize {
151        self.var_to_symbol.len()
152    }
153
154    /// Check if empty.
155    #[inline]
156    pub fn is_empty(&self) -> bool {
157        self.var_to_symbol.is_empty()
158    }
159
160    /// Iterate over all `(VarId, Option<SymbolId>)` pairs.
161    pub fn iter(&self) -> impl Iterator<Item = (VarId, Option<SymbolId>)> + '_ {
162        self.var_to_symbol.iter().map(|(id, &sym)| (id, sym))
163    }
164
165    /// Clear all mappings.
166    pub fn clear(&mut self) {
167        self.var_to_symbol.clear();
168        self.symbol_to_var.clear();
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use slotmap::SlotMap;
176
177    #[test]
178    fn test_var_symbol_mapping_register() {
179        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
180        let sym1 = symbols.insert("x");
181        let sym2 = symbols.insert("y");
182
183        let mut mapping = VarSymbolMapping::new();
184
185        let var1 = mapping.register(sym1);
186        let var2 = mapping.register(sym2);
187
188        assert_ne!(var1, var2);
189        assert_eq!(mapping.len(), 2);
190    }
191
192    #[test]
193    fn test_var_symbol_mapping_idempotent() {
194        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
195        let sym = symbols.insert("x");
196
197        let mut mapping = VarSymbolMapping::new();
198
199        let var1 = mapping.register(sym);
200        let var2 = mapping.register(sym); // Same symbol again
201
202        assert_eq!(var1, var2);
203        assert_eq!(mapping.len(), 1);
204    }
205
206    #[test]
207    fn test_var_symbol_mapping_bidirectional() {
208        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
209        let sym = symbols.insert("x");
210
211        let mut mapping = VarSymbolMapping::new();
212        let var = mapping.register(sym);
213
214        // Forward lookup
215        assert_eq!(mapping.to_symbol(var), Some(sym));
216
217        // Reverse lookup
218        assert_eq!(mapping.to_var(sym), Some(var));
219    }
220
221    #[test]
222    fn test_var_symbol_mapping_contains() {
223        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
224        let sym1 = symbols.insert("x");
225        let sym2 = symbols.insert("y");
226
227        let mut mapping = VarSymbolMapping::new();
228        let var = mapping.register(sym1);
229
230        assert!(mapping.contains_symbol(sym1));
231        assert!(!mapping.contains_symbol(sym2));
232        assert!(mapping.contains_var(var));
233    }
234
235    #[test]
236    fn test_var_symbol_mapping_remove() {
237        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
238        let sym = symbols.insert("x");
239
240        let mut mapping = VarSymbolMapping::new();
241        let var = mapping.register(sym);
242
243        assert_eq!(mapping.len(), 1);
244        assert!(mapping.contains_var(var));
245
246        // Remove by VarId
247        let removed = mapping.remove(var);
248        assert_eq!(removed, Some(sym));
249        assert_eq!(mapping.len(), 0);
250        assert!(!mapping.contains_var(var));
251        assert!(!mapping.contains_symbol(sym));
252    }
253
254    #[test]
255    fn test_var_symbol_mapping_remove_by_symbol() {
256        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
257        let sym = symbols.insert("x");
258
259        let mut mapping = VarSymbolMapping::new();
260        let var = mapping.register(sym);
261
262        // Remove by SymbolId
263        let removed = mapping.remove_by_symbol(sym);
264        assert_eq!(removed, Some(var));
265        assert_eq!(mapping.len(), 0);
266    }
267
268    #[test]
269    fn test_var_symbol_mapping_iter() {
270        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
271        let sym1 = symbols.insert("x");
272        let sym2 = symbols.insert("y");
273
274        let mut mapping = VarSymbolMapping::new();
275        mapping.register(sym1);
276        mapping.register(sym2);
277
278        let pairs: Vec<_> = mapping.iter().collect();
279        assert_eq!(pairs.len(), 2);
280    }
281
282    #[test]
283    fn test_var_symbol_mapping_clear() {
284        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
285        let sym = symbols.insert("x");
286
287        let mut mapping = VarSymbolMapping::new();
288        mapping.register(sym);
289        assert!(!mapping.is_empty());
290
291        mapping.clear();
292        assert!(mapping.is_empty());
293        assert!(!mapping.contains_symbol(sym));
294    }
295
296    #[test]
297    fn test_var_symbol_mapping_reregister_after_remove() {
298        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
299        let sym = symbols.insert("x");
300
301        let mut mapping = VarSymbolMapping::new();
302        let var1 = mapping.register(sym);
303        mapping.remove(var1);
304
305        // Re-register same symbol
306        let var2 = mapping.register(sym);
307        assert_ne!(var1, var2); // Should get a new VarId
308        assert_eq!(mapping.len(), 1);
309        assert!(mapping.contains_var(var2));
310    }
311
312    #[test]
313    fn test_allocate_produces_distinct_ids() {
314        let mut mapping = VarSymbolMapping::new();
315
316        let v1 = mapping.allocate();
317        let v2 = mapping.allocate();
318        let v3 = mapping.allocate();
319
320        assert_ne!(v1, v2);
321        assert_ne!(v2, v3);
322        assert_ne!(v1, v3);
323        assert_eq!(mapping.len(), 3);
324
325        // Allocated vars have no SymbolId
326        assert_eq!(mapping.to_symbol(v1), None);
327        assert_eq!(mapping.to_symbol(v2), None);
328    }
329
330    #[test]
331    fn test_allocate_and_register_coexist() {
332        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
333        let sym = symbols.insert("x");
334
335        let mut mapping = VarSymbolMapping::new();
336        let anon = mapping.allocate();
337        let named = mapping.register(sym);
338
339        assert_ne!(anon, named);
340        assert_eq!(mapping.len(), 2);
341        assert_eq!(mapping.to_symbol(anon), None);
342        assert_eq!(mapping.to_symbol(named), Some(sym));
343        assert_eq!(mapping.to_var(sym), Some(named));
344    }
345
346    #[test]
347    fn test_remove_allocated_var() {
348        let mut mapping = VarSymbolMapping::new();
349        let v = mapping.allocate();
350        assert_eq!(mapping.len(), 1);
351
352        let removed = mapping.remove(v);
353        assert_eq!(removed, None); // No SymbolId associated
354        assert_eq!(mapping.len(), 0);
355        assert!(!mapping.contains_var(v));
356    }
357}