debtmap/analyzers/
closure_analyzer.rs1use std::collections::HashSet;
8use syn::{Expr, ExprClosure, visit::Visit};
9
10use super::purity_detector::{MutationScope, PurityDetector};
11use super::scope_tracker::ScopeTracker;
12use crate::core::PurityLevel;
13
14#[derive(Debug, Clone)]
16pub struct ClosurePurity {
17 pub level: PurityLevel,
18 pub confidence: f32,
19 pub captures: Vec<Capture>,
20 pub has_nested_closures: bool,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum CaptureMode {
26 ByValue,
28 ByRef,
30 ByMutRef,
32}
33
34#[derive(Debug, Clone)]
36pub struct Capture {
37 pub var_name: String,
38 pub mode: CaptureMode,
39 pub is_mutated: bool,
40 pub scope: MutationScope,
41}
42
43#[derive(Debug)]
45pub struct ClosureAnalyzer<'a> {
46 parent_scope: &'a ScopeTracker,
48 captures: Vec<Capture>,
50 confidence_penalties: Vec<&'static str>,
52}
53
54impl<'a> ClosureAnalyzer<'a> {
55 pub fn new(parent_scope: &'a ScopeTracker) -> Self {
57 Self {
58 parent_scope,
59 captures: Vec::new(),
60 confidence_penalties: Vec::new(),
61 }
62 }
63
64 pub fn analyze_closure(&mut self, closure: &ExprClosure) -> ClosurePurity {
66 let mut body_detector = PurityDetector::new();
68
69 for input in &closure.inputs {
71 if let syn::Pat::Ident(pat_ident) = input {
72 body_detector
73 .scope_mut()
74 .add_local_var(pat_ident.ident.to_string());
75 }
76 }
77
78 body_detector.visit_expr(&closure.body);
80
81 self.captures = self.find_captures(closure, &body_detector);
83
84 self.infer_capture_modes(closure, &body_detector);
86
87 let has_nested_closures = self.contains_nested_closures(&closure.body);
89 if has_nested_closures {
90 self.confidence_penalties.push("nested_closures");
91 }
92
93 let level = self.determine_purity_level(&body_detector);
95
96 let confidence = self.calculate_confidence(&body_detector);
98
99 ClosurePurity {
100 level,
101 confidence,
102 captures: self.captures.clone(),
103 has_nested_closures,
104 }
105 }
106
107 fn find_captures(
109 &self,
110 closure: &ExprClosure,
111 _body_detector: &PurityDetector,
112 ) -> Vec<Capture> {
113 let mut params: HashSet<String> = HashSet::new();
115 for input in &closure.inputs {
116 if let syn::Pat::Ident(pat_ident) = input {
117 params.insert(pat_ident.ident.to_string());
118 }
119 }
120
121 let mut visitor = CaptureDetector {
123 params: ¶ms,
124 parent_scope: self.parent_scope,
125 captures: Vec::new(),
126 };
127 visitor.visit_expr(&closure.body);
128
129 visitor.captures
130 }
131
132 fn infer_capture_modes(&mut self, closure: &ExprClosure, body_detector: &PurityDetector) {
134 let has_move = closure.capture.is_some();
136
137 for capture in &mut self.captures {
138 if has_move {
140 capture.mode = CaptureMode::ByValue;
141 continue;
142 }
143
144 let is_mutated = body_detector
146 .local_mutations()
147 .iter()
148 .any(|m| m.target == capture.var_name);
149
150 capture.is_mutated = is_mutated;
151
152 capture.mode = if is_mutated {
154 CaptureMode::ByMutRef
155 } else {
156 CaptureMode::ByRef
157 };
158
159 capture.scope = if self.parent_scope.is_local(&capture.var_name) {
161 MutationScope::Local
162 } else {
163 MutationScope::External
164 };
165 }
166 }
167
168 fn contains_nested_closures(&self, expr: &Expr) -> bool {
170 let mut visitor = ClosureDetector { found: false };
171 visitor.visit_expr(expr);
172 visitor.found
173 }
174
175 fn determine_purity_level(&self, body_detector: &PurityDetector) -> PurityLevel {
177 if body_detector.has_io_operations() || body_detector.has_unsafe_blocks() {
179 return PurityLevel::Impure;
180 }
181
182 if body_detector.modifies_external_state() {
184 return PurityLevel::Impure;
185 }
186
187 let mutates_external = self
189 .captures
190 .iter()
191 .any(|c| c.is_mutated && c.scope == MutationScope::External);
192
193 if mutates_external {
194 return PurityLevel::Impure;
195 }
196
197 let mutates_local = self
199 .captures
200 .iter()
201 .any(|c| c.is_mutated && c.scope == MutationScope::Local);
202
203 if mutates_local || !body_detector.local_mutations().is_empty() {
204 return PurityLevel::LocallyPure;
205 }
206
207 if body_detector.accesses_external_state() {
209 return PurityLevel::ReadOnly;
210 }
211
212 PurityLevel::StrictlyPure
214 }
215
216 fn calculate_confidence(&self, body_detector: &PurityDetector) -> f32 {
218 let mut confidence: f32 = 1.0;
219
220 if self.confidence_penalties.contains(&"nested_closures") {
222 confidence *= 0.85;
223 }
224
225 if body_detector.accesses_external_state() {
227 confidence *= 0.80;
228 }
229
230 if self.captures.len() > 3 {
232 confidence *= 0.90;
233 }
234
235 if self.captures.iter().any(|c| c.mode != CaptureMode::ByValue) {
237 confidence *= 0.95;
238 }
239
240 confidence.clamp(0.5, 1.0)
241 }
242}
243
244struct CaptureDetector<'a> {
246 params: &'a HashSet<String>,
247 parent_scope: &'a ScopeTracker,
248 captures: Vec<Capture>,
249}
250
251impl<'ast, 'a> Visit<'ast> for CaptureDetector<'a> {
252 fn visit_expr(&mut self, expr: &'ast Expr) {
253 if let Expr::Path(path) = expr
254 && let Some(ident) = path.path.get_ident()
255 {
256 let name = ident.to_string();
257
258 if !self.params.contains(&name) && name != "self" && name != "Self" {
260 if self.parent_scope.is_local(&name) || self.parent_scope.is_self(&name) {
262 if !self.captures.iter().any(|c| c.var_name == name) {
264 self.captures.push(Capture {
265 var_name: name,
266 mode: CaptureMode::ByRef, is_mutated: false,
268 scope: MutationScope::Local,
269 });
270 }
271 }
272 }
273 }
274
275 syn::visit::visit_expr(self, expr);
276 }
277}
278
279struct ClosureDetector {
281 found: bool,
282}
283
284impl<'ast> Visit<'ast> for ClosureDetector {
285 fn visit_expr(&mut self, expr: &'ast Expr) {
286 if matches!(expr, Expr::Closure(_)) {
287 self.found = true;
288 return;
289 }
290 syn::visit::visit_expr(self, expr);
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use syn::parse_quote;
298
299 #[test]
300 fn test_simple_closure_no_captures() {
301 let closure: ExprClosure = parse_quote!(|x| x * 2);
302 let parent_scope = ScopeTracker::new();
303 let mut analyzer = ClosureAnalyzer::new(&parent_scope);
304
305 let result = analyzer.analyze_closure(&closure);
306
307 assert_eq!(result.level, PurityLevel::StrictlyPure);
308 assert!(result.captures.is_empty());
309 assert!(!result.has_nested_closures);
310 }
311
312 #[test]
313 fn test_closure_with_capture() {
314 let closure: ExprClosure = parse_quote!(|x| x + y);
315 let mut parent_scope = ScopeTracker::new();
316 parent_scope.add_local_var("y".to_string());
317 let mut analyzer = ClosureAnalyzer::new(&parent_scope);
318
319 let result = analyzer.analyze_closure(&closure);
320
321 assert_eq!(result.captures.len(), 1);
322 assert_eq!(result.captures[0].var_name, "y");
323 assert_eq!(result.captures[0].mode, CaptureMode::ByRef);
324 }
325
326 #[test]
327 fn test_move_closure() {
328 let closure: ExprClosure = parse_quote!(move |x| x + y);
329 let mut parent_scope = ScopeTracker::new();
330 parent_scope.add_local_var("y".to_string());
331 let mut analyzer = ClosureAnalyzer::new(&parent_scope);
332
333 let result = analyzer.analyze_closure(&closure);
334
335 assert_eq!(result.captures.len(), 1);
336 assert_eq!(result.captures[0].mode, CaptureMode::ByValue);
337 }
338
339 #[test]
340 fn test_nested_closure_detection() {
341 let closure: ExprClosure = parse_quote!(|x| {
342 let f = |y| y * 2;
343 f(x)
344 });
345 let parent_scope = ScopeTracker::new();
346 let mut analyzer = ClosureAnalyzer::new(&parent_scope);
347
348 let result = analyzer.analyze_closure(&closure);
349
350 assert!(result.has_nested_closures);
351 assert!(result.confidence < 0.9); }
353}