use std::collections::{HashMap, HashSet};
use syn::visit::Visit;
use syn::{File, ImplItem, TraitItem};
#[derive(Debug, Clone, Default)]
pub struct ProjectScope {
pub functions: HashSet<String>,
pub methods: HashSet<String>,
pub types: HashSet<String>,
pub trivial_methods: HashSet<String>,
pub trait_only_methods: HashSet<String>,
pub methods_by_type: HashMap<String, HashSet<String>>,
}
impl ProjectScope {
pub fn from_files(files: &[(&str, &File)]) -> Self {
let mut collector = ScopeCollector {
functions: HashSet::new(),
methods: HashSet::new(),
types: HashSet::new(),
trivial_candidates: HashSet::new(),
non_trivial_methods: HashSet::new(),
trait_method_names: HashSet::new(),
concrete_method_names: HashSet::new(),
methods_by_type: HashMap::new(),
};
files
.iter()
.for_each(|(_, file)| collector.visit_file(file));
ProjectScope {
functions: collector.functions,
methods: collector.methods,
types: collector.types,
trivial_methods: collector
.trivial_candidates
.difference(&collector.non_trivial_methods)
.cloned()
.collect(),
trait_only_methods: collector
.trait_method_names
.difference(&collector.concrete_method_names)
.cloned()
.collect(),
methods_by_type: collector.methods_by_type,
}
}
pub fn is_own_function(&self, name: &str) -> bool {
if let Some((prefix, method)) = name.split_once("::") {
if prefix == "Self" {
return self.methods.contains(method) && !self.trait_only_methods.contains(method);
}
self.types.contains(prefix)
&& !method.starts_with(char::is_uppercase)
&& !self.trait_only_methods.contains(method)
} else {
self.functions.contains(name)
}
}
pub fn is_own_method(&self, name: &str) -> bool {
self.methods.contains(name)
&& !self.trivial_methods.contains(name)
&& !self.trait_only_methods.contains(name)
}
pub fn is_own_self_method(&self, method: &str, parent_type: &str) -> bool {
self.methods_by_type
.get(parent_type)
.map(|m| m.contains(method))
.unwrap_or(false)
&& !self.trivial_methods.contains(method)
&& !self.trait_only_methods.contains(method)
}
}
fn has_trivial_self_signature(sig: &syn::Signature) -> bool {
let has_receiver = sig
.inputs
.iter()
.any(|arg| matches!(arg, syn::FnArg::Receiver(_)));
let typed_count = sig
.inputs
.iter()
.filter(|arg| matches!(arg, syn::FnArg::Typed(_)))
.count();
has_receiver && typed_count == 0
}
fn is_trivial_method_call(mc: &syn::ExprMethodCall) -> bool {
let method_name = mc.method.to_string();
if mc.args.is_empty() {
return matches!(
method_name.as_str(),
"len"
| "is_empty"
| "clone"
| "as_ref"
| "as_mut"
| "as_str"
| "to_owned"
| "to_string"
| "borrow"
| "borrow_mut"
);
}
if mc.args.len() != 1 || !matches!(method_name.as_str(), "get") {
return false;
}
let mut current = &mc.args[0];
loop {
match current {
syn::Expr::Lit(_) => return true,
syn::Expr::Field(f) => current = &f.base,
syn::Expr::Path(p) if p.path.is_ident("self") => return true,
syn::Expr::Reference(r) => current = &r.expr,
_ => return false,
}
}
}
fn is_trivial_accessor_body(block: &syn::Block) -> bool {
if block.stmts.len() != 1 {
return false;
}
let expr = match &block.stmts[0] {
syn::Stmt::Expr(e, _) => e,
_ => return false,
};
let check_call = |mc: &syn::ExprMethodCall| is_trivial_method_call(mc);
let mut current = expr;
loop {
match current {
syn::Expr::Field(_) => return true,
syn::Expr::Reference(r) => current = &r.expr,
syn::Expr::Cast(c) => current = &c.expr,
syn::Expr::Unary(u) => current = &u.expr,
syn::Expr::Paren(p) => current = &p.expr,
syn::Expr::MethodCall(mc) if check_call(mc) => {
current = &mc.receiver;
}
_ => return false,
}
}
}
struct ScopeCollector {
functions: HashSet<String>,
methods: HashSet<String>,
types: HashSet<String>,
trivial_candidates: HashSet<String>,
non_trivial_methods: HashSet<String>,
trait_method_names: HashSet<String>,
concrete_method_names: HashSet<String>,
methods_by_type: HashMap<String, HashSet<String>>,
}
impl<'ast> Visit<'ast> for ScopeCollector {
fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
self.functions.insert(node.sig.ident.to_string());
syn::visit::visit_item_fn(self, node);
}
fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
let type_name = if let syn::Type::Path(tp) = &*node.self_ty {
tp.path.segments.last().map(|seg| {
self.types.insert(seg.ident.to_string());
seg.ident.to_string()
})
} else {
None
};
let is_trait_impl = node.trait_.is_some();
for item in &node.items {
if let ImplItem::Fn(method) = item {
let name = method.sig.ident.to_string();
self.methods.insert(name.clone());
if let Some(ref tn) = type_name {
self.methods_by_type
.entry(tn.clone())
.or_default()
.insert(name.clone());
}
if is_trait_impl {
self.trait_method_names.insert(name.clone());
} else {
self.concrete_method_names.insert(name.clone());
}
if has_trivial_self_signature(&method.sig)
&& is_trivial_accessor_body(&method.block)
{
self.trivial_candidates.insert(name);
} else {
self.non_trivial_methods.insert(name);
}
}
}
syn::visit::visit_item_impl(self, node);
}
fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
self.types.insert(node.ident.to_string());
for item in &node.items {
if let TraitItem::Fn(method) = item {
let name = method.sig.ident.to_string();
self.methods.insert(name.clone());
self.trait_method_names.insert(name);
}
}
syn::visit::visit_item_trait(self, node);
}
fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
self.types.insert(node.ident.to_string());
syn::visit::visit_item_struct(self, node);
}
fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
self.types.insert(node.ident.to_string());
syn::visit::visit_item_enum(self, node);
}
fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
syn::visit::visit_item_mod(self, node);
}
}