Skip to main content

ryo_executor/engine/impls/
lock.rs

1//! V2 ASTRegApply implementations for lock-related idiom mutations
2//!
3//! # Classification: Primitive
4//!
5//! These detect lock optimization opportunities:
6//! - UseAtomic: Detect Mutex<primitive> that could use atomic types
7//! - UseRwLock: Detect Mutex that could use RwLock for read-heavy patterns
8//! - LockScope: Detect locks held across await points
9//!
10//! Note: These mutations currently only DETECT opportunities.
11//! Actual refactoring is not yet implemented (returns changes: 0).
12
13use ryo_mutations::idiom::{LockScopeMutation, UseAtomicMutation, UseRwLockMutation};
14use ryo_mutations::MutationResult;
15use ryo_source::pure::{PureFields, PureItem, PureType};
16use ryo_symbol::SymbolKind;
17
18use crate::engine::{ASTMutationContext, ASTRegApply};
19
20impl ASTRegApply for UseAtomicMutation {
21    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
22        let mut opportunities = Vec::new();
23
24        // Find structs and check for atomic opportunities
25        let struct_ids: Vec<_> = ctx
26            .symbol_registry
27            .iter()
28            .filter(|(id, path)| {
29                if ctx.symbol_registry.kind(*id) != Some(SymbolKind::Struct) {
30                    return false;
31                }
32                // Apply target filter
33                if let Some(ref target) = self.target_struct {
34                    return path.name() == target;
35                }
36                true
37            })
38            .map(|(id, path)| (id, path.name().to_string()))
39            .collect();
40
41        for (struct_id, struct_name) in struct_ids {
42            if let Some(PureItem::Struct(s)) = ctx.ast_registry.get(struct_id) {
43                if let PureFields::Named(fields) = &s.fields {
44                    for field in fields {
45                        let type_str = match &field.ty {
46                            PureType::Path(p) => p.as_str(),
47                            _ => continue,
48                        };
49
50                        if type_str.contains("Mutex<") {
51                            if let Some(atomic_type) = is_atomic_candidate(&field.name) {
52                                opportunities.push(format!(
53                                    "{}.{} → {}",
54                                    struct_name, field.name, atomic_type
55                                ));
56                            }
57                        }
58                    }
59                }
60            }
61        }
62
63        MutationResult {
64            mutation_type: "UseAtomic".to_string(),
65            changes: 0, // Detection only
66            description: if opportunities.is_empty() {
67                "No atomic optimization opportunities found".to_string()
68            } else {
69                format!(
70                    "Found {} atomic opportunities: {}",
71                    opportunities.len(),
72                    opportunities.join(", ")
73                )
74            },
75        }
76    }
77}
78
79impl ASTRegApply for UseRwLockMutation {
80    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
81        let mut opportunities = Vec::new();
82
83        // Find structs and check for RwLock opportunities
84        let struct_ids: Vec<_> = ctx
85            .symbol_registry
86            .iter()
87            .filter(|(id, path)| {
88                if ctx.symbol_registry.kind(*id) != Some(SymbolKind::Struct) {
89                    return false;
90                }
91                if let Some(ref target) = self.target_struct {
92                    return path.name() == target;
93                }
94                true
95            })
96            .map(|(id, path)| (id, path.name().to_string()))
97            .collect();
98
99        for (struct_id, struct_name) in struct_ids {
100            if let Some(PureItem::Struct(s)) = ctx.ast_registry.get(struct_id) {
101                if let PureFields::Named(fields) = &s.fields {
102                    for field in fields {
103                        let type_str = match &field.ty {
104                            PureType::Path(p) => p.as_str(),
105                            _ => continue,
106                        };
107
108                        // Check for Mutex<Collection> patterns
109                        if type_str.contains("Mutex<")
110                            && (type_str.contains("HashMap")
111                                || type_str.contains("BTreeMap")
112                                || type_str.contains("Vec<")
113                                || type_str.contains("HashSet")
114                                || type_str.contains("BTreeSet")
115                                || type_str.contains("VecDeque"))
116                        {
117                            opportunities
118                                .push(format!("{}.{}: Mutex → RwLock", struct_name, field.name));
119                        }
120                    }
121                }
122            }
123        }
124
125        MutationResult {
126            mutation_type: "UseRwLock".to_string(),
127            changes: 0, // Detection only
128            description: if opportunities.is_empty() {
129                "No RwLock optimization opportunities found".to_string()
130            } else {
131                format!(
132                    "Found {} RwLock opportunities: {}",
133                    opportunities.len(),
134                    opportunities.join(", ")
135                )
136            },
137        }
138    }
139}
140
141impl ASTRegApply for LockScopeMutation {
142    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
143        let mut opportunities = Vec::new();
144
145        // Find async functions and check for lock scope issues
146        let fn_ids: Vec<_> = if let Some(target_id) = self.target_fn {
147            // Target function specified: direct lookup
148            if let Some(path) = ctx.symbol_registry.resolve(target_id) {
149                vec![(target_id, path.name().to_string())]
150            } else {
151                vec![]
152            }
153        } else {
154            // No target: collect all functions
155            ctx.symbol_registry
156                .iter()
157                .filter(|(id, _)| ctx.symbol_registry.kind(*id) == Some(SymbolKind::Function))
158                .map(|(id, path)| (id, path.name().to_string()))
159                .collect()
160        };
161
162        for (fn_id, fn_name) in fn_ids {
163            if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
164                if func.is_async {
165                    // Check for lock patterns in async functions
166                    // This is a simplified detection - real impl would trace lock guards
167                    let body_str = format!("{:?}", func.body);
168                    if (body_str.contains("lock()")
169                        || body_str.contains("read()")
170                        || body_str.contains("write()"))
171                        && body_str.contains("await")
172                    {
173                        opportunities.push(format!("{}: potential lock across await", fn_name));
174                    }
175                }
176            }
177        }
178
179        MutationResult {
180            mutation_type: "LockScope".to_string(),
181            changes: 0, // Detection only
182            description: if opportunities.is_empty() {
183                "No lock scope issues detected".to_string()
184            } else {
185                format!(
186                    "Found {} potential lock scope issues: {}",
187                    opportunities.len(),
188                    opportunities.join(", ")
189                )
190            },
191        }
192    }
193}
194
195/// Check if field name suggests atomic usage
196fn is_atomic_candidate(field_name: &str) -> Option<&'static str> {
197    let lower = field_name.to_lowercase();
198
199    // Counter-like
200    if lower.contains("count")
201        || lower.contains("counter")
202        || lower.contains("num")
203        || lower.contains("total")
204        || lower.contains("size")
205        || lower.contains("len")
206    {
207        return Some("AtomicUsize");
208    }
209
210    // Flag-like
211    if lower.contains("flag")
212        || lower.contains("enabled")
213        || lower.contains("active")
214        || lower.contains("ready")
215        || lower.contains("done")
216        || lower.starts_with("is_")
217    {
218        return Some("AtomicBool");
219    }
220
221    // ID-like
222    if lower.contains("id") || lower.contains("index") || lower.contains("seq") {
223        return Some("AtomicU64");
224    }
225
226    None
227}