use crate::priority::call_graph::FunctionId;
use anyhow::Result;
use im::{HashMap, HashSet, Vector};
use std::path::Path;
use syn::visit::Visit;
use syn::{Expr, ExprCall, ExprClosure, ExprPath, File, Ident, ItemFn, Local, Pat, PatIdent, Type};
#[derive(Debug, Clone)]
pub struct ClosureInfo {
pub closure_id: String,
pub containing_function: FunctionId,
pub line: usize,
pub calls: Vector<FunctionId>,
pub captures_variables: bool,
pub captured_by_ref: HashSet<String>,
pub captured_by_value: HashSet<String>,
}
#[derive(Debug, Clone)]
pub struct FunctionPointerInfo {
pub variable_name: String,
pub defining_function: FunctionId,
pub possible_targets: HashSet<FunctionId>,
pub line: usize,
pub is_parameter: bool,
}
#[derive(Debug, Clone)]
pub struct FunctionPointerCall {
pub caller: FunctionId,
pub pointer_id: String,
pub line: usize,
}
#[derive(Debug, Clone)]
pub struct HigherOrderFunctionCall {
pub caller: FunctionId,
pub hof_function: String,
pub function_arguments: Vector<FunctionId>,
pub line: usize,
}
#[derive(Debug, Clone)]
pub struct FunctionPointerTracker {
closures: HashMap<String, ClosureInfo>,
function_pointers: HashMap<String, FunctionPointerInfo>,
pointer_calls: Vector<FunctionPointerCall>,
hof_calls: Vector<HigherOrderFunctionCall>,
variable_to_pointer: HashMap<String, String>,
potential_pointer_targets: HashSet<FunctionId>,
}
impl FunctionPointerTracker {
pub fn new() -> Self {
Self {
closures: HashMap::new(),
function_pointers: HashMap::new(),
pointer_calls: Vector::new(),
hof_calls: Vector::new(),
variable_to_pointer: HashMap::new(),
potential_pointer_targets: HashSet::new(),
}
}
pub fn analyze_file(&mut self, file_path: &Path, ast: &File) -> Result<()> {
let mut visitor = FunctionPointerVisitor::new(file_path.to_path_buf());
visitor.visit_file(ast);
for closure in visitor.closures {
let closure_id = closure.closure_id.clone();
self.closures.insert(closure_id, closure);
}
for pointer in visitor.function_pointers {
let pointer_id = format!(
"{}_{}",
pointer.defining_function.name, pointer.variable_name
);
self.variable_to_pointer
.insert(pointer.variable_name.clone(), pointer_id.clone());
for target in &pointer.possible_targets {
self.potential_pointer_targets.insert(target.clone());
}
self.function_pointers.insert(pointer_id, pointer);
}
for call in visitor.pointer_calls {
self.pointer_calls.push_back(call);
}
for hof_call in visitor.hof_calls {
for func_arg in &hof_call.function_arguments {
self.potential_pointer_targets.insert(func_arg.clone());
}
self.hof_calls.push_back(hof_call);
}
Ok(())
}
pub fn get_function_pointer_calls(&self) -> Vector<FunctionPointerCall> {
self.pointer_calls.clone()
}
pub fn resolve_pointer_targets(&self, pointer_id: &str) -> Option<Vector<FunctionId>> {
self.function_pointers
.get(pointer_id)
.map(|pointer| pointer.possible_targets.iter().cloned().collect())
}
pub fn might_be_called_through_pointer(&self, func_id: &FunctionId) -> bool {
self.potential_pointer_targets.contains(func_id)
|| self
.closures
.values()
.any(|closure| closure.calls.contains(func_id))
}
pub fn get_higher_order_calls(&self) -> Vector<HigherOrderFunctionCall> {
self.hof_calls.clone()
}
pub fn get_statistics(&self) -> FunctionPointerStatistics {
let total_closures = self.closures.len();
let total_function_pointers = self.function_pointers.len();
let total_pointer_calls = self.pointer_calls.len();
let total_hof_calls = self.hof_calls.len();
let potential_targets = self.potential_pointer_targets.len();
FunctionPointerStatistics {
total_closures,
total_function_pointers,
total_pointer_calls,
total_hof_calls,
potential_targets,
}
}
pub fn get_definitely_used_functions(&self) -> HashSet<FunctionId> {
let mut used_functions = HashSet::new();
for closure in self.closures.values() {
for called_func in &closure.calls {
used_functions.insert(called_func.clone());
}
}
for hof_call in &self.hof_calls {
for func_arg in &hof_call.function_arguments {
used_functions.insert(func_arg.clone());
}
}
used_functions
}
}
#[derive(Debug, Clone)]
pub struct FunctionPointerStatistics {
pub total_closures: usize,
pub total_function_pointers: usize,
pub total_pointer_calls: usize,
pub total_hof_calls: usize,
pub potential_targets: usize,
}
struct FunctionPointerVisitor {
file_path: std::path::PathBuf,
closures: Vec<ClosureInfo>,
function_pointers: Vec<FunctionPointerInfo>,
pointer_calls: Vec<FunctionPointerCall>,
hof_calls: Vec<HigherOrderFunctionCall>,
current_function: Option<FunctionId>,
closure_counter: usize,
}
impl FunctionPointerVisitor {
fn new(file_path: std::path::PathBuf) -> Self {
Self {
file_path,
closures: Vec::new(),
function_pointers: Vec::new(),
pointer_calls: Vec::new(),
hof_calls: Vec::new(),
current_function: None,
closure_counter: 0,
}
}
fn get_line_number(&self, span: proc_macro2::Span) -> usize {
span.start().line
}
fn is_higher_order_function(&self, name: &str) -> bool {
matches!(
name,
"map"
| "filter"
| "fold"
| "reduce"
| "for_each"
| "find"
| "any"
| "all"
| "collect"
| "and_then"
| "or_else"
| "iter"
| "enumerate"
| "zip"
| "chain"
| "take"
| "skip"
)
}
fn extract_function_name_from_path(&self, path: &ExprPath) -> Option<String> {
if path.path.segments.len() == 1 {
Some(path.path.segments.first()?.ident.to_string())
} else {
let segments: Vec<String> = path
.path
.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect();
Some(segments.join("::"))
}
}
fn analyze_closure(&mut self, closure: &ExprClosure) {
if let Some(containing_function) = &self.current_function {
self.closure_counter += 1;
let closure_id = format!(
"{}_closure_{}",
containing_function.name, self.closure_counter
);
let line = self.get_line_number(closure.or1_token.span);
let mut closure_visitor = ClosureCallVisitor::new();
closure_visitor.visit_expr(&closure.body);
let closure_info = ClosureInfo {
closure_id,
containing_function: containing_function.clone(),
line,
calls: closure_visitor.function_calls.into_iter().collect(),
captures_variables: closure.capture.is_some(),
captured_by_ref: HashSet::new(), captured_by_value: HashSet::new(), };
self.closures.push(closure_info);
}
}
fn extract_pointer_assignment_data(local: &Local) -> Option<(&Ident, &Expr)> {
match &local.pat {
Pat::Ident(PatIdent { ident, .. }) => {
local.init.as_ref().map(|init| (ident, &*init.expr))
}
_ => None,
}
}
fn analyze_function_pointer_assignment(&mut self, local: &Local) {
let Some(current_func) = &self.current_function else {
return;
};
let Some((ident, init_expr)) = Self::extract_pointer_assignment_data(local) else {
return;
};
let var_name = ident.to_string();
let line = self.get_line_number(ident.span());
let possible_targets = self.extract_possible_targets(init_expr);
let pointer_info = FunctionPointerInfo {
variable_name: var_name,
defining_function: current_func.clone(),
possible_targets,
line,
is_parameter: false,
};
self.function_pointers.push(pointer_info);
}
fn extract_possible_targets(&self, expr: &Expr) -> HashSet<FunctionId> {
let mut possible_targets = HashSet::new();
if let Expr::Path(path) = expr {
if let Some(func_name) = self.extract_function_name_from_path(path) {
let target_func = FunctionId::new(
self.file_path.clone(),
func_name,
0, );
possible_targets.insert(target_func);
}
}
possible_targets
}
fn extract_direct_pointer_call(
&self,
call: &ExprCall,
caller: &FunctionId,
line: usize,
) -> Option<FunctionPointerCall> {
if let Expr::Path(path) = &*call.func {
self.extract_function_name_from_path(path)
.map(|func_name| FunctionPointerCall {
caller: caller.clone(),
pointer_id: func_name,
line,
})
} else {
None
}
}
fn extract_hof_call(
&self,
call: &ExprCall,
caller: &FunctionId,
line: usize,
) -> Option<HigherOrderFunctionCall> {
let path = match &*call.func {
Expr::Path(p) => p,
_ => return None,
};
let func_name = self.extract_function_name_from_path(path)?;
if !self.is_higher_order_function(&func_name) {
return None;
}
let function_arguments = self.extract_function_arguments(call);
(!function_arguments.is_empty()).then(|| HigherOrderFunctionCall {
caller: caller.clone(),
hof_function: func_name,
function_arguments,
line,
})
}
fn extract_function_arguments(&self, call: &ExprCall) -> Vector<FunctionId> {
let mut function_arguments = Vector::new();
for arg in &call.args {
if let Expr::Path(arg_path) = arg {
if let Some(arg_func_name) = self.extract_function_name_from_path(arg_path) {
let func_arg = FunctionId::new(self.file_path.clone(), arg_func_name, 0);
function_arguments.push_back(func_arg);
}
}
}
function_arguments
}
fn analyze_call_expression(&mut self, call: &ExprCall) {
if let Some(caller) = &self.current_function {
let line = self.get_line_number(call.paren_token.span.open());
if let Some(pointer_call) = self.extract_direct_pointer_call(call, caller, line) {
self.pointer_calls.push(pointer_call);
}
if let Some(hof_call) = self.extract_hof_call(call, caller, line) {
self.hof_calls.push(hof_call);
}
}
}
}
impl<'ast> Visit<'ast> for FunctionPointerVisitor {
fn visit_item_fn(&mut self, item: &'ast ItemFn) {
let func_name = item.sig.ident.to_string();
let line = self.get_line_number(item.sig.ident.span());
self.current_function = Some(FunctionId::new(self.file_path.clone(), func_name, line));
for param in &item.sig.inputs {
if let syn::FnArg::Typed(typed_param) = param {
if let Type::BareFn(_) = &*typed_param.ty {
if let Pat::Ident(PatIdent { ident, .. }) = &*typed_param.pat {
let param_name = ident.to_string();
let line = self.get_line_number(ident.span());
if let Some(current_func) = &self.current_function {
let pointer_info = FunctionPointerInfo {
variable_name: param_name,
defining_function: current_func.clone(),
possible_targets: HashSet::new(), line,
is_parameter: true,
};
self.function_pointers.push(pointer_info);
}
}
}
}
}
syn::visit::visit_item_fn(self, item);
self.current_function = None;
}
fn visit_expr_closure(&mut self, expr: &'ast ExprClosure) {
self.analyze_closure(expr);
syn::visit::visit_expr_closure(self, expr);
}
fn visit_local(&mut self, local: &'ast Local) {
self.analyze_function_pointer_assignment(local);
syn::visit::visit_local(self, local);
}
fn visit_expr_call(&mut self, call: &'ast ExprCall) {
self.analyze_call_expression(call);
syn::visit::visit_expr_call(self, call);
}
}
struct ClosureCallVisitor {
function_calls: Vec<FunctionId>,
}
impl ClosureCallVisitor {
fn new() -> Self {
Self {
function_calls: Vec::new(),
}
}
fn extract_function_name_from_path(&self, path: &ExprPath) -> Option<String> {
if path.path.segments.len() == 1 {
Some(path.path.segments.first()?.ident.to_string())
} else {
let segments: Vec<String> = path
.path
.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect();
Some(segments.join("::"))
}
}
}
impl<'ast> Visit<'ast> for ClosureCallVisitor {
fn visit_expr_call(&mut self, call: &'ast ExprCall) {
if let Expr::Path(path) = &*call.func {
if let Some(func_name) = self.extract_function_name_from_path(path) {
let func_id = FunctionId::new(
std::path::PathBuf::new(), func_name,
0,
);
self.function_calls.push(func_id);
}
}
syn::visit::visit_expr_call(self, call);
}
fn visit_expr_method_call(&mut self, call: &'ast syn::ExprMethodCall) {
let method_name = call.method.to_string();
let func_id = FunctionId::new(std::path::PathBuf::new(), method_name, 0);
self.function_calls.push(func_id);
syn::visit::visit_expr_method_call(self, call);
}
}
impl Default for FunctionPointerTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_extract_direct_pointer_call() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let caller = FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
);
let call: ExprCall = parse_quote! { func_ptr(42) };
let result = visitor.extract_direct_pointer_call(&call, &caller, 10);
assert!(result.is_some());
let pointer_call = result.unwrap();
assert_eq!(pointer_call.pointer_id, "func_ptr");
assert_eq!(pointer_call.line, 10);
assert_eq!(pointer_call.caller.name, "test_func");
}
#[test]
fn test_extract_direct_pointer_call_with_method_call() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let caller = FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
);
let call: ExprCall = parse_quote! { compute(42) };
let mut call = call;
call.func = Box::new(parse_quote! { 42 }); let result = visitor.extract_direct_pointer_call(&call, &caller, 10);
assert!(result.is_none());
}
#[test]
fn test_extract_hof_call_map() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let caller = FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
);
let call: ExprCall = parse_quote! { map(process_item) };
let result = visitor.extract_hof_call(&call, &caller, 15);
assert!(result.is_some());
let hof_call = result.unwrap();
assert_eq!(hof_call.hof_function, "map");
assert_eq!(hof_call.function_arguments.len(), 1);
assert_eq!(hof_call.function_arguments[0].name, "process_item");
assert_eq!(hof_call.line, 15);
}
#[test]
fn test_extract_hof_call_filter() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let caller = FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
);
let call: ExprCall = parse_quote! { filter(is_valid) };
let result = visitor.extract_hof_call(&call, &caller, 20);
assert!(result.is_some());
let hof_call = result.unwrap();
assert_eq!(hof_call.hof_function, "filter");
assert_eq!(hof_call.function_arguments.len(), 1);
assert_eq!(hof_call.function_arguments[0].name, "is_valid");
}
#[test]
fn test_extract_hof_call_non_hof() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let caller = FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
);
let call: ExprCall = parse_quote! { regular_func(arg) };
let result = visitor.extract_hof_call(&call, &caller, 25);
assert!(result.is_none());
}
#[test]
fn test_extract_hof_call_empty_arguments() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let caller = FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
);
let call: ExprCall = parse_quote! { map() };
let result = visitor.extract_hof_call(&call, &caller, 30);
assert!(result.is_none());
}
#[test]
fn test_extract_hof_call_closure_argument() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let caller = FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
);
let call: ExprCall = parse_quote! { map(|x| x + 1) };
let result = visitor.extract_hof_call(&call, &caller, 35);
assert!(result.is_none());
}
#[test]
fn test_extract_hof_call_nested_path() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let caller = FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
);
let call: ExprCall = parse_quote! { filter(module::is_valid) };
let result = visitor.extract_hof_call(&call, &caller, 40);
assert!(result.is_some());
let hof_call = result.unwrap();
assert_eq!(hof_call.hof_function, "filter");
assert_eq!(hof_call.function_arguments.len(), 1);
assert_eq!(hof_call.function_arguments[0].name, "module::is_valid");
}
#[test]
fn test_extract_function_arguments() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let call: ExprCall = parse_quote! { fold(initial, combine_func) };
let args = visitor.extract_function_arguments(&call);
assert_eq!(args.len(), 2);
assert_eq!(args[0].name, "initial");
assert_eq!(args[1].name, "combine_func");
}
#[test]
fn test_extract_function_arguments_mixed() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let call: ExprCall = parse_quote! { process(42, handler, "string") };
let args = visitor.extract_function_arguments(&call);
assert_eq!(args.len(), 1);
assert_eq!(args[0].name, "handler");
}
#[test]
fn test_extract_function_arguments_empty() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let call: ExprCall = parse_quote! { compute(42, "string", true) };
let args = visitor.extract_function_arguments(&call);
assert!(args.is_empty());
}
#[test]
fn test_is_higher_order_function() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
assert!(visitor.is_higher_order_function("map"));
assert!(visitor.is_higher_order_function("filter"));
assert!(visitor.is_higher_order_function("fold"));
assert!(visitor.is_higher_order_function("for_each"));
assert!(visitor.is_higher_order_function("find"));
assert!(visitor.is_higher_order_function("any"));
assert!(visitor.is_higher_order_function("all"));
assert!(!visitor.is_higher_order_function("process"));
assert!(!visitor.is_higher_order_function("compute"));
assert!(!visitor.is_higher_order_function("regular_func"));
}
#[test]
fn test_analyze_call_expression_integration() {
let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
visitor.current_function = Some(FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
));
let call: ExprCall = parse_quote! { callback() };
visitor.analyze_call_expression(&call);
assert_eq!(visitor.pointer_calls.len(), 1);
assert_eq!(visitor.pointer_calls[0].pointer_id, "callback");
let hof_call: ExprCall = parse_quote! { map(transform) };
visitor.analyze_call_expression(&hof_call);
assert_eq!(visitor.hof_calls.len(), 1);
assert_eq!(visitor.hof_calls[0].hof_function, "map");
}
#[test]
fn test_analyze_call_expression_no_current_function() {
let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let call: ExprCall = parse_quote! { func() };
visitor.analyze_call_expression(&call);
assert!(visitor.pointer_calls.is_empty());
assert!(visitor.hof_calls.is_empty());
}
#[test]
fn test_extract_possible_targets_with_path() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let expr: Expr = parse_quote! { my_function };
let targets = visitor.extract_possible_targets(&expr);
assert_eq!(targets.len(), 1);
let target = targets.iter().next().unwrap();
assert_eq!(target.name, "my_function");
assert_eq!(target.file, std::path::PathBuf::from("test.rs"));
}
#[test]
fn test_extract_possible_targets_with_qualified_path() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let expr: Expr = parse_quote! { module::submodule::function };
let targets = visitor.extract_possible_targets(&expr);
assert_eq!(targets.len(), 1);
let target = targets.iter().next().unwrap();
assert_eq!(target.name, "module::submodule::function");
}
#[test]
fn test_extract_possible_targets_non_path() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let expr: Expr = parse_quote! { 42 };
let targets = visitor.extract_possible_targets(&expr);
assert!(targets.is_empty());
}
#[test]
fn test_extract_possible_targets_closure() {
let visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let expr: Expr = parse_quote! { |x| x + 1 };
let targets = visitor.extract_possible_targets(&expr);
assert!(targets.is_empty());
}
#[test]
fn test_analyze_function_pointer_assignment_complete() {
let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
visitor.current_function = Some(FunctionId::new(
std::path::PathBuf::from("test.rs"),
"outer_func".to_string(),
1,
));
let pat = Pat::Ident(PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: parse_quote! { func_ptr },
subpat: None,
});
let init_expr: Expr = parse_quote! { my_function };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr),
diverge: None,
}),
semi_token: Default::default(),
};
visitor.analyze_function_pointer_assignment(&local);
assert_eq!(visitor.function_pointers.len(), 1);
let pointer = &visitor.function_pointers[0];
assert_eq!(pointer.variable_name, "func_ptr");
assert!(!pointer.is_parameter);
assert_eq!(pointer.possible_targets.len(), 1);
}
#[test]
fn test_analyze_function_pointer_assignment_no_init() {
let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
visitor.current_function = Some(FunctionId::new(
std::path::PathBuf::from("test.rs"),
"outer_func".to_string(),
1,
));
let pat = Pat::Ident(PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: parse_quote! { func_ptr },
subpat: None,
});
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: None, semi_token: Default::default(),
};
visitor.analyze_function_pointer_assignment(&local);
assert!(visitor.function_pointers.is_empty());
}
#[test]
fn test_analyze_function_pointer_assignment_non_ident_pattern() {
let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
visitor.current_function = Some(FunctionId::new(
std::path::PathBuf::from("test.rs"),
"outer_func".to_string(),
1,
));
let pat: Pat = parse_quote! { (a, b) };
let init_expr: Expr = parse_quote! { get_tuple() };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr),
diverge: None,
}),
semi_token: Default::default(),
};
visitor.analyze_function_pointer_assignment(&local);
assert!(visitor.function_pointers.is_empty());
}
#[test]
fn test_analyze_function_pointer_assignment_no_current_function() {
let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
let pat = Pat::Ident(PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: parse_quote! { func_ptr },
subpat: None,
});
let init_expr: Expr = parse_quote! { my_function };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr),
diverge: None,
}),
semi_token: Default::default(),
};
visitor.analyze_function_pointer_assignment(&local);
assert!(visitor.function_pointers.is_empty());
}
#[test]
fn test_extract_pointer_assignment_data_with_ident() {
let pat: Pat = parse_quote! { func_ptr };
let init_expr: Expr = parse_quote! { my_function };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr),
diverge: None,
}),
semi_token: Default::default(),
};
let result = FunctionPointerVisitor::extract_pointer_assignment_data(&local);
assert!(result.is_some());
let (ident, _expr) = result.unwrap();
assert_eq!(ident.to_string(), "func_ptr");
}
#[test]
fn test_extract_pointer_assignment_data_without_init() {
let pat: Pat = parse_quote! { func_ptr };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: None,
semi_token: Default::default(),
};
let result = FunctionPointerVisitor::extract_pointer_assignment_data(&local);
assert!(result.is_none());
}
#[test]
fn test_extract_pointer_assignment_data_with_non_ident_pattern() {
let pat: Pat = parse_quote! { (a, b) };
let init_expr: Expr = parse_quote! { (func1, func2) };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr),
diverge: None,
}),
semi_token: Default::default(),
};
let result = FunctionPointerVisitor::extract_pointer_assignment_data(&local);
assert!(result.is_none());
}
#[test]
fn test_extract_pointer_assignment_data_with_mut_ident() {
let pat: Pat = parse_quote! { mut func_ptr };
let init_expr: Expr = parse_quote! { my_function };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr),
diverge: None,
}),
semi_token: Default::default(),
};
let result = FunctionPointerVisitor::extract_pointer_assignment_data(&local);
assert!(result.is_some());
let (ident, _expr) = result.unwrap();
assert_eq!(ident.to_string(), "func_ptr");
}
#[test]
fn test_analyze_function_pointer_assignment_complete_flow() {
let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
visitor.current_function = Some(FunctionId::new(
std::path::PathBuf::from("test.rs"),
"parent_func".to_string(),
10,
));
let pat: Pat = parse_quote! { callback };
let init_expr: Expr = parse_quote! { process_item };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr),
diverge: None,
}),
semi_token: Default::default(),
};
visitor.analyze_function_pointer_assignment(&local);
assert_eq!(visitor.function_pointers.len(), 1);
let pointer_info = &visitor.function_pointers[0];
assert_eq!(pointer_info.variable_name, "callback");
assert_eq!(pointer_info.defining_function.name, "parent_func");
assert!(!pointer_info.is_parameter);
assert_eq!(pointer_info.possible_targets.len(), 1);
}
#[test]
fn test_analyze_function_pointer_assignment_with_complex_init() {
let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
visitor.current_function = Some(FunctionId::new(
std::path::PathBuf::from("test.rs"),
"setup_handlers".to_string(),
5,
));
let pat: Pat = parse_quote! { handler };
let init_expr: Expr = parse_quote! { |x| x + 1 };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr),
diverge: None,
}),
semi_token: Default::default(),
};
visitor.analyze_function_pointer_assignment(&local);
assert_eq!(visitor.function_pointers.len(), 1);
let pointer_info = &visitor.function_pointers[0];
assert_eq!(pointer_info.variable_name, "handler");
assert!(pointer_info.possible_targets.is_empty());
}
#[test]
fn test_analyze_function_pointer_assignment_edge_cases() {
let mut visitor = FunctionPointerVisitor::new(std::path::PathBuf::from("test.rs"));
visitor.current_function = Some(FunctionId::new(
std::path::PathBuf::from("test.rs"),
"test_func".to_string(),
1,
));
let pat: Pat = parse_quote! { func_ptr };
let init_expr: Expr = parse_quote! { some_func };
let local = Local {
attrs: vec![],
let_token: Default::default(),
pat,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr),
diverge: None,
}),
semi_token: Default::default(),
};
visitor.analyze_function_pointer_assignment(&local);
assert_eq!(visitor.function_pointers.len(), 1);
let pat2: Pat = parse_quote! { Point { x, y } };
let init_expr2: Expr = parse_quote! { get_point() };
let local2 = Local {
attrs: vec![],
let_token: Default::default(),
pat: pat2,
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(init_expr2),
diverge: None,
}),
semi_token: Default::default(),
};
visitor.analyze_function_pointer_assignment(&local2);
assert_eq!(visitor.function_pointers.len(), 1);
}
}