1use super::match_patterns::MatchExpressionRecognizer;
2use std::collections::HashMap;
3use syn::{
4 Block, Expr, ExprAsync, ExprBlock, ExprClosure, ExprForLoop, ExprIf, ExprLoop, ExprMatch,
5 ExprWhile, ImplItem, Item, Stmt, visit::Visit,
6};
7
8#[derive(Debug, Clone)]
10pub struct MatchLocation {
11 pub line: usize,
12 pub arms: usize,
13 pub complexity: u32,
14 pub context: ComplexityContext,
15}
16
17#[derive(Debug, Clone)]
19pub struct ComplexityContext {
20 pub in_closure: bool,
21 pub in_async: bool,
22 pub nesting_depth: u32,
23 pub function_role: FunctionRole,
24}
25
26#[derive(Debug, Clone, PartialEq)]
27pub enum FunctionRole {
28 EntryPoint,
29 CoreLogic,
30 Utility,
31 Test,
32 Unknown,
33}
34
35const MAX_RECURSION_DEPTH: u32 = 150;
37
38#[derive(Debug, Clone, Hash, PartialEq, Eq)]
40pub struct CacheKey {
41 function_name: String,
42 file_path: String,
43}
44
45pub struct RecursiveMatchDetector {
47 pub matches_found: Vec<MatchLocation>,
48 depth_tracker: u32,
49 complexity_context: ComplexityContext,
50 in_closure: bool,
51 in_async: bool,
52 cache: HashMap<CacheKey, Vec<MatchLocation>>,
54 max_depth_reached: u32,
56}
57
58impl Default for RecursiveMatchDetector {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64impl RecursiveMatchDetector {
65 pub fn new() -> Self {
66 Self {
67 matches_found: Vec::new(),
68 depth_tracker: 0,
69 complexity_context: ComplexityContext {
70 in_closure: false,
71 in_async: false,
72 nesting_depth: 0,
73 function_role: FunctionRole::Unknown,
74 },
75 in_closure: false,
76 in_async: false,
77 cache: HashMap::new(),
78 max_depth_reached: 0,
79 }
80 }
81
82 pub fn with_cache(cache: HashMap<CacheKey, Vec<MatchLocation>>) -> Self {
84 let mut detector = Self::new();
85 detector.cache = cache;
86 detector
87 }
88
89 pub fn get_cache(&self) -> &HashMap<CacheKey, Vec<MatchLocation>> {
91 &self.cache
92 }
93
94 fn check_depth_limit(&mut self) -> bool {
96 if self.depth_tracker > MAX_RECURSION_DEPTH {
97 eprintln!(
98 "Warning: Maximum recursion depth {} reached, stopping traversal",
99 MAX_RECURSION_DEPTH
100 );
101 return false;
102 }
103 if self.depth_tracker > self.max_depth_reached {
104 self.max_depth_reached = self.depth_tracker;
105 }
106 true
107 }
108
109 pub fn find_all_matches(&mut self, item: &Item) -> Vec<MatchLocation> {
111 self.traverse_item_recursively(item);
112 self.matches_found.clone()
113 }
114
115 pub fn find_matches_in_block(&mut self, block: &Block) -> Vec<MatchLocation> {
117 self.visit_block(block);
118 self.matches_found.clone()
119 }
120
121 fn traverse_item_recursively(&mut self, item: &Item) {
122 match item {
123 Item::Fn(func) => {
124 self.determine_function_role(&func.sig.ident.to_string());
125 self.visit_block(&func.block);
126 }
127 Item::Impl(impl_block) => {
128 for item in &impl_block.items {
129 if let ImplItem::Fn(method) = item {
130 self.determine_function_role(&method.sig.ident.to_string());
131 self.visit_block(&method.block);
132 }
133 }
134 }
135 _ => {}
136 }
137 }
138
139 fn determine_function_role(&mut self, name: &str) {
140 self.complexity_context.function_role = if name == "main" {
141 FunctionRole::EntryPoint
142 } else if name.starts_with("test_") || name.ends_with("_test") {
143 FunctionRole::Test
144 } else if name.starts_with("get_") || name.starts_with("set_") || name.starts_with("is_") {
145 FunctionRole::Utility
146 } else {
147 FunctionRole::CoreLogic
148 };
149 }
150
151 fn calculate_match_complexity(&self, match_expr: &ExprMatch) -> u32 {
152 let recognizer = MatchExpressionRecognizer::new();
153 let mut complexity = match_expr.arms.len() as u32;
154
155 let simple_arms = match_expr
157 .arms
158 .iter()
159 .all(|arm| recognizer.is_simple_arm(&arm.body));
160
161 if simple_arms {
162 complexity = (complexity as f32).log2().ceil() as u32;
164 }
165
166 complexity += self.depth_tracker.min(3);
168
169 complexity
170 }
171
172 fn get_line_number(&self, _expr: &ExprMatch) -> usize {
173 1
176 }
177
178 fn visit_with_nested_depth(&mut self, visit_nested: impl FnOnce(&mut Self)) {
179 self.depth_tracker += 1;
180 if self.check_depth_limit() {
181 visit_nested(self);
182 }
183 self.depth_tracker -= 1;
184 }
185
186 fn record_match(&mut self, match_expr: &ExprMatch) {
187 self.matches_found.push(MatchLocation {
188 line: self.get_line_number(match_expr),
189 arms: match_expr.arms.len(),
190 complexity: self.calculate_match_complexity(match_expr),
191 context: self.complexity_context.clone(),
192 });
193 }
194
195 fn visit_match_expr(&mut self, match_expr: &ExprMatch) {
196 self.record_match(match_expr);
197 self.visit_with_nested_depth(|detector| {
198 for arm in &match_expr.arms {
199 detector.visit_expr(&arm.body);
200 }
201 });
202 }
203
204 fn visit_closure_expr(&mut self, closure: &ExprClosure) {
205 let was_in_closure = self.in_closure;
206 self.in_closure = true;
207 self.complexity_context.in_closure = true;
208
209 self.visit_with_nested_depth(|detector| {
210 detector.visit_expr(&closure.body);
211 });
212
213 self.complexity_context.in_closure = was_in_closure;
214 self.in_closure = was_in_closure;
215 }
216
217 fn visit_async_expr(&mut self, async_block: &ExprAsync) {
218 let was_in_async = self.in_async;
219 self.in_async = true;
220 self.complexity_context.in_async = true;
221
222 self.visit_with_nested_depth(|detector| {
223 detector.visit_block(&async_block.block);
224 });
225
226 self.complexity_context.in_async = was_in_async;
227 self.in_async = was_in_async;
228 }
229
230 fn visit_block_expr(&mut self, expr_block: &ExprBlock) {
231 self.visit_with_nested_depth(|detector| {
232 detector.visit_block(&expr_block.block);
233 });
234 }
235
236 fn visit_if_expr(&mut self, if_expr: &ExprIf) {
237 self.visit_with_nested_depth(|detector| {
238 detector.visit_expr(&if_expr.cond);
239 detector.visit_block(&if_expr.then_branch);
240 if let Some((_else_token, else_branch)) = &if_expr.else_branch {
241 detector.visit_expr(else_branch);
242 }
243 });
244 }
245
246 fn visit_while_expr(&mut self, while_expr: &ExprWhile) {
247 self.visit_with_nested_depth(|detector| {
248 detector.visit_expr(&while_expr.cond);
249 detector.visit_block(&while_expr.body);
250 });
251 }
252
253 fn visit_for_loop_expr(&mut self, for_loop: &ExprForLoop) {
254 self.visit_with_nested_depth(|detector| {
255 detector.visit_expr(&for_loop.expr);
256 detector.visit_block(&for_loop.body);
257 });
258 }
259
260 fn visit_loop_expr(&mut self, loop_expr: &ExprLoop) {
261 self.visit_with_nested_depth(|detector| {
262 detector.visit_block(&loop_expr.body);
263 });
264 }
265
266 fn visit_other_expr(&mut self, expr: &Expr) {
267 self.visit_with_nested_depth(|detector| {
268 detector.visit_other_expr_children(expr);
269 });
270 }
271
272 fn visit_other_expr_children(&mut self, expr: &Expr) {
273 match expr {
274 Expr::Binary(e) => {
275 self.visit_expr(&e.left);
276 self.visit_expr(&e.right);
277 }
278 Expr::Unary(e) => {
279 self.visit_expr(&e.expr);
280 }
281 Expr::Call(e) => {
282 self.visit_expr(&e.func);
283 for arg in &e.args {
284 self.visit_expr(arg);
285 }
286 }
287 Expr::MethodCall(e) => {
288 self.visit_expr(&e.receiver);
289 for arg in &e.args {
290 self.visit_expr(arg);
291 }
292 }
293 Expr::Field(e) => {
294 self.visit_expr(&e.base);
295 }
296 Expr::Index(e) => {
297 self.visit_expr(&e.expr);
298 self.visit_expr(&e.index);
299 }
300 Expr::Paren(e) => {
301 self.visit_expr(&e.expr);
302 }
303 Expr::Try(e) => {
304 self.visit_expr(&e.expr);
305 }
306 Expr::Await(e) => {
307 self.visit_expr(&e.base);
308 }
309 Expr::Lit(_) | Expr::Path(_) | Expr::Continue(_) | Expr::Break(_) => {}
310 _ => {
311 if self.depth_tracker < MAX_RECURSION_DEPTH / 2 {
312 syn::visit::visit_expr(self, expr);
313 }
314 }
315 }
316 }
317}
318
319impl<'ast> Visit<'ast> for RecursiveMatchDetector {
320 fn visit_expr(&mut self, expr: &'ast Expr) {
321 if !self.check_depth_limit() {
323 return;
324 }
325
326 match expr {
327 Expr::Match(match_expr) => self.visit_match_expr(match_expr),
328 Expr::Closure(closure) => self.visit_closure_expr(closure),
329 Expr::Async(async_block) => self.visit_async_expr(async_block),
330 Expr::Block(expr_block) => self.visit_block_expr(expr_block),
331 Expr::If(if_expr) => self.visit_if_expr(if_expr),
332 Expr::While(while_expr) => self.visit_while_expr(while_expr),
333 Expr::ForLoop(for_loop) => self.visit_for_loop_expr(for_loop),
334 Expr::Loop(loop_expr) => self.visit_loop_expr(loop_expr),
335 _ => self.visit_other_expr(expr),
336 }
337 }
338
339 fn visit_block(&mut self, block: &'ast Block) {
340 if !self.check_depth_limit() {
342 return;
343 }
344
345 for stmt in &block.stmts {
346 if !self.check_depth_limit() {
347 break;
348 }
349 self.visit_stmt(stmt);
350 }
351 }
352
353 fn visit_stmt(&mut self, stmt: &'ast Stmt) {
354 if !self.check_depth_limit() {
356 return;
357 }
358
359 match stmt {
360 Stmt::Expr(expr, _) => {
361 self.visit_expr(expr);
362 }
363 Stmt::Macro(_) => {
364 }
366 Stmt::Local(local) => {
367 if let Some(init) = &local.init {
368 self.visit_expr(&init.expr);
369 }
370 }
371 Stmt::Item(item) => {
372 if let Item::Fn(func) = item {
374 self.depth_tracker += 1;
375 if self.check_depth_limit() {
376 self.visit_block(&func.block);
377 }
378 self.depth_tracker -= 1;
379 }
380 }
381 }
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use syn::parse_quote;
389
390 #[test]
391 fn test_recursive_match_detection() {
392 let item: Item = parse_quote! {
393 fn process_value(val: Value) -> Result<String> {
394 let result = match val {
395 Value::String(s) => s,
396 Value::Number(n) => n.to_string(),
397 Value::Bool(b) => b.to_string(),
398 Value::Nested(inner) => {
399 match inner.kind {
401 Kind::A => "type_a",
402 Kind::B => "type_b",
403 Kind::C => "type_c",
404 }
405 }
406 _ => "unknown",
407 };
408
409 Ok(result)
410 }
411 };
412
413 let mut detector = RecursiveMatchDetector::new();
414 let matches = detector.find_all_matches(&item);
415
416 assert_eq!(matches.len(), 2);
418 assert_eq!(matches[0].arms, 5); assert_eq!(matches[1].arms, 3); }
421
422 #[test]
423 fn test_match_in_closure() {
424 let block: Block = parse_quote! {{
425 let processor = |item| {
426 match item {
427 Item::A => 1,
428 Item::B => 2,
429 Item::C => 3,
430 }
431 };
432 }};
433
434 let mut detector = RecursiveMatchDetector::new();
435 let matches = detector.find_matches_in_block(&block);
436
437 assert_eq!(matches.len(), 1);
438 assert!(matches[0].context.in_closure);
439 }
440
441 #[test]
442 fn test_match_in_async_block() {
443 let block: Block = parse_quote! {{
444 let result = async {
445 match fetch_data().await {
446 Ok(data) => process(data),
447 Err(e) => handle_error(e),
448 }
449 };
450 }};
451
452 let mut detector = RecursiveMatchDetector::new();
453 let matches = detector.find_matches_in_block(&block);
454
455 assert_eq!(matches.len(), 1);
456 assert!(matches[0].context.in_async);
457 }
458
459 #[test]
460 fn test_context_is_restored_after_closure_and_async() {
461 let block: Block = parse_quote! {{
462 let from_closure = |item| {
463 match item {
464 Item::A => 1,
465 _ => 2,
466 }
467 };
468
469 let from_async = async {
470 match fetch_data().await {
471 Ok(data) => data,
472 Err(_) => 0,
473 }
474 };
475
476 match current {
477 Current::Ready => 1,
478 _ => 0,
479 }
480 }};
481
482 let mut detector = RecursiveMatchDetector::new();
483 let matches = detector.find_matches_in_block(&block);
484
485 assert_eq!(matches.len(), 3);
486 assert!(matches[0].context.in_closure);
487 assert!(!matches[0].context.in_async);
488 assert!(!matches[1].context.in_closure);
489 assert!(matches[1].context.in_async);
490 assert!(!matches[2].context.in_closure);
491 assert!(!matches[2].context.in_async);
492 }
493
494 #[test]
495 fn test_default_fallback_finds_match_in_tuple_expression() {
496 let block: Block = parse_quote! {{
497 let pair = (
498 match left {
499 Side::A => 1,
500 _ => 0,
501 },
502 match right {
503 Side::B => 2,
504 _ => 0,
505 },
506 );
507 }};
508
509 let mut detector = RecursiveMatchDetector::new();
510 let matches = detector.find_matches_in_block(&block);
511
512 assert_eq!(matches.len(), 2);
513 }
514
515 #[test]
516 fn test_deeply_nested_matches() {
517 let block: Block = parse_quote! {{
518 if condition {
519 for item in items {
520 while processing {
521 match item.state {
522 State::Init => {
523 match item.sub_state {
524 SubState::Ready => "ready",
525 SubState::Waiting => "waiting",
526 }
527 }
528 State::Done => "done",
529 }
530 }
531 }
532 }
533 }};
534
535 let mut detector = RecursiveMatchDetector::new();
536 let matches = detector.find_matches_in_block(&block);
537
538 assert_eq!(matches.len(), 2);
539 assert!(matches[1].complexity > matches[0].arms as u32);
541 }
542}