1use crate::priority::call_graph::FunctionId;
7use anyhow::Result;
8use im::{HashMap, HashSet, Vector};
9use std::path::Path;
10use syn::visit::Visit;
11use syn::{Expr, ExprCall, ExprClosure, ExprPath, File, Ident, ItemFn, Local, Pat, PatIdent, Type};
12
13#[derive(Debug, Clone)]
15pub struct ClosureInfo {
16 pub closure_id: String,
18 pub containing_function: FunctionId,
20 pub line: usize,
22 pub calls: Vector<FunctionId>,
24 pub captures_variables: bool,
26 pub captured_by_ref: HashSet<String>,
28 pub captured_by_value: HashSet<String>,
30}
31
32#[derive(Debug, Clone)]
34pub struct FunctionPointerInfo {
35 pub variable_name: String,
37 pub defining_function: FunctionId,
39 pub possible_targets: HashSet<FunctionId>,
41 pub line: usize,
43 pub is_parameter: bool,
45}
46
47#[derive(Debug, Clone)]
49pub struct FunctionPointerCall {
50 pub caller: FunctionId,
52 pub pointer_id: String,
54 pub line: usize,
56}
57
58#[derive(Debug, Clone)]
60pub struct HigherOrderFunctionCall {
61 pub caller: FunctionId,
63 pub hof_function: String,
65 pub function_arguments: Vector<FunctionId>,
67 pub line: usize,
69}
70
71#[derive(Debug, Clone)]
73pub struct FunctionPointerTracker {
74 closures: HashMap<String, ClosureInfo>,
76 function_pointers: HashMap<String, FunctionPointerInfo>,
78 pointer_calls: Vector<FunctionPointerCall>,
80 hof_calls: Vector<HigherOrderFunctionCall>,
82 variable_to_pointer: HashMap<String, String>,
84 potential_pointer_targets: HashSet<FunctionId>,
86}
87
88impl FunctionPointerTracker {
89 pub fn new() -> Self {
91 Self {
92 closures: HashMap::new(),
93 function_pointers: HashMap::new(),
94 pointer_calls: Vector::new(),
95 hof_calls: Vector::new(),
96 variable_to_pointer: HashMap::new(),
97 potential_pointer_targets: HashSet::new(),
98 }
99 }
100
101 pub fn analyze_file(&mut self, file_path: &Path, ast: &File) -> Result<()> {
103 let mut visitor = FunctionPointerVisitor::new(file_path.to_path_buf());
104 visitor.visit_file(ast);
105
106 for closure in visitor.closures {
108 let closure_id = closure.closure_id.clone();
109 self.closures.insert(closure_id, closure);
110 }
111
112 for pointer in visitor.function_pointers {
114 let pointer_id = format!(
115 "{}_{}",
116 pointer.defining_function.name, pointer.variable_name
117 );
118
119 self.variable_to_pointer
121 .insert(pointer.variable_name.clone(), pointer_id.clone());
122
123 for target in &pointer.possible_targets {
125 self.potential_pointer_targets.insert(target.clone());
126 }
127
128 self.function_pointers.insert(pointer_id, pointer);
129 }
130
131 for call in visitor.pointer_calls {
133 self.pointer_calls.push_back(call);
134 }
135
136 for hof_call in visitor.hof_calls {
138 for func_arg in &hof_call.function_arguments {
140 self.potential_pointer_targets.insert(func_arg.clone());
141 }
142 self.hof_calls.push_back(hof_call);
143 }
144
145 Ok(())
146 }
147
148 pub fn get_function_pointer_calls(&self) -> Vector<FunctionPointerCall> {
150 self.pointer_calls.clone()
151 }
152
153 pub fn resolve_pointer_targets(&self, pointer_id: &str) -> Option<Vector<FunctionId>> {
155 self.function_pointers
156 .get(pointer_id)
157 .map(|pointer| pointer.possible_targets.iter().cloned().collect())
158 }
159
160 pub fn might_be_called_through_pointer(&self, func_id: &FunctionId) -> bool {
162 self.potential_pointer_targets.contains(func_id)
163 || self
164 .closures
165 .values()
166 .any(|closure| closure.calls.contains(func_id))
167 }
168
169 pub fn get_higher_order_calls(&self) -> Vector<HigherOrderFunctionCall> {
171 self.hof_calls.clone()
172 }
173
174 pub fn get_statistics(&self) -> FunctionPointerStatistics {
176 let total_closures = self.closures.len();
177 let total_function_pointers = self.function_pointers.len();
178 let total_pointer_calls = self.pointer_calls.len();
179 let total_hof_calls = self.hof_calls.len();
180 let potential_targets = self.potential_pointer_targets.len();
181
182 FunctionPointerStatistics {
183 total_closures,
184 total_function_pointers,
185 total_pointer_calls,
186 total_hof_calls,
187 potential_targets,
188 }
189 }
190
191 pub fn get_definitely_used_functions(&self) -> HashSet<FunctionId> {
193 let mut used_functions = HashSet::new();
194
195 for closure in self.closures.values() {
197 for called_func in &closure.calls {
198 used_functions.insert(called_func.clone());
199 }
200 }
201
202 for hof_call in &self.hof_calls {
204 for func_arg in &hof_call.function_arguments {
205 used_functions.insert(func_arg.clone());
206 }
207 }
208
209 used_functions
210 }
211}
212
213#[derive(Debug, Clone)]
215pub struct FunctionPointerStatistics {
216 pub total_closures: usize,
217 pub total_function_pointers: usize,
218 pub total_pointer_calls: usize,
219 pub total_hof_calls: usize,
220 pub potential_targets: usize,
221}
222
223struct FunctionPointerVisitor {
225 file_path: std::path::PathBuf,
226 closures: Vec<ClosureInfo>,
227 function_pointers: Vec<FunctionPointerInfo>,
228 pointer_calls: Vec<FunctionPointerCall>,
229 hof_calls: Vec<HigherOrderFunctionCall>,
230 current_function: Option<FunctionId>,
231 closure_counter: usize,
232}
233
234impl FunctionPointerVisitor {
235 fn new(file_path: std::path::PathBuf) -> Self {
236 Self {
237 file_path,
238 closures: Vec::new(),
239 function_pointers: Vec::new(),
240 pointer_calls: Vec::new(),
241 hof_calls: Vec::new(),
242 current_function: None,
243 closure_counter: 0,
244 }
245 }
246
247 fn get_line_number(&self, span: proc_macro2::Span) -> usize {
248 span.start().line
249 }
250
251 fn is_higher_order_function(&self, name: &str) -> bool {
252 matches!(
253 name,
254 "map"
255 | "filter"
256 | "fold"
257 | "reduce"
258 | "for_each"
259 | "find"
260 | "any"
261 | "all"
262 | "collect"
263 | "and_then"
264 | "or_else"
265 | "iter"
266 | "enumerate"
267 | "zip"
268 | "chain"
269 | "take"
270 | "skip"
271 )
272 }
273
274 fn extract_function_name_from_path(&self, path: &ExprPath) -> Option<String> {
275 if path.path.segments.len() == 1 {
276 Some(path.path.segments.first()?.ident.to_string())
277 } else {
278 let segments: Vec<String> = path
280 .path
281 .segments
282 .iter()
283 .map(|seg| seg.ident.to_string())
284 .collect();
285 Some(segments.join("::"))
286 }
287 }
288
289 fn analyze_closure(&mut self, closure: &ExprClosure) {
290 if let Some(containing_function) = &self.current_function {
291 self.closure_counter += 1;
292 let closure_id = format!(
293 "{}_closure_{}",
294 containing_function.name, self.closure_counter
295 );
296 let line = self.get_line_number(closure.or1_token.span);
297
298 let mut closure_visitor = ClosureCallVisitor::new();
299 closure_visitor.visit_expr(&closure.body);
300
301 let closure_info = ClosureInfo {
302 closure_id,
303 containing_function: containing_function.clone(),
304 line,
305 calls: closure_visitor.function_calls.into_iter().collect(),
306 captures_variables: closure.capture.is_some(),
307 captured_by_ref: HashSet::new(), captured_by_value: HashSet::new(), };
310
311 self.closures.push(closure_info);
312 }
313 }
314
315 fn extract_pointer_assignment_data(local: &Local) -> Option<(&Ident, &Expr)> {
317 match &local.pat {
318 Pat::Ident(PatIdent { ident, .. }) => {
319 local.init.as_ref().map(|init| (ident, &*init.expr))
320 }
321 _ => None,
322 }
323 }
324
325 fn analyze_function_pointer_assignment(&mut self, local: &Local) {
326 let Some(current_func) = &self.current_function else {
327 return;
328 };
329
330 let Some((ident, init_expr)) = Self::extract_pointer_assignment_data(local) else {
331 return;
332 };
333
334 let var_name = ident.to_string();
335 let line = self.get_line_number(ident.span());
336 let possible_targets = self.extract_possible_targets(init_expr);
337
338 let pointer_info = FunctionPointerInfo {
339 variable_name: var_name,
340 defining_function: current_func.clone(),
341 possible_targets,
342 line,
343 is_parameter: false,
344 };
345
346 self.function_pointers.push(pointer_info);
347 }
348
349 fn extract_possible_targets(&self, expr: &Expr) -> HashSet<FunctionId> {
351 let mut possible_targets = HashSet::new();
352
353 if let Expr::Path(path) = expr {
354 if let Some(func_name) = self.extract_function_name_from_path(path) {
355 let target_func = FunctionId {
356 file: self.file_path.clone(),
357 name: func_name,
358 line: 0, };
360 possible_targets.insert(target_func);
361 }
362 }
363
364 possible_targets
365 }
366
367 fn extract_direct_pointer_call(
369 &self,
370 call: &ExprCall,
371 caller: &FunctionId,
372 line: usize,
373 ) -> Option<FunctionPointerCall> {
374 if let Expr::Path(path) = &*call.func {
375 self.extract_function_name_from_path(path)
376 .map(|func_name| FunctionPointerCall {
377 caller: caller.clone(),
378 pointer_id: func_name,
379 line,
380 })
381 } else {
382 None
383 }
384 }
385
386 fn extract_hof_call(
388 &self,
389 call: &ExprCall,
390 caller: &FunctionId,
391 line: usize,
392 ) -> Option<HigherOrderFunctionCall> {
393 let path = match &*call.func {
395 Expr::Path(p) => p,
396 _ => return None,
397 };
398
399 let func_name = self.extract_function_name_from_path(path)?;
401
402 if !self.is_higher_order_function(&func_name) {
404 return None;
405 }
406
407 let function_arguments = self.extract_function_arguments(call);
409
410 (!function_arguments.is_empty()).then(|| HigherOrderFunctionCall {
412 caller: caller.clone(),
413 hof_function: func_name,
414 function_arguments,
415 line,
416 })
417 }
418
419 fn extract_function_arguments(&self, call: &ExprCall) -> Vector<FunctionId> {
421 let mut function_arguments = Vector::new();
422
423 for arg in &call.args {
424 if let Expr::Path(arg_path) = arg {
425 if let Some(arg_func_name) = self.extract_function_name_from_path(arg_path) {
426 let func_arg = FunctionId {
427 file: self.file_path.clone(),
428 name: arg_func_name,
429 line: 0,
430 };
431 function_arguments.push_back(func_arg);
432 }
433 }
434 }
435
436 function_arguments
437 }
438
439 fn analyze_call_expression(&mut self, call: &ExprCall) {
440 if let Some(caller) = &self.current_function {
441 let line = self.get_line_number(call.paren_token.span.open());
442
443 if let Some(pointer_call) = self.extract_direct_pointer_call(call, caller, line) {
445 self.pointer_calls.push(pointer_call);
446 }
447
448 if let Some(hof_call) = self.extract_hof_call(call, caller, line) {
450 self.hof_calls.push(hof_call);
451 }
452 }
453 }
454}
455
456impl<'ast> Visit<'ast> for FunctionPointerVisitor {
457 fn visit_item_fn(&mut self, item: &'ast ItemFn) {
458 let func_name = item.sig.ident.to_string();
459 let line = self.get_line_number(item.sig.ident.span());
460
461 self.current_function = Some(FunctionId {
462 file: self.file_path.clone(),
463 name: func_name,
464 line,
465 });
466
467 for param in &item.sig.inputs {
469 if let syn::FnArg::Typed(typed_param) = param {
470 if let Type::BareFn(_) = &*typed_param.ty {
471 if let Pat::Ident(PatIdent { ident, .. }) = &*typed_param.pat {
473 let param_name = ident.to_string();
474 let line = self.get_line_number(ident.span());
475
476 if let Some(current_func) = &self.current_function {
477 let pointer_info = FunctionPointerInfo {
478 variable_name: param_name,
479 defining_function: current_func.clone(),
480 possible_targets: HashSet::new(), line,
482 is_parameter: true,
483 };
484
485 self.function_pointers.push(pointer_info);
486 }
487 }
488 }
489 }
490 }
491
492 syn::visit::visit_item_fn(self, item);
494
495 self.current_function = None;
496 }
497
498 fn visit_expr_closure(&mut self, expr: &'ast ExprClosure) {
499 self.analyze_closure(expr);
500
501 syn::visit::visit_expr_closure(self, expr);
503 }
504
505 fn visit_local(&mut self, local: &'ast Local) {
506 self.analyze_function_pointer_assignment(local);
507
508 syn::visit::visit_local(self, local);
510 }
511
512 fn visit_expr_call(&mut self, call: &'ast ExprCall) {
513 self.analyze_call_expression(call);
514
515 syn::visit::visit_expr_call(self, call);
517 }
518}
519
520struct ClosureCallVisitor {
522 function_calls: Vec<FunctionId>,
523}
524
525impl ClosureCallVisitor {
526 fn new() -> Self {
527 Self {
528 function_calls: Vec::new(),
529 }
530 }
531
532 fn extract_function_name_from_path(&self, path: &ExprPath) -> Option<String> {
533 if path.path.segments.len() == 1 {
534 Some(path.path.segments.first()?.ident.to_string())
535 } else {
536 let segments: Vec<String> = path
537 .path
538 .segments
539 .iter()
540 .map(|seg| seg.ident.to_string())
541 .collect();
542 Some(segments.join("::"))
543 }
544 }
545}
546
547impl<'ast> Visit<'ast> for ClosureCallVisitor {
548 fn visit_expr_call(&mut self, call: &'ast ExprCall) {
549 if let Expr::Path(path) = &*call.func {
550 if let Some(func_name) = self.extract_function_name_from_path(path) {
551 let func_id = FunctionId {
552 file: std::path::PathBuf::new(), name: func_name,
554 line: 0,
555 };
556 self.function_calls.push(func_id);
557 }
558 }
559
560 syn::visit::visit_expr_call(self, call);
562 }
563
564 fn visit_expr_method_call(&mut self, call: &'ast syn::ExprMethodCall) {
565 let method_name = call.method.to_string();
566 let func_id = FunctionId {
567 file: std::path::PathBuf::new(),
568 name: method_name,
569 line: 0,
570 };
571 self.function_calls.push(func_id);
572
573 syn::visit::visit_expr_method_call(self, call);
575 }
576}
577
578impl Default for FunctionPointerTracker {
579 fn default() -> Self {
580 Self::new()
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587 use syn::parse_quote;
588
589 #[test]
590 fn test_extract_direct_pointer_call() {
591 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
592 let caller = FunctionId {
593 file: std::path::PathBuf::from("test.rs"),
594 name: "test_func".to_string(),
595 line: 1,
596 };
597
598 let call: ExprCall = parse_quote! { func_ptr(42) };
600 let result = visitor.extract_direct_pointer_call(&call, &caller, 10);
601
602 assert!(result.is_some());
603 let pointer_call = result.unwrap();
604 assert_eq!(pointer_call.pointer_id, "func_ptr");
605 assert_eq!(pointer_call.line, 10);
606 assert_eq!(pointer_call.caller.name, "test_func");
607 }
608
609 #[test]
610 fn test_extract_direct_pointer_call_with_method_call() {
611 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
612 let caller = FunctionId {
613 file: std::path::PathBuf::from("test.rs"),
614 name: "test_func".to_string(),
615 line: 1,
616 };
617
618 let call: ExprCall = parse_quote! { compute(42) };
621 let mut call = call;
623 call.func = Box::new(parse_quote! { 42 }); let result = visitor.extract_direct_pointer_call(&call, &caller, 10);
625
626 assert!(result.is_none());
627 }
628
629 #[test]
630 fn test_extract_hof_call_map() {
631 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
632 let caller = FunctionId {
633 file: std::path::PathBuf::from("test.rs"),
634 name: "test_func".to_string(),
635 line: 1,
636 };
637
638 let call: ExprCall = parse_quote! { map(process_item) };
640 let result = visitor.extract_hof_call(&call, &caller, 15);
641
642 assert!(result.is_some());
643 let hof_call = result.unwrap();
644 assert_eq!(hof_call.hof_function, "map");
645 assert_eq!(hof_call.function_arguments.len(), 1);
646 assert_eq!(hof_call.function_arguments[0].name, "process_item");
647 assert_eq!(hof_call.line, 15);
648 }
649
650 #[test]
651 fn test_extract_hof_call_filter() {
652 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
653 let caller = FunctionId {
654 file: std::path::PathBuf::from("test.rs"),
655 name: "test_func".to_string(),
656 line: 1,
657 };
658
659 let call: ExprCall = parse_quote! { filter(is_valid) };
661 let result = visitor.extract_hof_call(&call, &caller, 20);
662
663 assert!(result.is_some());
664 let hof_call = result.unwrap();
665 assert_eq!(hof_call.hof_function, "filter");
666 assert_eq!(hof_call.function_arguments.len(), 1);
667 assert_eq!(hof_call.function_arguments[0].name, "is_valid");
668 }
669
670 #[test]
671 fn test_extract_hof_call_non_hof() {
672 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
673 let caller = FunctionId {
674 file: std::path::PathBuf::from("test.rs"),
675 name: "test_func".to_string(),
676 line: 1,
677 };
678
679 let call: ExprCall = parse_quote! { regular_func(arg) };
681 let result = visitor.extract_hof_call(&call, &caller, 25);
682
683 assert!(result.is_none());
684 }
685
686 #[test]
687 fn test_extract_hof_call_empty_arguments() {
688 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
689 let caller = FunctionId {
690 file: std::path::PathBuf::from("test.rs"),
691 name: "test_func".to_string(),
692 line: 1,
693 };
694
695 let call: ExprCall = parse_quote! { map() };
697 let result = visitor.extract_hof_call(&call, &caller, 30);
698
699 assert!(result.is_none());
701 }
702
703 #[test]
704 fn test_extract_hof_call_closure_argument() {
705 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
706 let caller = FunctionId {
707 file: std::path::PathBuf::from("test.rs"),
708 name: "test_func".to_string(),
709 line: 1,
710 };
711
712 let call: ExprCall = parse_quote! { map(|x| x + 1) };
714 let result = visitor.extract_hof_call(&call, &caller, 35);
715
716 assert!(result.is_none());
718 }
719
720 #[test]
721 fn test_extract_hof_call_nested_path() {
722 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
723 let caller = FunctionId {
724 file: std::path::PathBuf::from("test.rs"),
725 name: "test_func".to_string(),
726 line: 1,
727 };
728
729 let call: ExprCall = parse_quote! { filter(module::is_valid) };
731 let result = visitor.extract_hof_call(&call, &caller, 40);
732
733 assert!(result.is_some());
734 let hof_call = result.unwrap();
735 assert_eq!(hof_call.hof_function, "filter");
736 assert_eq!(hof_call.function_arguments.len(), 1);
738 assert_eq!(hof_call.function_arguments[0].name, "module::is_valid");
739 }
740
741 #[test]
742 fn test_extract_function_arguments() {
743 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
744
745 let call: ExprCall = parse_quote! { fold(initial, combine_func) };
747 let args = visitor.extract_function_arguments(&call);
748
749 assert_eq!(args.len(), 2);
750 assert_eq!(args[0].name, "initial");
751 assert_eq!(args[1].name, "combine_func");
752 }
753
754 #[test]
755 fn test_extract_function_arguments_mixed() {
756 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
757
758 let call: ExprCall = parse_quote! { process(42, handler, "string") };
760 let args = visitor.extract_function_arguments(&call);
761
762 assert_eq!(args.len(), 1);
764 assert_eq!(args[0].name, "handler");
765 }
766
767 #[test]
768 fn test_extract_function_arguments_empty() {
769 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
770
771 let call: ExprCall = parse_quote! { compute(42, "string", true) };
773 let args = visitor.extract_function_arguments(&call);
774
775 assert!(args.is_empty());
776 }
777
778 #[test]
779 fn test_is_higher_order_function() {
780 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
781
782 assert!(visitor.is_higher_order_function("map"));
784 assert!(visitor.is_higher_order_function("filter"));
785 assert!(visitor.is_higher_order_function("fold"));
786 assert!(visitor.is_higher_order_function("for_each"));
787 assert!(visitor.is_higher_order_function("find"));
788 assert!(visitor.is_higher_order_function("any"));
789 assert!(visitor.is_higher_order_function("all"));
790
791 assert!(!visitor.is_higher_order_function("process"));
793 assert!(!visitor.is_higher_order_function("compute"));
794 assert!(!visitor.is_higher_order_function("regular_func"));
795 }
796
797 #[test]
798 fn test_analyze_call_expression_integration() {
799 let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
800 visitor.current_function = Some(FunctionId {
801 file: std::path::PathBuf::from("test.rs"),
802 name: "test_func".to_string(),
803 line: 1,
804 });
805
806 let call: ExprCall = parse_quote! { callback() };
808 visitor.analyze_call_expression(&call);
809 assert_eq!(visitor.pointer_calls.len(), 1);
810 assert_eq!(visitor.pointer_calls[0].pointer_id, "callback");
811
812 let hof_call: ExprCall = parse_quote! { map(transform) };
814 visitor.analyze_call_expression(&hof_call);
815 assert_eq!(visitor.hof_calls.len(), 1);
816 assert_eq!(visitor.hof_calls[0].hof_function, "map");
817 }
818
819 #[test]
820 fn test_analyze_call_expression_no_current_function() {
821 let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
822 let call: ExprCall = parse_quote! { func() };
825 visitor.analyze_call_expression(&call);
826
827 assert!(visitor.pointer_calls.is_empty());
829 assert!(visitor.hof_calls.is_empty());
830 }
831
832 #[test]
833 fn test_extract_possible_targets_with_path() {
834 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
835
836 let expr: Expr = parse_quote! { my_function };
838 let targets = visitor.extract_possible_targets(&expr);
839
840 assert_eq!(targets.len(), 1);
841 let target = targets.iter().next().unwrap();
842 assert_eq!(target.name, "my_function");
843 assert_eq!(target.file, std::path::PathBuf::from("test.rs"));
844 }
845
846 #[test]
847 fn test_extract_possible_targets_with_qualified_path() {
848 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
849
850 let expr: Expr = parse_quote! { module::submodule::function };
852 let targets = visitor.extract_possible_targets(&expr);
853
854 assert_eq!(targets.len(), 1);
855 let target = targets.iter().next().unwrap();
856 assert_eq!(target.name, "module::submodule::function");
857 }
858
859 #[test]
860 fn test_extract_possible_targets_non_path() {
861 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
862
863 let expr: Expr = parse_quote! { 42 };
865 let targets = visitor.extract_possible_targets(&expr);
866
867 assert!(targets.is_empty());
868 }
869
870 #[test]
871 fn test_extract_possible_targets_closure() {
872 let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
873
874 let expr: Expr = parse_quote! { |x| x + 1 };
876 let targets = visitor.extract_possible_targets(&expr);
877
878 assert!(targets.is_empty());
879 }
880
881 #[test]
882 fn test_analyze_function_pointer_assignment_complete() {
883 let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
884 visitor.current_function = Some(FunctionId {
885 file: std::path::PathBuf::from("test.rs"),
886 name: "outer_func".to_string(),
887 line: 1,
888 });
889
890 let pat = Pat::Ident(PatIdent {
893 attrs: vec![],
894 by_ref: None,
895 mutability: None,
896 ident: parse_quote! { func_ptr },
897 subpat: None,
898 });
899
900 let init_expr: Expr = parse_quote! { my_function };
901 let local = Local {
902 attrs: vec![],
903 let_token: Default::default(),
904 pat,
905 init: Some(syn::LocalInit {
906 eq_token: Default::default(),
907 expr: Box::new(init_expr),
908 diverge: None,
909 }),
910 semi_token: Default::default(),
911 };
912
913 visitor.analyze_function_pointer_assignment(&local);
914
915 assert_eq!(visitor.function_pointers.len(), 1);
916 let pointer = &visitor.function_pointers[0];
917 assert_eq!(pointer.variable_name, "func_ptr");
918 assert!(!pointer.is_parameter);
919 assert_eq!(pointer.possible_targets.len(), 1);
920 }
921
922 #[test]
923 fn test_analyze_function_pointer_assignment_no_init() {
924 let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
925 visitor.current_function = Some(FunctionId {
926 file: std::path::PathBuf::from("test.rs"),
927 name: "outer_func".to_string(),
928 line: 1,
929 });
930
931 let pat = Pat::Ident(PatIdent {
933 attrs: vec![],
934 by_ref: None,
935 mutability: None,
936 ident: parse_quote! { func_ptr },
937 subpat: None,
938 });
939
940 let local = Local {
941 attrs: vec![],
942 let_token: Default::default(),
943 pat,
944 init: None, semi_token: Default::default(),
946 };
947
948 visitor.analyze_function_pointer_assignment(&local);
949
950 assert!(visitor.function_pointers.is_empty());
951 }
952
953 #[test]
954 fn test_analyze_function_pointer_assignment_non_ident_pattern() {
955 let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
956 visitor.current_function = Some(FunctionId {
957 file: std::path::PathBuf::from("test.rs"),
958 name: "outer_func".to_string(),
959 line: 1,
960 });
961
962 let pat: Pat = parse_quote! { (a, b) };
964 let init_expr: Expr = parse_quote! { get_tuple() };
965
966 let local = Local {
967 attrs: vec![],
968 let_token: Default::default(),
969 pat,
970 init: Some(syn::LocalInit {
971 eq_token: Default::default(),
972 expr: Box::new(init_expr),
973 diverge: None,
974 }),
975 semi_token: Default::default(),
976 };
977
978 visitor.analyze_function_pointer_assignment(&local);
979
980 assert!(visitor.function_pointers.is_empty());
981 }
982
983 #[test]
984 fn test_analyze_function_pointer_assignment_no_current_function() {
985 let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
986 let pat = Pat::Ident(PatIdent {
989 attrs: vec![],
990 by_ref: None,
991 mutability: None,
992 ident: parse_quote! { func_ptr },
993 subpat: None,
994 });
995
996 let init_expr: Expr = parse_quote! { my_function };
997 let local = Local {
998 attrs: vec![],
999 let_token: Default::default(),
1000 pat,
1001 init: Some(syn::LocalInit {
1002 eq_token: Default::default(),
1003 expr: Box::new(init_expr),
1004 diverge: None,
1005 }),
1006 semi_token: Default::default(),
1007 };
1008
1009 visitor.analyze_function_pointer_assignment(&local);
1010
1011 assert!(visitor.function_pointers.is_empty());
1013 }
1014
1015 #[test]
1016 fn test_extract_pointer_assignment_data_with_ident() {
1017 let pat: Pat = parse_quote! { func_ptr };
1019 let init_expr: Expr = parse_quote! { my_function };
1020 let local = Local {
1021 attrs: vec![],
1022 let_token: Default::default(),
1023 pat,
1024 init: Some(syn::LocalInit {
1025 eq_token: Default::default(),
1026 expr: Box::new(init_expr),
1027 diverge: None,
1028 }),
1029 semi_token: Default::default(),
1030 };
1031
1032 let result = FunctionPointerVisitor::extract_pointer_assignment_data(&local);
1033 assert!(result.is_some());
1034 let (ident, _expr) = result.unwrap();
1035 assert_eq!(ident.to_string(), "func_ptr");
1036 }
1037
1038 #[test]
1039 fn test_extract_pointer_assignment_data_without_init() {
1040 let pat: Pat = parse_quote! { func_ptr };
1042 let local = Local {
1043 attrs: vec![],
1044 let_token: Default::default(),
1045 pat,
1046 init: None,
1047 semi_token: Default::default(),
1048 };
1049
1050 let result = FunctionPointerVisitor::extract_pointer_assignment_data(&local);
1051 assert!(result.is_none());
1052 }
1053
1054 #[test]
1055 fn test_extract_pointer_assignment_data_with_non_ident_pattern() {
1056 let pat: Pat = parse_quote! { (a, b) };
1058 let init_expr: Expr = parse_quote! { (func1, func2) };
1059 let local = Local {
1060 attrs: vec![],
1061 let_token: Default::default(),
1062 pat,
1063 init: Some(syn::LocalInit {
1064 eq_token: Default::default(),
1065 expr: Box::new(init_expr),
1066 diverge: None,
1067 }),
1068 semi_token: Default::default(),
1069 };
1070
1071 let result = FunctionPointerVisitor::extract_pointer_assignment_data(&local);
1072 assert!(result.is_none());
1073 }
1074
1075 #[test]
1076 fn test_extract_pointer_assignment_data_with_mut_ident() {
1077 let pat: Pat = parse_quote! { mut func_ptr };
1079 let init_expr: Expr = parse_quote! { my_function };
1080 let local = Local {
1081 attrs: vec![],
1082 let_token: Default::default(),
1083 pat,
1084 init: Some(syn::LocalInit {
1085 eq_token: Default::default(),
1086 expr: Box::new(init_expr),
1087 diverge: None,
1088 }),
1089 semi_token: Default::default(),
1090 };
1091
1092 let result = FunctionPointerVisitor::extract_pointer_assignment_data(&local);
1093 assert!(result.is_some());
1094 let (ident, _expr) = result.unwrap();
1095 assert_eq!(ident.to_string(), "func_ptr");
1096 }
1097
1098 #[test]
1099 fn test_analyze_function_pointer_assignment_complete_flow() {
1100 let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
1102 visitor.current_function = Some(FunctionId {
1103 file: std::path::PathBuf::from("test.rs"),
1104 name: "parent_func".to_string(),
1105 line: 10,
1106 });
1107
1108 let pat: Pat = parse_quote! { callback };
1109 let init_expr: Expr = parse_quote! { process_item };
1110 let local = Local {
1111 attrs: vec![],
1112 let_token: Default::default(),
1113 pat,
1114 init: Some(syn::LocalInit {
1115 eq_token: Default::default(),
1116 expr: Box::new(init_expr),
1117 diverge: None,
1118 }),
1119 semi_token: Default::default(),
1120 };
1121
1122 visitor.analyze_function_pointer_assignment(&local);
1123
1124 assert_eq!(visitor.function_pointers.len(), 1);
1125 let pointer_info = &visitor.function_pointers[0];
1126 assert_eq!(pointer_info.variable_name, "callback");
1127 assert_eq!(pointer_info.defining_function.name, "parent_func");
1128 assert!(!pointer_info.is_parameter);
1129 assert_eq!(pointer_info.possible_targets.len(), 1);
1130 }
1131
1132 #[test]
1133 fn test_analyze_function_pointer_assignment_with_complex_init() {
1134 let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
1136 visitor.current_function = Some(FunctionId {
1137 file: std::path::PathBuf::from("test.rs"),
1138 name: "setup_handlers".to_string(),
1139 line: 5,
1140 });
1141
1142 let pat: Pat = parse_quote! { handler };
1143 let init_expr: Expr = parse_quote! { |x| x + 1 };
1145 let local = Local {
1146 attrs: vec![],
1147 let_token: Default::default(),
1148 pat,
1149 init: Some(syn::LocalInit {
1150 eq_token: Default::default(),
1151 expr: Box::new(init_expr),
1152 diverge: None,
1153 }),
1154 semi_token: Default::default(),
1155 };
1156
1157 visitor.analyze_function_pointer_assignment(&local);
1158
1159 assert_eq!(visitor.function_pointers.len(), 1);
1161 let pointer_info = &visitor.function_pointers[0];
1162 assert_eq!(pointer_info.variable_name, "handler");
1163 assert!(pointer_info.possible_targets.is_empty());
1165 }
1166
1167 #[test]
1168 fn test_analyze_function_pointer_assignment_edge_cases() {
1169 let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
1171 visitor.current_function = Some(FunctionId {
1172 file: std::path::PathBuf::from("test.rs"),
1173 name: "test_func".to_string(),
1174 line: 1,
1175 });
1176
1177 let pat: Pat = parse_quote! { func_ptr };
1179 let init_expr: Expr = parse_quote! { some_func };
1180 let local = Local {
1181 attrs: vec![],
1182 let_token: Default::default(),
1183 pat,
1184 init: Some(syn::LocalInit {
1185 eq_token: Default::default(),
1186 expr: Box::new(init_expr),
1187 diverge: None,
1188 }),
1189 semi_token: Default::default(),
1190 };
1191
1192 visitor.analyze_function_pointer_assignment(&local);
1193 assert_eq!(visitor.function_pointers.len(), 1);
1194
1195 let pat2: Pat = parse_quote! { Point { x, y } };
1197 let init_expr2: Expr = parse_quote! { get_point() };
1198 let local2 = Local {
1199 attrs: vec![],
1200 let_token: Default::default(),
1201 pat: pat2,
1202 init: Some(syn::LocalInit {
1203 eq_token: Default::default(),
1204 expr: Box::new(init_expr2),
1205 diverge: None,
1206 }),
1207 semi_token: Default::default(),
1208 };
1209
1210 visitor.analyze_function_pointer_assignment(&local2);
1211 assert_eq!(visitor.function_pointers.len(), 1);
1213 }
1214}