use crate::analyzer::{FunctionSignature, OwnershipMode, SignatureRegistry};
use crate::parser::Type;
fn lookup_sig<'a>(
method: &str,
receiver_type: Option<&str>,
registry: &'a SignatureRegistry,
) -> Option<&'a FunctionSignature> {
if let Some(ty) = receiver_type {
let base = ty.split('<').next().unwrap_or(ty);
let qualified = format!("{}::{}", base, method);
if let Some(sig) = registry.get_signature(&qualified) {
return Some(sig);
}
if base != ty {
let qualified_full = format!("{}::{}", ty, method);
if let Some(sig) = registry.get_signature(&qualified_full) {
return Some(sig);
}
}
}
None
}
fn lookup_suffix<'a>(
method: &str,
registry: &'a SignatureRegistry,
) -> Option<&'a FunctionSignature> {
let pattern = format!("::{}", method);
let mut matches = registry
.signatures
.iter()
.filter(|(key, _)| key.ends_with(&pattern));
let first = matches.next();
if matches.next().is_some() {
return None; }
first.map(|(_, sig)| sig)
}
fn first_arg_ownership(sig: &FunctionSignature) -> Option<OwnershipMode> {
let start = if sig.has_self_receiver { 1 } else { 0 };
sig.param_ownership.get(start).copied()
}
fn first_arg_type(sig: &FunctionSignature) -> Option<&Type> {
let start = if sig.has_self_receiver { 1 } else { 0 };
sig.param_types.get(start)
}
fn arg_count(sig: &FunctionSignature) -> usize {
if sig.has_self_receiver {
sig.param_ownership.len().saturating_sub(1)
} else {
sig.param_ownership.len()
}
}
fn is_reference_type(ty: &Type) -> bool {
matches!(ty, Type::Reference(_))
}
fn is_str_reference(ty: &Type) -> bool {
matches!(ty, Type::Reference(inner) if matches!(&**inner, Type::Custom(n) if n == "str"))
}
fn is_closure_type(ty: &Type) -> bool {
matches!(ty, Type::Custom(n) if n == "Fn" || n == "FnMut" || n == "FnOnce")
|| matches!(ty, Type::FunctionPointer { .. })
}
fn is_usize_type(ty: &Type) -> bool {
matches!(ty, Type::Custom(n) if n == "usize")
}
fn return_type_is(sig: &FunctionSignature, pred: impl Fn(&Type) -> bool) -> bool {
sig.return_type.as_ref().is_some_and(&pred)
}
const MAP_TYPES: &[&str] = &["HashMap", "BTreeMap", "Map", "IndexMap"];
fn is_map_receiver(receiver_type: Option<&str>) -> bool {
receiver_type.is_some_and(|ty| {
let base = ty.split('<').next().unwrap_or(ty);
MAP_TYPES.contains(&base)
})
}
pub fn method_mutates_receiver_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
if let Some(sig) = lookup_sig(method, receiver_type, registry) {
if sig.has_self_receiver && !sig.param_ownership.is_empty() {
return sig.param_ownership[0] == OwnershipMode::MutBorrowed;
}
}
if let Some(sig) = lookup_suffix(method, registry) {
if sig.has_self_receiver && !sig.param_ownership.is_empty() {
return sig.param_ownership[0] == OwnershipMode::MutBorrowed;
}
}
false
}
pub fn is_known_readonly_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
if let Some(sig) = lookup_sig(method, receiver_type, registry) {
if sig.has_self_receiver && !sig.param_ownership.is_empty() {
return sig.param_ownership[0] != OwnershipMode::MutBorrowed;
}
}
if let Some(sig) = lookup_suffix(method, registry) {
if sig.has_self_receiver && !sig.param_ownership.is_empty() {
return sig.param_ownership[0] != OwnershipMode::MutBorrowed;
}
}
false
}
pub fn is_known_stdlib_method_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
lookup_sig(method, receiver_type, registry).is_some()
|| lookup_suffix(method, registry).is_some()
}
pub fn method_returns_usize_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
sig.is_some_and(|s| return_type_is(s, is_usize_type))
}
pub fn method_returns_iterator_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
sig.is_some_and(|s| return_type_is(s, |ty| matches!(ty, Type::Custom(n) if n == "Iterator")))
}
pub fn method_is_type_preserving_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
sig.is_some_and(|s| return_type_is(s, |ty| matches!(ty, Type::Custom(n) if n == "Self")))
}
pub fn method_is_storage_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
if let Some(s) = sig {
if !s.has_self_receiver || arg_count(s) == 0 {
return false;
}
if s.param_ownership[0] != OwnershipMode::MutBorrowed {
return false;
}
let start = 1; for i in start..s.param_ownership.len() {
if s.param_ownership[i] == OwnershipMode::Owned {
if let Some(ty) = s.param_types.get(i) {
if !is_usize_type(ty) && !is_closure_type(ty) {
return true;
}
}
}
}
}
false
}
pub fn method_auto_borrows_arg_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
sig.is_some_and(|s| {
first_arg_type(s).is_some_and(is_reference_type)
&& first_arg_ownership(s) == Some(OwnershipMode::Borrowed)
})
}
pub fn method_is_map_key_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
if !is_map_receiver(receiver_type) {
if receiver_type.is_some() {
return false;
}
for map_ty in MAP_TYPES {
if let Some(sig) = lookup_sig(method, Some(map_ty), registry) {
if sig.has_self_receiver
&& first_arg_ownership(sig) == Some(OwnershipMode::Borrowed)
&& first_arg_type(sig).is_some_and(is_reference_type)
{
return true;
}
}
}
return false;
}
let sig = lookup_sig(method, receiver_type, registry);
sig.is_some_and(|s| {
s.has_self_receiver
&& first_arg_ownership(s) == Some(OwnershipMode::Borrowed)
&& first_arg_type(s).is_some_and(is_reference_type)
})
}
pub fn method_is_option_accessor_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
sig.is_some_and(|s| {
s.has_self_receiver
&& return_type_is(s, |ty| {
matches!(ty,
Type::Option(inner) if matches!(&**inner, Type::Reference(_))
) || matches!(ty, Type::Custom(n) if n == "T")
})
})
}
pub fn method_is_capacity_cast_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
sig.is_some_and(|s| first_arg_type(s).is_some_and(is_usize_type))
}
pub fn method_is_index_taking_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
method_is_capacity_cast_qualified(method, receiver_type, registry)
}
pub fn method_is_closure_taking_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
sig.is_some_and(|s| first_arg_type(s).is_some_and(is_closure_type))
}
pub fn method_is_slice_search_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
sig.is_some_and(|s| {
s.has_self_receiver
&& first_arg_ownership(s) == Some(OwnershipMode::Borrowed)
&& first_arg_type(s).is_some_and(|ty| is_reference_type(ty) && !is_str_reference(ty))
})
}
pub fn method_is_string_search_qualified(
method: &str,
receiver_type: Option<&str>,
registry: &SignatureRegistry,
) -> bool {
let sig =
lookup_sig(method, receiver_type, registry).or_else(|| lookup_suffix(method, registry));
sig.is_some_and(|s| {
s.has_self_receiver
&& first_arg_ownership(s) == Some(OwnershipMode::Borrowed)
&& first_arg_type(s).is_some_and(is_str_reference)
})
}
pub fn method_mutates_receiver(method: &str) -> bool {
matches!(
method,
"push"
| "pop"
| "insert"
| "remove"
| "clear"
| "append"
| "extend"
| "drain"
| "truncate"
| "resize"
| "retain"
| "sort"
| "sort_by"
| "sort_by_key"
| "sort_unstable"
| "sort_unstable_by"
| "dedup"
| "reverse"
| "swap"
| "swap_remove"
| "reserve"
| "shrink_to_fit"
| "split_off"
| "fill"
| "set"
| "rotate_left"
| "rotate_right"
| "set_len"
| "push_str"
| "push_front"
| "push_back"
| "pop_front"
| "pop_back"
| "make_ascii_lowercase"
| "make_ascii_uppercase"
| "add"
| "take"
| "replace"
| "get_or_insert"
| "get_or_insert_with"
| "entry"
| "get_mut"
| "iter_mut"
| "values_mut"
)
}
pub fn method_returns_iterator(method: &str) -> bool {
matches!(
method,
"iter"
| "iter_mut"
| "into_iter"
| "keys"
| "values"
| "values_mut"
| "drain"
| "lines"
| "chars"
| "bytes"
| "split"
| "split_whitespace"
| "enumerate"
| "windows"
| "chunks"
| "match_indices"
| "rsplit"
| "splitn"
)
}
pub fn is_map_key_method(method: &str) -> bool {
matches!(
method,
"get" | "get_mut" | "contains_key" | "remove" | "get_key_value"
)
}
pub fn is_index_taking_method(method: &str) -> bool {
matches!(
method,
"insert" | "remove" | "swap" | "swap_remove" | "drain" | "split_off"
)
}
pub fn is_closure_taking_method(method: &str) -> bool {
matches!(
method,
"filter"
| "any"
| "all"
| "find"
| "find_map"
| "position"
| "take_while"
| "skip_while"
| "map_while"
| "partition"
| "rposition"
| "retain"
| "sort_by"
| "sort_by_key"
| "sort_unstable_by"
)
}
pub fn is_runtime_std_module(name: &str) -> bool {
matches!(
name,
"strings"
| "json"
| "time"
| "math"
| "random"
| "http"
| "mime"
| "subprocess"
| "async_runtime"
| "async"
| "cli"
| "crypto"
| "csv"
| "db"
| "regex"
| "testing"
| "game"
| "env"
)
}
pub fn runtime_std_module_uses_asref_str(module: &str) -> bool {
matches!(
module,
"strings" | "json" | "regex" | "csv" | "mime" | "http" | "env"
)
}
pub fn runtime_std_param_needs_auto_borrow(
module: &str,
_func: &str,
param_type: &crate::parser::Type,
) -> bool {
use crate::parser::Type;
match module {
"json" => {
matches!(param_type, Type::Custom(name) if name == "Value")
}
_ => false,
}
}