Skip to main content

ryo_analysis/query/
dataflow_v2.rs

1//! DataFlowGraphV2 - Data-Oriented Design implementation of DataFlowGraph.
2//!
3//! Key improvements over V1:
4//! - **String-free**: Uses SymbolId instead of String for variable names
5//! - **SoA (Structure of Arrays)**: Cache-efficient data layout
6//! - **Arena-based indexing**: No petgraph dependency
7//! - **Inverted index**: O(1) symbol → variables lookup
8
9use super::var_id::{VarId, VarSymbolMapping};
10use crate::define_index;
11use crate::symbol::{SymbolId, SymbolRegistry};
12use serde::{Deserialize, Serialize};
13use slotmap::SecondaryMap;
14use smallvec::SmallVec;
15use std::collections::{HashMap, HashSet};
16
17// Import V2 trackers
18use super::borrow_v2::{
19    ActiveBorrowV2, BorrowAnalysis, BorrowConflict, BorrowKind, BorrowTrackerV2, MoveError,
20};
21use super::lock_v2::LockTrackerV2;
22
23// ============================================================
24// Shared Vocabulary Types
25// ============================================================
26
27/// Kind of data flow.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29pub enum FlowKind {
30    // === Value Flow ===
31    /// Value is assigned: `target = source`.
32    Assign,
33    /// Value is derived/computed: `target = f(source)`.
34    Derive,
35    /// Value is read from source.
36    Read,
37    /// Value is written to target.
38    Write,
39    /// Value is returned from function.
40    Return,
41    /// Value is passed as argument.
42    Argument,
43    /// Value flows through a reference (legacy, prefer SharedBorrow/MutBorrow).
44    Reference,
45    /// Value flows through a dereference.
46    Dereference,
47    /// Conditional flow (if/match guard).
48    Conditional,
49
50    // === Ownership/Borrow Flow (Rust-specific) ===
51    /// Shared borrow: `&source`.
52    SharedBorrow,
53    /// Mutable borrow: `&mut source`.
54    MutBorrow,
55    /// Ownership transfer (move): `target = source` where source is not Copy.
56    Move,
57    /// Clone operation: `target = source.clone()`.
58    Clone,
59    /// Copy operation: `target = source` where source is Copy.
60    Copy,
61    /// Drop/scope end: value goes out of scope.
62    Drop,
63    /// Reborrow: borrowing from an existing reference.
64    Reborrow,
65    /// Index operation: `collection[index]`.
66    Index,
67}
68
69impl FlowKind {
70    /// Check if this flow kind transfers ownership.
71    pub fn transfers_ownership(&self) -> bool {
72        matches!(self, FlowKind::Move | FlowKind::Return)
73    }
74
75    /// Check if this flow kind creates a borrow.
76    pub fn is_borrow(&self) -> bool {
77        matches!(
78            self,
79            FlowKind::SharedBorrow | FlowKind::MutBorrow | FlowKind::Reborrow
80        )
81    }
82
83    /// Check if this flow kind is a mutable operation.
84    pub fn is_mutable(&self) -> bool {
85        matches!(
86            self,
87            FlowKind::MutBorrow | FlowKind::Write | FlowKind::Assign
88        )
89    }
90}
91
92/// Kind of variable.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
94pub enum VarKind {
95    /// Local variable.
96    Local,
97    /// Function parameter.
98    Parameter,
99    /// Struct field.
100    Field,
101    /// Static or const.
102    Static,
103    /// Return value.
104    Return,
105    /// Temporary/intermediate value.
106    Temp,
107}
108
109/// Edge in the data flow graph with detailed information.
110#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
111pub struct FlowEdge {
112    /// Flow kind.
113    pub kind: FlowKind,
114    /// Line number where this flow occurs (0 if unknown).
115    pub line: u32,
116}
117
118/// Variable node with name and metadata.
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct VarNode {
121    /// Variable name.
122    pub name: String,
123    /// Variable kind.
124    pub kind: VarKind,
125    /// Line number where defined (0 if unknown).
126    pub line: u32,
127}
128
129/// Statistics about the data flow graph.
130#[derive(Debug, Clone, Default, Serialize, Deserialize)]
131pub struct DataFlowStats {
132    /// Total number of variables.
133    pub var_count: usize,
134    /// Total number of flow edges.
135    pub flow_count: usize,
136    /// Variables by kind.
137    pub vars_by_kind: HashMap<String, usize>,
138    /// Flows by kind.
139    pub flows_by_kind: HashMap<String, usize>,
140}
141
142/// A chain of flow steps from source to target.
143#[derive(Debug, Clone)]
144pub struct FlowChain {
145    /// Steps in the chain (variable indices with flow kinds).
146    pub steps: Vec<FlowStep>,
147}
148
149/// A single step in a flow chain.
150#[derive(Debug, Clone)]
151pub struct FlowStep {
152    /// Variable index at this step.
153    pub var_id: VarId,
154    /// Flow kind to next step (None for last step).
155    pub flow_kind: Option<FlowKind>,
156}
157
158// ============================================================
159// Index Types
160// ============================================================
161
162define_index! {
163    /// Index into the flows array.
164    pub struct FlowId;
165}
166
167define_index! {
168    /// Index into the scopes array.
169    pub struct ScopeId;
170}
171
172// ============================================================
173// Scope & Guard Types
174// ============================================================
175
176/// Kind of scope in control flow.
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
178pub enum ScopeKind {
179    /// Function / method body (root scope).
180    Function,
181    /// `if cond { ← here }`
182    IfThen,
183    /// `if cond { } else { ← here }`
184    IfElse,
185    /// `while cond { ← here }`
186    WhileBody,
187    /// `for pat in iter { ← here }`
188    ForBody,
189    /// `loop { ← here }`
190    LoopBody,
191    /// `match expr { pat => { ← here } }`
192    MatchArm,
193    /// Plain block `{ ← here }`
194    Block,
195    /// Closure `|x| { ← here }`
196    Closure,
197}
198
199/// A scope node in the scope tree.
200#[derive(Debug, Clone)]
201pub struct ScopeData {
202    /// Parent scope (None for root / Function scope).
203    pub parent: Option<ScopeId>,
204    /// What kind of scope this is.
205    pub kind: ScopeKind,
206    /// Guard constraint imposed by the enclosing condition (if any).
207    pub guard: Option<Guard>,
208}
209
210/// A conditional guard that constrains variables within a scope.
211#[derive(Debug, Clone)]
212pub struct Guard {
213    /// What kind of constraint.
214    pub kind: GuardKind,
215    /// Names of constrained variables (e.g., "i", "idx").
216    pub var_names: SmallVec<[String; 2]>,
217}
218
219/// Kind of guard condition.
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
221pub enum GuardKind {
222    /// `left < right` (e.g., `i < arr.len()`)
223    LessThan,
224    /// `left <= right`
225    LessEqual,
226    /// `left > right`
227    GreaterThan,
228    /// `left >= right`
229    GreaterEqual,
230    /// `if let Some(x) = expr`
231    LetSome,
232    /// `if let Ok(x) = expr`
233    LetOk,
234    /// `expr.is_some()`
235    IsSome,
236    /// `expr.is_ok()`
237    IsOk,
238    /// Non-empty check (e.g., `!v.is_empty()`)
239    NonEmpty,
240}
241
242// ============================================================
243// Data Structures (SoA - Structure of Arrays)
244// ============================================================
245
246/// Variable data (12 bytes) - DOD optimized.
247///
248/// Each variable is identified by its SymbolId (registered via `SymbolRegistry::register_var`).
249/// The parent symbol and variable name can be retrieved via the SymbolRegistry.
250///
251/// # DOD Design
252///
253/// - `parent`: Reference to containing symbol (function/method/struct)
254/// - `kind`: Variable classification
255/// - `line`: Source location for diagnostics
256/// - `is_mut`: Whether binding was declared `let mut` (set by builder from
257///   `PurePattern::Ident { is_mut }`, used by UnnecessaryClone to suppress FPs
258///   on mutable working copies)
259///
260/// Variable's SymbolId is stored in `VarSymbolMapping` (VarId ↔ SymbolId).
261#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
262pub struct VarData {
263    /// Parent symbol (function, method, struct, etc.) that contains this variable.
264    pub parent: SymbolId,
265    /// Variable kind (parameter, local, temp, field, etc.).
266    pub kind: VarKind,
267    /// Line number where defined (0 = unknown).
268    pub line: u32,
269    /// Whether the binding is declared `mut` (e.g. `let mut x = ...`).
270    pub is_mut: bool,
271}
272
273/// Flow edge data (8 bytes).
274///
275/// Represents a directed edge from one variable to another.
276#[derive(Clone, Copy, Debug, PartialEq, Eq)]
277pub struct FlowEdgeData {
278    /// Source variable index.
279    pub from: VarId,
280    /// Target variable index.
281    pub to: VarId,
282}
283
284/// Flow metadata.
285///
286/// Additional information about a flow edge.
287#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
288pub struct FlowData {
289    /// Flow kind.
290    pub kind: FlowKind,
291    /// Line number where this flow occurs (0 = unknown).
292    pub line: u32,
293    /// Scope in which this flow occurs.
294    pub scope: ScopeId,
295}
296
297// ============================================================
298// DataFlowGraphV2
299// ============================================================
300
301/// Data flow graph with Data-Oriented Design.
302///
303/// # Design Principles
304///
305/// 1. **SoA Layout**: Variables and flows stored in separate arrays
306/// 2. **String-free**: Variable names are SymbolIds
307/// 3. **Inverted Index**: O(1) lookup from containing symbol to variables
308/// 4. **Cache-friendly**: Dense arrays for iteration
309/// 5. **Integrated Trackers**: BorrowTrackerV2 and LockTrackerV2 built-in
310///
311/// # Memory Layout
312///
313/// ```text
314/// DataFlowGraphV2
315/// ├── vars: SecondaryMap<VarId, VarData>   // Variable metadata
316/// ├── var_names: SecondaryMap<VarId, String>
317/// ├── flows: Vec<FlowData>                 // Flow metadata (with scope)
318/// ├── edges: Vec<FlowEdgeData>             // Flow edges (parallel to flows)
319/// ├── scopes: Vec<ScopeData>               // Scope tree
320/// ├── Adjacency Lists
321/// │   ├── outgoing / incoming
322/// ├── Lookup Tables
323/// │   ├── var_mapping: VarSymbolMapping
324/// │   └── symbol_to_vars
325/// └── Trackers
326///     ├── borrow_tracker
327///     └── lock_tracker
328/// ```
329#[derive(Debug, Clone, Default)]
330pub struct DataFlowGraphV2 {
331    // === Data Storage (SecondaryMap for stable VarId keys) ===
332    /// Variable data (keyed by VarId from var_mapping).
333    vars: SecondaryMap<VarId, VarData>,
334
335    /// Variable names (keyed by VarId).
336    var_names: SecondaryMap<VarId, String>,
337
338    /// Flow metadata array.
339    flows: Vec<FlowData>,
340
341    /// Flow edge array (parallel to flows).
342    edges: Vec<FlowEdgeData>,
343
344    // === Scope Tree ===
345    /// Scope hierarchy (control flow scopes with guards).
346    scopes: Vec<ScopeData>,
347
348    // === Adjacency Lists (SecondaryMap for stable VarId keys) ===
349    /// Outgoing flows for each variable.
350    outgoing: SecondaryMap<VarId, SmallVec<[FlowId; 4]>>,
351
352    /// Incoming flows for each variable.
353    incoming: SecondaryMap<VarId, SmallVec<[FlowId; 4]>>,
354
355    // === Lookup Tables ===
356    /// VarId ↔ SymbolId mapping (SlotMap-based, supports deletion).
357    var_mapping: VarSymbolMapping,
358
359    /// Containing symbol → variable list.
360    symbol_to_vars: HashMap<SymbolId, SmallVec<[VarId; 8]>>,
361
362    // === Trackers ===
363    /// Borrow state tracking.
364    borrow_tracker: BorrowTrackerV2,
365
366    /// Lock acquisition tracking.
367    lock_tracker: LockTrackerV2,
368}
369
370impl DataFlowGraphV2 {
371    /// Create a new empty graph.
372    pub fn new() -> Self {
373        Self::default()
374    }
375
376    /// Create with pre-allocated capacity.
377    pub fn with_capacity(var_capacity: usize, flow_capacity: usize) -> Self {
378        Self {
379            vars: SecondaryMap::new(),
380            var_names: SecondaryMap::new(),
381            flows: Vec::with_capacity(flow_capacity),
382            edges: Vec::with_capacity(flow_capacity),
383            scopes: Vec::new(),
384            outgoing: SecondaryMap::new(),
385            incoming: SecondaryMap::new(),
386            var_mapping: VarSymbolMapping::with_capacity(var_capacity),
387            symbol_to_vars: HashMap::with_capacity(var_capacity / 4),
388            borrow_tracker: BorrowTrackerV2::new(),
389            lock_tracker: LockTrackerV2::new(),
390        }
391    }
392
393    // ========== Variable Operations ==========
394
395    /// Add a variable to the graph.
396    ///
397    /// # Arguments
398    ///
399    /// - `data`: Variable metadata (parent, kind, line)
400    /// - `name`: Variable name (for display/debugging)
401    /// - `var_symbol_id`: The variable's SymbolId if registered in SymbolRegistry,
402    ///   or None for local variables not in the registry.
403    ///
404    /// # Returns
405    ///
406    /// The VarId for referencing this variable in the dataflow graph.
407    pub fn add_var(
408        &mut self,
409        data: VarData,
410        name: String,
411        var_symbol_id: Option<SymbolId>,
412    ) -> VarId {
413        let var_id = match var_symbol_id {
414            Some(sid) => self.var_mapping.register(sid),
415            None => self.var_mapping.allocate(),
416        };
417
418        // Insert into SecondaryMaps (keyed by VarId)
419        self.vars.insert(var_id, data);
420        self.var_names.insert(var_id, name);
421        self.outgoing.insert(var_id, SmallVec::new());
422        self.incoming.insert(var_id, SmallVec::new());
423
424        // Update symbol_to_vars index (group by parent symbol)
425        self.symbol_to_vars
426            .entry(data.parent)
427            .or_default()
428            .push(var_id);
429
430        var_id
431    }
432
433    /// Get variable data by VarId.
434    #[inline]
435    pub fn var(&self, id: VarId) -> Option<&VarData> {
436        self.vars.get(id)
437    }
438
439    /// Get variable name by VarId.
440    #[inline]
441    pub fn var_name(&self, id: VarId) -> Option<&str> {
442        self.var_names.get(id).map(|s| s.as_str())
443    }
444
445    /// Get mutable variable data by VarId.
446    #[inline]
447    pub fn var_mut(&mut self, id: VarId) -> Option<&mut VarData> {
448        self.vars.get_mut(id)
449    }
450
451    /// Get all variables for a containing symbol.
452    #[inline]
453    pub fn vars_in_symbol(&self, symbol: SymbolId) -> &[VarId] {
454        self.symbol_to_vars
455            .get(&symbol)
456            .map(|v| v.as_slice())
457            .unwrap_or(&[])
458    }
459
460    /// Get the number of variables.
461    #[inline]
462    pub fn var_count(&self) -> usize {
463        self.vars.len()
464    }
465
466    // ========== Flow Operations ==========
467
468    /// Add a flow edge between two variables.
469    ///
470    /// Returns the FlowId for the new flow.
471    pub fn add_flow(&mut self, from: VarId, to: VarId, data: FlowData) -> FlowId {
472        let flow_id = FlowId::from_raw(self.flows.len() as u32);
473
474        self.flows.push(data);
475        self.edges.push(FlowEdgeData { from, to });
476
477        // Update adjacency lists (SecondaryMap - direct key access)
478        if let Some(out) = self.outgoing.get_mut(from) {
479            out.push(flow_id);
480        }
481        if let Some(inc) = self.incoming.get_mut(to) {
482            inc.push(flow_id);
483        }
484
485        flow_id
486    }
487
488    /// Get flow data by FlowId.
489    #[inline]
490    pub fn flow(&self, id: FlowId) -> Option<&FlowData> {
491        self.flows.get(id.as_usize())
492    }
493
494    /// Get flow edge by FlowId.
495    #[inline]
496    pub fn edge(&self, id: FlowId) -> Option<&FlowEdgeData> {
497        self.edges.get(id.as_usize())
498    }
499
500    /// Get the number of flows.
501    #[inline]
502    pub fn flow_count(&self) -> usize {
503        self.flows.len()
504    }
505
506    // ========== Graph Traversal ==========
507
508    /// Get outgoing flows from a variable.
509    #[inline]
510    pub fn outgoing(&self, var: VarId) -> &[FlowId] {
511        self.outgoing.get(var).map(|v| v.as_slice()).unwrap_or(&[])
512    }
513
514    /// Get incoming flows to a variable.
515    #[inline]
516    pub fn incoming(&self, var: VarId) -> &[FlowId] {
517        self.incoming.get(var).map(|v| v.as_slice()).unwrap_or(&[])
518    }
519
520    /// Get target variables that receive values from this variable.
521    pub fn successors(&self, var: VarId) -> impl Iterator<Item = VarId> + '_ {
522        self.outgoing(var)
523            .iter()
524            .filter_map(|&flow_id| self.edges.get(flow_id.as_usize()).map(|e| e.to))
525    }
526
527    /// Get source variables that provide values to this variable.
528    pub fn predecessors(&self, var: VarId) -> impl Iterator<Item = VarId> + '_ {
529        self.incoming(var)
530            .iter()
531            .filter_map(|&flow_id| self.edges.get(flow_id.as_usize()).map(|e| e.from))
532    }
533
534    // ========== Analysis Helpers ==========
535
536    /// Find all variables that this variable's value flows to (transitive).
537    ///
538    /// Impact analysis: "If I change this variable, what else is affected?"
539    pub fn impact(&self, start: VarId) -> Vec<VarId> {
540        let mut visited: HashSet<VarId> = HashSet::new();
541        let mut result = Vec::new();
542        let mut stack = vec![start];
543
544        while let Some(var) = stack.pop() {
545            if visited.contains(&var) {
546                continue;
547            }
548            visited.insert(var);
549            result.push(var);
550
551            for next in self.successors(var) {
552                if !visited.contains(&next) {
553                    stack.push(next);
554                }
555            }
556        }
557
558        result
559    }
560
561    /// Find all variables that contribute to this variable's value (transitive).
562    ///
563    /// Provenance analysis: "Where does this value come from?"
564    pub fn provenance(&self, start: VarId) -> Vec<VarId> {
565        let mut visited: HashSet<VarId> = HashSet::new();
566        let mut result = Vec::new();
567        let mut stack = vec![start];
568
569        while let Some(var) = stack.pop() {
570            if visited.contains(&var) {
571                continue;
572            }
573            visited.insert(var);
574            result.push(var);
575
576            for prev in self.predecessors(var) {
577                if !visited.contains(&prev) {
578                    stack.push(prev);
579                }
580            }
581        }
582
583        result
584    }
585
586    // ========== Lookup ==========
587
588    /// Convert VarId to SymbolId.
589    #[inline]
590    pub fn var_to_symbol(&self, var: VarId) -> Option<SymbolId> {
591        self.var_mapping.to_symbol(var)
592    }
593
594    /// Convert SymbolId to VarId.
595    #[inline]
596    pub fn symbol_to_var(&self, symbol: SymbolId) -> Option<VarId> {
597        self.var_mapping.to_var(symbol)
598    }
599
600    /// Iterate over all variables.
601    pub fn iter_vars(&self) -> impl Iterator<Item = (VarId, &VarData)> {
602        self.vars.iter()
603    }
604
605    /// Resolve variables by SymbolId or name with tiered fallback.
606    ///
607    /// Resolution order:
608    /// 1. `symbol_id` → O(1) lookup via `vars_in_symbol`
609    /// 2. `name` → registry name lookup → `vars_in_symbol` for each match
610    /// 3. `name` → fallback to dataflow variable name substring search
611    ///
612    /// Returns `(matching_vars, display_name)` or `None` if neither input is provided.
613    pub fn resolve_vars(
614        &self,
615        symbol_id: Option<SymbolId>,
616        name: Option<&str>,
617        registry: &SymbolRegistry,
618    ) -> Option<(Vec<VarId>, String)> {
619        if let Some(sid) = symbol_id {
620            let vars = self.vars_in_symbol(sid).to_vec();
621            let display = registry
622                .resolve(sid)
623                .map(|p| p.name().to_string())
624                .unwrap_or_else(|| format!("{:?}", sid));
625            return Some((vars, display));
626        }
627
628        let name = name?;
629
630        // Step 1: Registry name → SymbolId → vars_in_symbol (O(1) per symbol)
631        let symbol_ids = registry.find_by_name(name);
632        let mut vars = Vec::new();
633        for &sid in &symbol_ids {
634            vars.extend_from_slice(self.vars_in_symbol(sid));
635        }
636        if !vars.is_empty() {
637            return Some((vars, name.to_string()));
638        }
639
640        // Step 2: Fallback to dataflow variable name substring search
641        let vars: Vec<VarId> = self
642            .iter_vars()
643            .filter_map(|(var_id, _)| {
644                self.var_name(var_id).and_then(|n| {
645                    if n == name || n.contains(name) {
646                        Some(var_id)
647                    } else {
648                        None
649                    }
650                })
651            })
652            .collect();
653        Some((vars, name.to_string()))
654    }
655
656    /// Iterate over all flows.
657    pub fn iter_flows(&self) -> impl Iterator<Item = (FlowId, &FlowData, &FlowEdgeData)> {
658        self.flows
659            .iter()
660            .zip(self.edges.iter())
661            .enumerate()
662            .map(|(i, (data, edge))| (FlowId::from_raw(i as u32), data, edge))
663    }
664
665    /// Clear all data.
666    pub fn clear(&mut self) {
667        self.vars.clear();
668        self.var_names.clear();
669        self.flows.clear();
670        self.edges.clear();
671        self.scopes.clear();
672        self.outgoing.clear();
673        self.incoming.clear();
674        self.var_mapping.clear();
675        self.symbol_to_vars.clear();
676        self.borrow_tracker.clear();
677        self.lock_tracker.clear();
678    }
679
680    /// Clear variables and flows for the given parent symbols.
681    ///
682    /// Used for incremental updates: clear data for modified functions,
683    /// then rebuild from the updated AST.
684    ///
685    /// Note: This doesn't compact the flow arrays (they may contain stale edges
686    /// referencing removed VarIds). The edges are effectively orphaned and ignored
687    /// since the VarIds no longer exist in var_mapping.
688    pub fn clear_for_symbols(&mut self, symbols: &[SymbolId]) {
689        for &symbol_id in symbols {
690            // Get and remove the list of VarIds for this symbol
691            if let Some(var_ids) = self.symbol_to_vars.remove(&symbol_id) {
692                for var_id in var_ids {
693                    // Remove variable data
694                    self.vars.remove(var_id);
695                    self.var_names.remove(var_id);
696                    self.outgoing.remove(var_id);
697                    self.incoming.remove(var_id);
698
699                    // Remove from var_mapping (bidirectional)
700                    self.var_mapping.remove(var_id);
701                }
702            }
703        }
704
705        // Note: We don't compact flows/edges arrays here.
706        // Stale edges referencing removed VarIds are orphaned but harmless.
707        // A full rebuild or periodic compaction can clean them up if needed.
708    }
709
710    // ========== Scope Operations ==========
711
712    /// Add a scope to the scope tree. Returns the ScopeId.
713    pub fn add_scope(&mut self, data: ScopeData) -> ScopeId {
714        let id = ScopeId::from_raw(self.scopes.len() as u32);
715        self.scopes.push(data);
716        id
717    }
718
719    /// Get scope data by ScopeId.
720    #[inline]
721    pub fn scope(&self, id: ScopeId) -> Option<&ScopeData> {
722        self.scopes.get(id.as_usize())
723    }
724
725    /// Get the number of scopes.
726    #[inline]
727    pub fn scope_count(&self) -> usize {
728        self.scopes.len()
729    }
730
731    // ========== Guard Queries ==========
732
733    /// Check if a variable (by name) has a matching guard in the given scope or any ancestor.
734    ///
735    /// Walks up the scope tree from `scope` looking for a guard whose
736    /// `kind` is in `kinds` and whose `var_names` contains `var_name`.
737    pub fn is_guarded(&self, var_name: &str, scope: ScopeId, kinds: &[GuardKind]) -> bool {
738        let mut current = Some(scope);
739        while let Some(sid) = current {
740            if let Some(scope_data) = self.scopes.get(sid.as_usize()) {
741                if let Some(guard) = &scope_data.guard {
742                    if kinds.contains(&guard.kind) && guard.var_names.iter().any(|n| n == var_name)
743                    {
744                        return true;
745                    }
746                }
747                current = scope_data.parent;
748            } else {
749                break;
750            }
751        }
752        false
753    }
754
755    /// Collect all guards active in the scope chain (from scope up to root).
756    pub fn active_guards(&self, scope: ScopeId) -> Vec<&Guard> {
757        let mut guards = Vec::new();
758        let mut current = Some(scope);
759        while let Some(sid) = current {
760            if let Some(scope_data) = self.scopes.get(sid.as_usize()) {
761                if let Some(guard) = &scope_data.guard {
762                    guards.push(guard);
763                }
764                current = scope_data.parent;
765            } else {
766                break;
767            }
768        }
769        guards
770    }
771
772    /// Find all flows of a given kind within a symbol.
773    pub fn flows_of_kind_in_symbol(
774        &self,
775        symbol_id: SymbolId,
776        kind: FlowKind,
777    ) -> Vec<(FlowId, &FlowData, &FlowEdgeData)> {
778        let var_ids = self.vars_in_symbol(symbol_id);
779        let mut result = Vec::new();
780        for &var_id in var_ids {
781            for &flow_id in self.outgoing(var_id) {
782                if let (Some(flow), Some(edge)) = (self.flow(flow_id), self.edge(flow_id)) {
783                    if flow.kind == kind {
784                        result.push((flow_id, flow, edge));
785                    }
786                }
787            }
788        }
789        result
790    }
791
792    // ========== Tracker Access ==========
793
794    /// Get the borrow tracker.
795    #[inline]
796    pub fn borrow_tracker(&self) -> &BorrowTrackerV2 {
797        &self.borrow_tracker
798    }
799
800    /// Get mutable access to the borrow tracker.
801    #[inline]
802    pub fn borrow_tracker_mut(&mut self) -> &mut BorrowTrackerV2 {
803        &mut self.borrow_tracker
804    }
805
806    /// Get the lock tracker.
807    #[inline]
808    pub fn lock_tracker(&self) -> &LockTrackerV2 {
809        &self.lock_tracker
810    }
811
812    /// Get mutable access to the lock tracker.
813    #[inline]
814    pub fn lock_tracker_mut(&mut self) -> &mut LockTrackerV2 {
815        &mut self.lock_tracker
816    }
817
818    // ========== Borrow Tracking Convenience Methods ==========
819
820    /// Add a borrow relationship.
821    pub fn add_borrow(&mut self, source: VarId, borrow: VarId, kind: BorrowKind, line: u32) {
822        self.borrow_tracker.add_borrow(source, borrow, kind, line);
823    }
824
825    /// End a borrow at a specific line.
826    pub fn end_borrow(&mut self, borrow: VarId, line: u32) {
827        self.borrow_tracker.end_borrow(borrow, line);
828    }
829
830    /// Query borrow conflicts for a variable.
831    pub fn borrow_conflicts(
832        &self,
833        source: VarId,
834        kind: BorrowKind,
835        at_line: u32,
836    ) -> Vec<BorrowConflict> {
837        self.borrow_tracker.conflicts(source, kind, at_line)
838    }
839
840    // ========== Borrow Analysis ==========
841
842    /// Analyze borrow conflicts and use-after-move errors for a symbol.
843    ///
844    /// Scans all flows within the symbol to detect:
845    /// - Simultaneous mutable borrows
846    /// - Mutable + shared borrow conflicts
847    /// - Use after move
848    pub fn borrow_analysis(&self, symbol_id: SymbolId) -> BorrowAnalysis {
849        let mut conflicts = Vec::new();
850        let mut move_errors = Vec::new();
851
852        let var_ids = self.vars_in_symbol(symbol_id);
853
854        // Track active borrows and moves for each variable
855        let mut active_borrows: HashMap<VarId, Vec<(VarId, BorrowKind, u32)>> = HashMap::new();
856        let mut moved_vars: HashMap<VarId, u32> = HashMap::new();
857
858        for &var_id in var_ids {
859            for &flow_id in self.outgoing(var_id) {
860                if let (Some(flow), Some(edge)) = (self.flow(flow_id), self.edge(flow_id)) {
861                    match flow.kind {
862                        FlowKind::SharedBorrow => {
863                            let source = edge.from;
864                            let borrower = edge.to;
865                            if let Some(borrows) = active_borrows.get(&source) {
866                                for &(existing_borrower, existing_kind, existing_line) in borrows {
867                                    if existing_kind.conflicts_with(BorrowKind::Shared) {
868                                        conflicts.push(BorrowConflict::new(
869                                            source,
870                                            ActiveBorrowV2::new(
871                                                existing_borrower,
872                                                existing_kind,
873                                                existing_line,
874                                            ),
875                                            BorrowKind::Shared,
876                                            flow.line,
877                                        ));
878                                    }
879                                }
880                            }
881                            active_borrows.entry(source).or_default().push((
882                                borrower,
883                                BorrowKind::Shared,
884                                flow.line,
885                            ));
886                        }
887                        FlowKind::MutBorrow => {
888                            let source = edge.from;
889                            let borrower = edge.to;
890                            if let Some(borrows) = active_borrows.get(&source) {
891                                for &(existing_borrower, existing_kind, existing_line) in borrows {
892                                    conflicts.push(BorrowConflict::new(
893                                        source,
894                                        ActiveBorrowV2::new(
895                                            existing_borrower,
896                                            existing_kind,
897                                            existing_line,
898                                        ),
899                                        BorrowKind::Mutable,
900                                        flow.line,
901                                    ));
902                                }
903                            }
904                            active_borrows.entry(source).or_default().push((
905                                borrower,
906                                BorrowKind::Mutable,
907                                flow.line,
908                            ));
909                        }
910                        FlowKind::Move => {
911                            let source = edge.from;
912                            moved_vars.insert(source, flow.line);
913                        }
914                        FlowKind::Read | FlowKind::Derive => {
915                            let source = edge.from;
916                            if let Some(&move_line) = moved_vars.get(&source) {
917                                if flow.line > move_line {
918                                    move_errors.push(MoveError {
919                                        variable: source,
920                                        moved_at: move_line,
921                                        used_at: flow.line,
922                                    });
923                                }
924                            }
925                        }
926                        FlowKind::Drop => {
927                            let source = edge.from;
928                            active_borrows.remove(&source);
929                        }
930                        _ => {}
931                    }
932                }
933            }
934        }
935
936        BorrowAnalysis {
937            conflicts,
938            move_errors,
939        }
940    }
941}
942
943#[cfg(test)]
944mod tests {
945    use super::*;
946    use slotmap::SlotMap;
947
948    fn setup_symbols() -> (
949        SlotMap<SymbolId, &'static str>,
950        SymbolId,
951        SymbolId,
952        SymbolId,
953    ) {
954        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
955        let fn_sym = symbols.insert("my_fn");
956        let x_sym = symbols.insert("x");
957        let y_sym = symbols.insert("y");
958        (symbols, fn_sym, x_sym, y_sym)
959    }
960
961    #[test]
962    fn test_add_var() {
963        let (_symbols, fn_sym, x_sym, _y_sym) = setup_symbols();
964        let mut graph = DataFlowGraphV2::new();
965
966        let var_id = graph.add_var(
967            VarData {
968                parent: fn_sym,
969                kind: VarKind::Local,
970                line: 10,
971                is_mut: false,
972            },
973            "x".to_string(),
974            Some(x_sym),
975        );
976
977        assert_eq!(graph.var_count(), 1);
978        let data = graph.var(var_id).unwrap();
979        assert_eq!(data.parent, fn_sym);
980        assert_eq!(data.kind, VarKind::Local);
981        assert_eq!(data.line, 10);
982        assert_eq!(graph.var_name(var_id), Some("x"));
983        // var_symbol is now accessed via var_to_symbol()
984        assert_eq!(graph.var_to_symbol(var_id), Some(x_sym));
985    }
986
987    #[test]
988    fn test_add_flow() {
989        let (_symbols, fn_sym, x_sym, y_sym) = setup_symbols();
990        let mut graph = DataFlowGraphV2::new();
991
992        let x = graph.add_var(
993            VarData {
994                parent: fn_sym,
995                kind: VarKind::Local,
996                line: 10,
997                is_mut: false,
998            },
999            "x".to_string(),
1000            Some(x_sym),
1001        );
1002
1003        let y = graph.add_var(
1004            VarData {
1005                parent: fn_sym,
1006                kind: VarKind::Local,
1007                line: 11,
1008                is_mut: false,
1009            },
1010            "y".to_string(),
1011            Some(y_sym),
1012        );
1013
1014        let flow_id = graph.add_flow(
1015            x,
1016            y,
1017            FlowData {
1018                kind: FlowKind::Assign,
1019                line: 11,
1020                scope: ScopeId::from_raw(0),
1021            },
1022        );
1023
1024        assert_eq!(graph.flow_count(), 1);
1025
1026        let edge = graph.edge(flow_id).unwrap();
1027        assert_eq!(edge.from, x);
1028        assert_eq!(edge.to, y);
1029
1030        let flow = graph.flow(flow_id).unwrap();
1031        assert_eq!(flow.kind, FlowKind::Assign);
1032    }
1033
1034    #[test]
1035    fn test_successors_predecessors() {
1036        let (_symbols, fn_sym, x_sym, y_sym) = setup_symbols();
1037        let mut graph = DataFlowGraphV2::new();
1038
1039        let x = graph.add_var(
1040            VarData {
1041                parent: fn_sym,
1042                kind: VarKind::Local,
1043                line: 10,
1044                is_mut: false,
1045            },
1046            "x".to_string(),
1047            Some(x_sym),
1048        );
1049
1050        let y = graph.add_var(
1051            VarData {
1052                parent: fn_sym,
1053                kind: VarKind::Local,
1054                line: 11,
1055                is_mut: false,
1056            },
1057            "y".to_string(),
1058            Some(y_sym),
1059        );
1060
1061        graph.add_flow(
1062            x,
1063            y,
1064            FlowData {
1065                kind: FlowKind::Assign,
1066                line: 11,
1067                scope: ScopeId::from_raw(0),
1068            },
1069        );
1070
1071        // x → y
1072        let succs: Vec<_> = graph.successors(x).collect();
1073        assert_eq!(succs, vec![y]);
1074
1075        let preds: Vec<_> = graph.predecessors(y).collect();
1076        assert_eq!(preds, vec![x]);
1077    }
1078
1079    #[test]
1080    fn test_vars_in_symbol() {
1081        let (_symbols, fn_sym, x_sym, y_sym) = setup_symbols();
1082        let mut graph = DataFlowGraphV2::new();
1083
1084        let x = graph.add_var(
1085            VarData {
1086                parent: fn_sym,
1087                kind: VarKind::Local,
1088                line: 10,
1089                is_mut: false,
1090            },
1091            "x".to_string(),
1092            Some(x_sym),
1093        );
1094
1095        let y = graph.add_var(
1096            VarData {
1097                parent: fn_sym,
1098                kind: VarKind::Local,
1099                line: 11,
1100                is_mut: false,
1101            },
1102            "y".to_string(),
1103            Some(y_sym),
1104        );
1105
1106        let vars = graph.vars_in_symbol(fn_sym);
1107        assert_eq!(vars.len(), 2);
1108        assert!(vars.contains(&x));
1109        assert!(vars.contains(&y));
1110    }
1111
1112    #[test]
1113    fn test_impact_analysis() {
1114        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
1115        let fn_sym = symbols.insert("my_fn");
1116        let a_sym = symbols.insert("a");
1117        let b_sym = symbols.insert("b");
1118        let c_sym = symbols.insert("c");
1119
1120        let mut graph = DataFlowGraphV2::new();
1121
1122        let a = graph.add_var(
1123            VarData {
1124                parent: fn_sym,
1125                kind: VarKind::Local,
1126                line: 1,
1127                is_mut: false,
1128            },
1129            "a".to_string(),
1130            Some(a_sym),
1131        );
1132        let b = graph.add_var(
1133            VarData {
1134                parent: fn_sym,
1135                kind: VarKind::Local,
1136                line: 2,
1137                is_mut: false,
1138            },
1139            "b".to_string(),
1140            Some(b_sym),
1141        );
1142        let c = graph.add_var(
1143            VarData {
1144                parent: fn_sym,
1145                kind: VarKind::Local,
1146                line: 3,
1147                is_mut: false,
1148            },
1149            "c".to_string(),
1150            Some(c_sym),
1151        );
1152
1153        // a → b → c
1154        graph.add_flow(
1155            a,
1156            b,
1157            FlowData {
1158                kind: FlowKind::Assign,
1159                line: 2,
1160                scope: ScopeId::from_raw(0),
1161            },
1162        );
1163        graph.add_flow(
1164            b,
1165            c,
1166            FlowData {
1167                kind: FlowKind::Assign,
1168                line: 3,
1169                scope: ScopeId::from_raw(0),
1170            },
1171        );
1172
1173        let impact = graph.impact(a);
1174        assert_eq!(impact.len(), 3);
1175        assert!(impact.contains(&a));
1176        assert!(impact.contains(&b));
1177        assert!(impact.contains(&c));
1178    }
1179
1180    #[test]
1181    fn test_provenance_analysis() {
1182        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
1183        let fn_sym = symbols.insert("my_fn");
1184        let a_sym = symbols.insert("a");
1185        let b_sym = symbols.insert("b");
1186        let c_sym = symbols.insert("c");
1187
1188        let mut graph = DataFlowGraphV2::new();
1189
1190        let a = graph.add_var(
1191            VarData {
1192                parent: fn_sym,
1193                kind: VarKind::Local,
1194                line: 1,
1195                is_mut: false,
1196            },
1197            "a".to_string(),
1198            Some(a_sym),
1199        );
1200        let b = graph.add_var(
1201            VarData {
1202                parent: fn_sym,
1203                kind: VarKind::Local,
1204                line: 2,
1205                is_mut: false,
1206            },
1207            "b".to_string(),
1208            Some(b_sym),
1209        );
1210        let c = graph.add_var(
1211            VarData {
1212                parent: fn_sym,
1213                kind: VarKind::Local,
1214                line: 3,
1215                is_mut: false,
1216            },
1217            "c".to_string(),
1218            Some(c_sym),
1219        );
1220
1221        // a → b → c
1222        graph.add_flow(
1223            a,
1224            b,
1225            FlowData {
1226                kind: FlowKind::Assign,
1227                line: 2,
1228                scope: ScopeId::from_raw(0),
1229            },
1230        );
1231        graph.add_flow(
1232            b,
1233            c,
1234            FlowData {
1235                kind: FlowKind::Assign,
1236                line: 3,
1237                scope: ScopeId::from_raw(0),
1238            },
1239        );
1240
1241        let provenance = graph.provenance(c);
1242        assert_eq!(provenance.len(), 3);
1243        assert!(provenance.contains(&a));
1244        assert!(provenance.contains(&b));
1245        assert!(provenance.contains(&c));
1246    }
1247
1248    #[test]
1249    fn test_add_var_without_symbol_id_produces_distinct_vars() {
1250        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
1251        let fn_sym = symbols.insert("my_fn");
1252
1253        let mut graph = DataFlowGraphV2::new();
1254
1255        // Two local variables without their own SymbolId (e.g., lookup_var_symbol failed)
1256        let x = graph.add_var(
1257            VarData {
1258                parent: fn_sym,
1259                kind: VarKind::Local,
1260                line: 10,
1261                is_mut: false,
1262            },
1263            "x".to_string(),
1264            None,
1265        );
1266        let y = graph.add_var(
1267            VarData {
1268                parent: fn_sym,
1269                kind: VarKind::Local,
1270                line: 11,
1271                is_mut: false,
1272            },
1273            "y".to_string(),
1274            None,
1275        );
1276
1277        // Must produce distinct VarIds
1278        assert_ne!(x, y, "Different variables must get different VarIds");
1279
1280        // Each must retain its own name
1281        assert_eq!(graph.var_name(x), Some("x"));
1282        assert_eq!(graph.var_name(y), Some("y"));
1283
1284        // Both should appear in the graph
1285        assert_eq!(graph.var_count(), 2);
1286
1287        // No reverse SymbolId lookup for anonymous vars
1288        assert_eq!(graph.var_to_symbol(x), None);
1289        assert_eq!(graph.var_to_symbol(y), None);
1290    }
1291
1292    #[test]
1293    fn test_add_var_with_symbol_id_still_works() {
1294        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
1295        let fn_sym = symbols.insert("my_fn");
1296        let x_sym = symbols.insert("x");
1297
1298        let mut graph = DataFlowGraphV2::new();
1299
1300        let var_id = graph.add_var(
1301            VarData {
1302                parent: fn_sym,
1303                kind: VarKind::Local,
1304                line: 10,
1305                is_mut: false,
1306            },
1307            "x".to_string(),
1308            Some(x_sym),
1309        );
1310
1311        assert_eq!(graph.var_count(), 1);
1312        assert_eq!(graph.var_name(var_id), Some("x"));
1313        assert_eq!(graph.var_to_symbol(var_id), Some(x_sym));
1314    }
1315
1316    #[test]
1317    fn test_vars_for_symbol_with_anonymous_vars() {
1318        let mut symbols: SlotMap<SymbolId, &str> = SlotMap::with_key();
1319        let fn_sym = symbols.insert("my_fn");
1320
1321        let mut graph = DataFlowGraphV2::new();
1322
1323        let x = graph.add_var(
1324            VarData {
1325                parent: fn_sym,
1326                kind: VarKind::Local,
1327                line: 10,
1328                is_mut: false,
1329            },
1330            "x".to_string(),
1331            None,
1332        );
1333        let y = graph.add_var(
1334            VarData {
1335                parent: fn_sym,
1336                kind: VarKind::Local,
1337                line: 11,
1338                is_mut: false,
1339            },
1340            "y".to_string(),
1341            None,
1342        );
1343
1344        // Both vars should appear under the parent symbol
1345        let vars = graph.vars_in_symbol(fn_sym);
1346        assert_eq!(vars.len(), 2);
1347        assert!(vars.contains(&x));
1348        assert!(vars.contains(&y));
1349    }
1350}