use crate::analyzers::purity_detector::{is_known_pure_call, is_known_pure_method};
#[derive(Debug, Clone, PartialEq)]
pub enum CalleePurity {
KnownPure,
AnalyzedPure(f64),
AnalyzedImpure,
Unknown,
}
#[derive(Debug, Clone)]
pub struct CalleeEvidence {
pub callee_name: String,
pub callee_purity: CalleePurity,
}
pub fn resolve_callee_purity(
callee_name: &str,
receiver_type: Option<&str>,
cached_purity: Option<(bool, f64)>, ) -> CalleePurity {
if is_known_pure_call(callee_name, receiver_type) {
return CalleePurity::KnownPure;
}
if is_known_pure_method(callee_name) {
return CalleePurity::KnownPure;
}
if let Some((is_pure, confidence)) = cached_purity {
return if is_pure {
CalleePurity::AnalyzedPure(confidence)
} else {
CalleePurity::AnalyzedImpure
};
}
CalleePurity::Unknown
}
pub fn aggregate_callee_purity(callees: &[CalleeEvidence]) -> (bool, f64, Vec<String>) {
let mut is_pure = true;
let mut confidence = 1.0_f64;
let mut impure_reasons = Vec::new();
for callee in callees {
match &callee.callee_purity {
CalleePurity::KnownPure => {
confidence *= 1.02;
}
CalleePurity::AnalyzedPure(callee_conf) => {
confidence *= callee_conf;
}
CalleePurity::AnalyzedImpure => {
is_pure = false;
confidence = 0.95;
impure_reasons.push(format!("Calls impure function: {}", callee.callee_name));
}
CalleePurity::Unknown => {
confidence *= 0.9;
if confidence < 0.6 {
impure_reasons.push(format!("Calls unknown function: {}", callee.callee_name));
}
}
}
}
confidence = confidence.clamp(0.3, 1.0);
(is_pure, confidence, impure_reasons)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_known_pure_std_method() {
assert_eq!(
resolve_callee_purity("map", Some("Option"), None),
CalleePurity::KnownPure
);
assert_eq!(
resolve_callee_purity("and_then", Some("Result"), None),
CalleePurity::KnownPure
);
assert_eq!(
resolve_callee_purity("filter", Some("Iterator"), None),
CalleePurity::KnownPure
);
}
#[test]
fn test_known_pure_method_name_only() {
assert_eq!(
resolve_callee_purity("map", None, None),
CalleePurity::KnownPure
);
assert_eq!(
resolve_callee_purity("collect", None, None),
CalleePurity::KnownPure
);
assert_eq!(
resolve_callee_purity("len", None, None),
CalleePurity::KnownPure
);
}
#[test]
fn test_cached_pure_function() {
assert_eq!(
resolve_callee_purity("custom_func", None, Some((true, 0.95))),
CalleePurity::AnalyzedPure(0.95)
);
}
#[test]
fn test_cached_impure_function() {
assert_eq!(
resolve_callee_purity("io_func", None, Some((false, 0.8))),
CalleePurity::AnalyzedImpure
);
}
#[test]
fn test_unknown_function() {
assert_eq!(
resolve_callee_purity("external_crate::do_something", None, None),
CalleePurity::Unknown
);
}
#[test]
fn test_aggregate_all_pure() {
let callees = vec![
CalleeEvidence {
callee_name: "map".to_string(),
callee_purity: CalleePurity::KnownPure,
},
CalleeEvidence {
callee_name: "filter".to_string(),
callee_purity: CalleePurity::KnownPure,
},
CalleeEvidence {
callee_name: "collect".to_string(),
callee_purity: CalleePurity::KnownPure,
},
];
let (is_pure, confidence, reasons) = aggregate_callee_purity(&callees);
assert!(is_pure);
assert!((confidence - 1.0).abs() < 0.01);
assert!(reasons.is_empty());
}
#[test]
fn test_aggregate_with_impure() {
let callees = vec![
CalleeEvidence {
callee_name: "map".to_string(),
callee_purity: CalleePurity::KnownPure,
},
CalleeEvidence {
callee_name: "println".to_string(),
callee_purity: CalleePurity::AnalyzedImpure,
},
];
let (is_pure, _confidence, reasons) = aggregate_callee_purity(&callees);
assert!(!is_pure);
assert!(reasons.iter().any(|r| r.contains("println")));
}
#[test]
fn test_aggregate_with_unknown() {
let callees = vec![CalleeEvidence {
callee_name: "external_func".to_string(),
callee_purity: CalleePurity::Unknown,
}];
let (is_pure, confidence, _reasons) = aggregate_callee_purity(&callees);
assert!(is_pure);
assert!(confidence < 1.0);
}
}