use crate::core::FunctionMetrics;
use crate::priority::call_graph::{CallGraph, FunctionId};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PurityCategory {
Pure,
ImpureLocal,
Impure,
}
pub fn analyze_local_purity(_metric: &FunctionMetrics) -> PurityCategory {
PurityCategory::Pure
}
pub fn propagate_purity(
initial: HashMap<FunctionId, PurityCategory>,
graph: &CallGraph,
max_iterations: usize,
) -> HashMap<FunctionId, PurityCategory> {
let mut purity = initial;
for _ in 0..max_iterations {
let updated = propagate_one_step(&purity, graph);
if updated == purity {
break; }
purity = updated;
}
purity
}
fn propagate_one_step(
current: &HashMap<FunctionId, PurityCategory>,
graph: &CallGraph,
) -> HashMap<FunctionId, PurityCategory> {
current
.iter()
.map(|(id, category)| {
let callees = graph.get_callees(id);
let updated = refine_purity(*category, &callees, current);
(id.clone(), updated)
})
.collect()
}
fn refine_purity(
current: PurityCategory,
callees: &[FunctionId],
purity_map: &HashMap<FunctionId, PurityCategory>,
) -> PurityCategory {
if current == PurityCategory::Impure {
return PurityCategory::Impure;
}
let has_impure_callee = callees
.iter()
.filter_map(|id| purity_map.get(id))
.any(|p| *p == PurityCategory::Impure);
if has_impure_callee {
PurityCategory::Impure
} else {
current
}
}
pub fn analyze_purity(
metrics: &[FunctionMetrics],
call_graph: &CallGraph,
) -> crate::pipeline::data::PurityScores {
use crate::pipeline::data::PurityScores;
let mut initial = HashMap::new();
let mut func_ids = Vec::new();
for metric in metrics {
let func_id = FunctionId::new(metric.file.clone(), metric.name.clone(), metric.line);
let category = analyze_local_purity(metric);
initial.insert(func_id.clone(), category);
func_ids.push((func_id, metric.name.clone()));
}
let propagated = propagate_purity(initial, call_graph, 10);
let mut scores = HashMap::new();
let mut pure_functions = Vec::new();
let mut io_functions = Vec::new();
for (func_id, name) in func_ids {
if let Some(category) = propagated.get(&func_id) {
let score = match category {
PurityCategory::Pure => 1.0,
PurityCategory::ImpureLocal => 0.5,
PurityCategory::Impure => 0.0,
};
scores.insert(name.clone(), score);
match category {
PurityCategory::Pure => pure_functions.push(name),
PurityCategory::Impure => io_functions.push(name),
PurityCategory::ImpureLocal => {}
}
}
}
PurityScores {
scores,
pure_functions,
io_functions,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn test_func_id(name: &str) -> FunctionId {
FunctionId::new(PathBuf::from("test.rs"), name.to_string(), 1)
}
#[test]
fn test_propagate_purity_empty() {
let initial = HashMap::new();
let graph = CallGraph::new();
let result = propagate_purity(initial, &graph, 10);
assert!(result.is_empty());
}
#[test]
fn test_propagate_purity_converges() {
let mut initial = HashMap::new();
initial.insert(test_func_id("foo"), PurityCategory::Pure);
initial.insert(test_func_id("bar"), PurityCategory::Pure);
let graph = CallGraph::new();
let result = propagate_purity(initial.clone(), &graph, 10);
assert_eq!(
result.get(&test_func_id("foo")),
Some(&PurityCategory::Pure)
);
assert_eq!(
result.get(&test_func_id("bar")),
Some(&PurityCategory::Pure)
);
}
}