use once_cell::sync::Lazy;
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CallPurity {
Pure,
Impure,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum UnknownCallBehavior {
#[default]
Conservative,
Optimistic,
}
pub static KNOWN_PURE_FUNCTIONS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
let mut set = HashSet::new();
set.insert("std::cmp::min");
set.insert("std::cmp::max");
set.insert("std::cmp::Ord::cmp");
set.insert("std::cmp::PartialOrd::partial_cmp");
set.insert("i32::abs");
set.insert("i64::abs");
set.insert("f32::abs");
set.insert("f64::abs");
set.insert("f32::sqrt");
set.insert("f64::sqrt");
set.insert("f32::sin");
set.insert("f64::sin");
set.insert("f32::cos");
set.insert("f64::cos");
set.insert("f32::floor");
set.insert("f64::floor");
set.insert("f32::ceil");
set.insert("f64::ceil");
set.insert("f32::round");
set.insert("f64::round");
set.insert("i32::saturating_add");
set.insert("i32::saturating_sub");
set.insert("i64::saturating_add");
set.insert("i64::saturating_sub");
set.insert("i32::wrapping_add");
set.insert("i32::wrapping_sub");
set.insert("usize::saturating_add");
set.insert("usize::saturating_sub");
set.insert("Option::is_some");
set.insert("Option::is_none");
set.insert("Option::as_ref");
set.insert("Option::as_mut");
set.insert("Option::unwrap_or");
set.insert("Option::unwrap_or_else");
set.insert("Option::unwrap_or_default");
set.insert("Option::map");
set.insert("Option::and_then");
set.insert("Option::or");
set.insert("Option::or_else");
set.insert("Option::filter");
set.insert("Option::flatten");
set.insert("Option::copied");
set.insert("Option::cloned");
set.insert("Option::zip");
set.insert("Option::ok_or");
set.insert("Option::ok_or_else");
set.insert("Result::is_ok");
set.insert("Result::is_err");
set.insert("Result::as_ref");
set.insert("Result::map");
set.insert("Result::map_err");
set.insert("Result::and_then");
set.insert("Result::unwrap_or");
set.insert("Result::unwrap_or_else");
set.insert("Result::unwrap_or_default");
set.insert("Result::ok");
set.insert("Result::err");
set.insert("Result::copied");
set.insert("Result::cloned");
set.insert("str::len");
set.insert("str::is_empty");
set.insert("str::trim");
set.insert("str::trim_start");
set.insert("str::trim_end");
set.insert("str::to_lowercase");
set.insert("str::to_uppercase");
set.insert("str::contains");
set.insert("str::starts_with");
set.insert("str::ends_with");
set.insert("str::split");
set.insert("str::chars");
set.insert("str::bytes");
set.insert("str::lines");
set.insert("str::split_whitespace");
set.insert("str::replace");
set.insert("str::parse");
set.insert("String::len");
set.insert("String::is_empty");
set.insert("String::as_str");
set.insert("String::as_bytes");
set.insert("String::capacity");
set.insert("String::chars");
set.insert("String::bytes");
set.insert("Vec::len");
set.insert("Vec::is_empty");
set.insert("Vec::capacity");
set.insert("Vec::first");
set.insert("Vec::last");
set.insert("Vec::get");
set.insert("Vec::contains");
set.insert("Vec::iter");
set.insert("Vec::as_slice");
set.insert("Vec::binary_search");
set.insert("Vec::starts_with");
set.insert("Vec::ends_with");
set.insert("[T]::len");
set.insert("[T]::is_empty");
set.insert("[T]::first");
set.insert("[T]::last");
set.insert("[T]::get");
set.insert("[T]::contains");
set.insert("[T]::iter");
set.insert("[T]::split");
set.insert("[T]::chunks");
set.insert("[T]::windows");
set.insert("[T]::binary_search");
set.insert("HashMap::len");
set.insert("HashMap::is_empty");
set.insert("HashMap::contains_key");
set.insert("HashMap::get");
set.insert("HashMap::keys");
set.insert("HashMap::values");
set.insert("HashMap::iter");
set.insert("HashMap::capacity");
set.insert("HashSet::len");
set.insert("HashSet::is_empty");
set.insert("HashSet::contains");
set.insert("HashSet::get");
set.insert("HashSet::iter");
set.insert("HashSet::capacity");
set.insert("HashSet::is_subset");
set.insert("HashSet::is_superset");
set.insert("HashSet::is_disjoint");
set.insert("BTreeMap::len");
set.insert("BTreeMap::is_empty");
set.insert("BTreeMap::contains_key");
set.insert("BTreeMap::get");
set.insert("BTreeMap::keys");
set.insert("BTreeMap::values");
set.insert("BTreeMap::iter");
set.insert("BTreeMap::range");
set.insert("BTreeMap::first_key_value");
set.insert("BTreeMap::last_key_value");
set.insert("Iterator::count");
set.insert("Iterator::map");
set.insert("Iterator::filter");
set.insert("Iterator::filter_map");
set.insert("Iterator::flat_map");
set.insert("Iterator::flatten");
set.insert("Iterator::take");
set.insert("Iterator::skip");
set.insert("Iterator::zip");
set.insert("Iterator::enumerate");
set.insert("Iterator::peekable");
set.insert("Iterator::chain");
set.insert("Iterator::fold");
set.insert("Iterator::reduce");
set.insert("Iterator::all");
set.insert("Iterator::any");
set.insert("Iterator::find");
set.insert("Iterator::position");
set.insert("Iterator::sum");
set.insert("Iterator::product");
set.insert("Iterator::collect");
set.insert("Iterator::nth");
set.insert("Iterator::last");
set.insert("Iterator::min");
set.insert("Iterator::max");
set.insert("Iterator::min_by");
set.insert("Iterator::max_by");
set.insert("Iterator::min_by_key");
set.insert("Iterator::max_by_key");
set.insert("Iterator::rev");
set.insert("Iterator::cloned");
set.insert("Iterator::copied");
set.insert("Iterator::by_ref");
set.insert("Iterator::step_by");
set.insert("Iterator::take_while");
set.insert("Iterator::skip_while");
set.insert("Iterator::partition");
set.insert("Iterator::unzip");
set.insert("Iterator::inspect");
set.insert("Iterator::fuse");
set.insert("Iterator::cycle");
set.insert("Clone::clone");
set.insert("ToOwned::to_owned");
set.insert("Display::fmt");
set.insert("Debug::fmt");
set.insert("ToString::to_string");
set.insert("From::from");
set.insert("Into::into");
set.insert("TryFrom::try_from");
set.insert("TryInto::try_into");
set.insert("AsRef::as_ref");
set.insert("AsMut::as_mut");
set.insert("Deref::deref");
set.insert("DerefMut::deref_mut");
set.insert("Borrow::borrow");
set.insert("BorrowMut::borrow_mut");
set.insert("Default::default");
set.insert("PartialEq::eq");
set.insert("PartialEq::ne");
set.insert("Eq::eq");
set.insert("PartialOrd::partial_cmp");
set.insert("PartialOrd::lt");
set.insert("PartialOrd::le");
set.insert("PartialOrd::gt");
set.insert("PartialOrd::ge");
set.insert("Ord::cmp");
set.insert("Ord::max");
set.insert("Ord::min");
set.insert("Ord::clamp");
set.insert("Hash::hash");
set.insert("Index::index");
set.insert("IndexMut::index_mut");
set.insert("Path::exists");
set.insert("Path::is_file");
set.insert("Path::is_dir");
set.insert("Path::extension");
set.insert("Path::file_name");
set.insert("Path::file_stem");
set.insert("Path::parent");
set.insert("Path::join");
set.insert("Path::with_extension");
set.insert("Path::to_str");
set.insert("Path::to_string_lossy");
set.insert("Path::display");
set.insert("Path::canonicalize");
set.insert("Path::components");
set.insert("PathBuf::as_path");
set.insert("PathBuf::push");
set.insert("PathBuf::set_extension");
set
});
pub static KNOWN_IMPURE_FUNCTIONS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
let mut set = HashSet::new();
set.insert("std::io::Read::read");
set.insert("std::io::Read::read_to_string");
set.insert("std::io::Read::read_to_end");
set.insert("std::io::Write::write");
set.insert("std::io::Write::write_all");
set.insert("std::io::Write::flush");
set.insert("std::fs::read");
set.insert("std::fs::read_to_string");
set.insert("std::fs::write");
set.insert("std::fs::File::open");
set.insert("std::fs::File::create");
set.insert("std::fs::remove_file");
set.insert("std::fs::remove_dir");
set.insert("std::fs::remove_dir_all");
set.insert("std::fs::create_dir");
set.insert("std::fs::create_dir_all");
set.insert("std::fs::rename");
set.insert("std::fs::copy");
set.insert("std::fs::metadata");
set.insert("std::fs::read_dir");
set.insert("println");
set.insert("print");
set.insert("eprintln");
set.insert("eprint");
set.insert("dbg");
set.insert("std::net::TcpStream::connect");
set.insert("std::net::TcpListener::bind");
set.insert("std::net::UdpSocket::bind");
set.insert("std::net::UdpSocket::send");
set.insert("std::net::UdpSocket::recv");
set.insert("rand::random");
set.insert("rand::thread_rng");
set.insert("rand::Rng::gen");
set.insert("std::time::Instant::now");
set.insert("std::time::SystemTime::now");
set.insert("Vec::push");
set.insert("Vec::pop");
set.insert("Vec::insert");
set.insert("Vec::remove");
set.insert("Vec::clear");
set.insert("Vec::truncate");
set.insert("Vec::extend");
set.insert("Vec::append");
set.insert("Vec::drain");
set.insert("Vec::retain");
set.insert("Vec::resize");
set.insert("Vec::swap_remove");
set.insert("Vec::dedup");
set.insert("Vec::sort");
set.insert("Vec::sort_by");
set.insert("Vec::sort_by_key");
set.insert("Vec::reverse");
set.insert("HashMap::insert");
set.insert("HashMap::remove");
set.insert("HashMap::clear");
set.insert("HashMap::drain");
set.insert("HashMap::retain");
set.insert("HashMap::entry");
set.insert("HashSet::insert");
set.insert("HashSet::remove");
set.insert("HashSet::clear");
set.insert("HashSet::drain");
set.insert("HashSet::retain");
set.insert("BTreeMap::insert");
set.insert("BTreeMap::remove");
set.insert("BTreeMap::clear");
set.insert("BTreeMap::pop_first");
set.insert("BTreeMap::pop_last");
set.insert("String::push");
set.insert("String::push_str");
set.insert("String::pop");
set.insert("String::insert");
set.insert("String::insert_str");
set.insert("String::remove");
set.insert("String::clear");
set.insert("String::truncate");
set.insert("String::retain");
set.insert("String::drain");
set.insert("RefCell::borrow_mut");
set.insert("RefCell::replace");
set.insert("RefCell::swap");
set.insert("Cell::set");
set.insert("Cell::replace");
set.insert("Cell::swap");
set.insert("Mutex::lock");
set.insert("RwLock::write");
set.insert("RwLock::read");
set.insert("std::thread::spawn");
set.insert("std::thread::sleep");
set.insert("JoinHandle::join");
set.insert("Sender::send");
set.insert("Receiver::recv");
set.insert("Receiver::try_recv");
set.insert("std::env::var");
set.insert("std::env::set_var");
set.insert("std::env::remove_var");
set.insert("std::env::args");
set.insert("std::env::current_dir");
set.insert("std::env::set_current_dir");
set.insert("std::process::Command::new");
set.insert("std::process::Command::spawn");
set.insert("std::process::Command::output");
set.insert("std::process::exit");
set.insert("std::process::abort");
set
});
pub const PURE_METHOD_PATTERNS: &[&str] = &[
"len",
"is_empty",
"is_some",
"is_none",
"is_ok",
"is_err",
"as_ref",
"as_mut",
"as_str",
"as_slice",
"as_bytes",
"as_path",
"get",
"first",
"last",
"contains",
"contains_key",
"clone",
"to_owned",
"to_string",
"to_lowercase",
"to_uppercase",
"map",
"filter",
"and_then",
"or_else",
"unwrap_or",
"unwrap_or_default",
"unwrap_or_else",
"iter",
"into_iter",
"chars",
"bytes",
"lines",
"trim",
"trim_start",
"trim_end",
"abs",
"sqrt",
"sin",
"cos",
"floor",
"ceil",
"round",
"min",
"max",
"clamp",
"cmp",
"partial_cmp",
"eq",
"ne",
"lt",
"le",
"gt",
"ge",
"copied",
"cloned",
"flatten",
"zip",
"enumerate",
"rev",
"take",
"skip",
"fold",
"reduce",
"all",
"any",
"find",
"position",
"sum",
"product",
"collect",
"count",
"nth",
"split",
"chunks",
"windows",
"starts_with",
"ends_with",
"binary_search",
"is_subset",
"is_superset",
"is_disjoint",
"capacity",
"keys",
"values",
"from",
"into",
"default",
"hash",
"index",
"deref",
"borrow",
"display",
"fmt",
"parse",
];
pub const IMPURE_METHOD_PATTERNS: &[&str] = &[
"push",
"pop",
"insert",
"remove",
"clear",
"truncate",
"extend",
"append",
"drain",
"retain",
"resize",
"swap_remove",
"dedup",
"sort",
"sort_by",
"sort_by_key",
"reverse",
"read",
"read_to_string",
"read_to_end",
"write",
"write_all",
"flush",
"connect",
"bind",
"listen",
"accept",
"send",
"recv",
"spawn",
"join",
"sleep",
"lock",
"unlock",
"now",
"elapsed",
"random",
"gen",
"shuffle",
"set",
"replace",
"swap",
"borrow_mut",
"entry",
"pop_first",
"pop_last",
];
pub fn classify_call(func_name: &str) -> CallPurity {
if is_known_pure(func_name) {
return CallPurity::Pure;
}
if is_known_impure(func_name) {
return CallPurity::Impure;
}
CallPurity::Unknown
}
pub fn is_known_pure(func_name: &str) -> bool {
if KNOWN_PURE_FUNCTIONS.contains(func_name) {
return true;
}
let method_name = func_name.rsplit("::").next().unwrap_or(func_name);
PURE_METHOD_PATTERNS.contains(&method_name)
}
pub fn is_known_impure(func_name: &str) -> bool {
if KNOWN_IMPURE_FUNCTIONS.contains(func_name) {
return true;
}
let method_name = func_name.rsplit("::").next().unwrap_or(func_name);
IMPURE_METHOD_PATTERNS.contains(&method_name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_classify_std_pure_functions() {
assert_eq!(classify_call("Vec::len"), CallPurity::Pure);
assert_eq!(classify_call("Option::map"), CallPurity::Pure);
assert_eq!(classify_call("Option::is_some"), CallPurity::Pure);
assert_eq!(classify_call("Result::is_ok"), CallPurity::Pure);
assert_eq!(classify_call("Iterator::filter"), CallPurity::Pure);
assert_eq!(classify_call("str::trim"), CallPurity::Pure);
assert_eq!(classify_call("Clone::clone"), CallPurity::Pure);
assert_eq!(classify_call("Default::default"), CallPurity::Pure);
}
#[test]
fn test_classify_std_impure_functions() {
assert_eq!(classify_call("Vec::push"), CallPurity::Impure);
assert_eq!(classify_call("Vec::pop"), CallPurity::Impure);
assert_eq!(classify_call("HashMap::insert"), CallPurity::Impure);
assert_eq!(classify_call("std::fs::read"), CallPurity::Impure);
assert_eq!(classify_call("println"), CallPurity::Impure);
assert_eq!(classify_call("std::time::Instant::now"), CallPurity::Impure);
}
#[test]
fn test_classify_unknown_functions() {
assert_eq!(classify_call("my_custom_func"), CallPurity::Unknown);
assert_eq!(
classify_call("unknown_module::mystery_func"),
CallPurity::Unknown
);
assert_eq!(classify_call("foo"), CallPurity::Unknown);
}
#[test]
fn test_pure_method_pattern_matching() {
assert_eq!(classify_call("len"), CallPurity::Pure);
assert_eq!(classify_call("is_empty"), CallPurity::Pure);
assert_eq!(classify_call("clone"), CallPurity::Pure);
assert_eq!(classify_call("to_string"), CallPurity::Pure);
assert_eq!(classify_call("iter"), CallPurity::Pure);
assert_eq!(classify_call("map"), CallPurity::Pure);
assert_eq!(classify_call("filter"), CallPurity::Pure);
assert_eq!(classify_call("contains"), CallPurity::Pure);
}
#[test]
fn test_impure_method_pattern_matching() {
assert_eq!(classify_call("push"), CallPurity::Impure);
assert_eq!(classify_call("pop"), CallPurity::Impure);
assert_eq!(classify_call("insert"), CallPurity::Impure);
assert_eq!(classify_call("remove"), CallPurity::Impure);
assert_eq!(classify_call("clear"), CallPurity::Impure);
assert_eq!(classify_call("write"), CallPurity::Impure);
assert_eq!(classify_call("now"), CallPurity::Impure);
}
#[test]
fn test_qualified_method_pattern_matching() {
assert_eq!(classify_call("MyType::len"), CallPurity::Pure);
assert_eq!(classify_call("custom::module::is_empty"), CallPurity::Pure);
assert_eq!(classify_call("MyVec::push"), CallPurity::Impure);
assert_eq!(classify_call("custom::module::clear"), CallPurity::Impure);
}
#[test]
fn test_call_purity_enum_equality() {
assert_eq!(CallPurity::Pure, CallPurity::Pure);
assert_eq!(CallPurity::Impure, CallPurity::Impure);
assert_eq!(CallPurity::Unknown, CallPurity::Unknown);
assert_ne!(CallPurity::Pure, CallPurity::Impure);
assert_ne!(CallPurity::Pure, CallPurity::Unknown);
assert_ne!(CallPurity::Impure, CallPurity::Unknown);
}
#[test]
fn test_unknown_call_behavior_default() {
let default_behavior = UnknownCallBehavior::default();
assert_eq!(default_behavior, UnknownCallBehavior::Conservative);
}
#[test]
#[cfg_attr(debug_assertions, ignore)]
fn test_classification_performance() {
use std::time::Instant;
let funcs = vec![
"Vec::len",
"Option::map",
"std::fs::read",
"unknown_func",
"Iterator::collect",
"HashMap::insert",
"String::trim",
"push",
"clone",
"my_custom_function",
];
let start = Instant::now();
for _ in 0..10000 {
for func in &funcs {
let _ = classify_call(func);
}
}
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 500,
"Classification took {:?}, expected <500ms (spec: <0.5ms/lookup)",
elapsed
);
}
#[test]
fn test_known_pure_function_count() {
let count = KNOWN_PURE_FUNCTIONS.len();
assert!(
count >= 100,
"Should have at least 100 known pure functions, got {}",
count
);
}
#[test]
fn test_pure_patterns_completeness() {
let patterns = [
"len", "is_empty", "is_some", "is_none", "is_ok", "is_err", "get", "first", "last",
"contains", "clone", "iter", "map", "filter",
];
for pattern in patterns {
assert_eq!(
classify_call(pattern),
CallPurity::Pure,
"{} should be classified as pure",
pattern
);
}
}
#[test]
fn test_impure_patterns_completeness() {
let patterns = [
"push", "pop", "insert", "remove", "clear", "write", "read", "now",
];
for pattern in patterns {
assert_eq!(
classify_call(pattern),
CallPurity::Impure,
"{} should be classified as impure",
pattern
);
}
}
}