use crate::*;
#[allow(clippy::too_many_arguments)]
pub(crate) fn scan_items(
items: &[syn::Item],
modpath: &str,
locs: &[String],
loc_idx: &mut usize,
include_tests: bool,
fields: &FieldIndex,
returns: &ReturnIndex,
traits: TraitIndexes,
elems: ElemIndexes,
lazy_statics: &std::collections::HashSet<String>,
uses: &mut HashMap<String, String>,
out: &mut Vec<FnInfo>,
) {
for it in items {
if let syn::Item::Use(u) = it {
collect_use(&u.tree, String::new(), uses);
}
}
let qual = |name: &str| if modpath.is_empty() { name.to_string() } else { format!("{modpath}::{name}") };
for it in items {
match it {
syn::Item::Fn(f) => {
if !include_tests && is_cfg_test(&f.attrs) {
continue;
}
let n = f.sig.ident.to_string();
let loc = next_loc(locs, loc_idx);
out.push(fninfo(&n, &qual(&n), &loc, &f.sig, &f.block, None, uses, fields, returns, traits, elems, lazy_statics));
}
syn::Item::Impl(im) => {
if !include_tests && is_cfg_test(&im.attrs) {
continue; }
let tyname = impl_type_name(&im.self_ty);
for ii in &im.items {
if let syn::ImplItem::Fn(m) = ii {
if !include_tests && is_cfg_test(&m.attrs) {
continue; }
let n = m.sig.ident.to_string();
let q = match &tyname {
Some(t) => qual(&format!("{t}::{n}")),
None => qual(&n),
};
let loc = next_loc(locs, loc_idx);
out.push(fninfo(&n, &q, &loc, &m.sig, &m.block, tyname.as_deref(), uses, fields, returns, traits, elems, lazy_statics));
}
}
}
syn::Item::Mod(m) => {
if !include_tests && is_cfg_test(&m.attrs) {
continue; }
if let Some((_, inner)) = &m.content {
let sub = qual(&m.ident.to_string());
let mut subuses = uses.clone();
scan_items(inner, &sub, locs, loc_idx, include_tests, fields, returns, traits, elems, lazy_statics, &mut subuses, out);
}
}
syn::Item::Trait(tr) => {
if !include_tests && is_cfg_test(&tr.attrs) {
continue;
}
let tname = tr.ident.to_string();
for ti in &tr.items {
if let syn::TraitItem::Fn(m) = ti {
let Some(block) = &m.default else { continue }; if !include_tests && is_cfg_test(&m.attrs) {
continue;
}
let n = m.sig.ident.to_string();
let loc = next_loc(locs, loc_idx);
out.push(fninfo(&n, &qual(&format!("{tname}::{n}")), &loc, &m.sig, block,
Some(&tname), uses, fields, returns, traits, elems, lazy_statics));
}
}
}
_ => {}
}
if lazy_unit_emitted(it, include_tests) {
if let Some((name, body)) = lazy_static_unit(it) {
let block = syn::Block { brace_token: Default::default(), stmts: body };
let sig: syn::Signature = syn::parse_quote!(fn __candor_lazy_init());
let loc = next_loc(locs, loc_idx);
let q = format!("{LAZY_UNIT_PREFIX}::{name}");
out.push(fninfo(&name, &q, &loc, &sig, &block, None, uses, fields, returns, traits, elems, lazy_statics));
}
}
}
}
pub(crate) fn lazy_unit_emitted(it: &syn::Item, include_tests: bool) -> bool {
let attrs: &[syn::Attribute] = match it {
syn::Item::Static(s) => &s.attrs,
syn::Item::Const(c) => &c.attrs,
syn::Item::Macro(m) => &m.attrs,
_ => return false,
};
if !include_tests && is_cfg_test(attrs) {
return false;
}
lazy_static_unit(it).is_some()
}
pub(crate) fn next_loc(locs: &[String], loc_idx: &mut usize) -> String {
debug_assert!(*loc_idx < locs.len(), "fn_locs/scan_items walk-order drift: more fns than locs");
let l = locs.get(*loc_idx).cloned().unwrap_or_default();
*loc_idx += 1;
l
}
pub(crate) fn fn_locs(items: &[syn::Item], file: &str, include_tests: bool, out: &mut Vec<String>) {
use syn::spanned::Spanned;
let loc = |sp: proc_macro2::Span| {
let s = sp.start();
format!("{file}:{}:{}", s.line, s.column + 1)
};
for it in items {
match it {
syn::Item::Fn(f) => {
if !include_tests && is_cfg_test(&f.attrs) {
continue;
}
out.push(loc(f.span()));
}
syn::Item::Impl(im) => {
if !include_tests && is_cfg_test(&im.attrs) {
continue;
}
for ii in &im.items {
if let syn::ImplItem::Fn(m) = ii {
if !include_tests && is_cfg_test(&m.attrs) {
continue;
}
out.push(loc(m.span()));
}
}
}
syn::Item::Mod(m) => {
if !include_tests && is_cfg_test(&m.attrs) {
continue;
}
if let Some((_, inner)) = &m.content {
fn_locs(inner, file, include_tests, out);
}
}
syn::Item::Trait(tr) => {
if !include_tests && is_cfg_test(&tr.attrs) {
continue;
}
for ti in &tr.items {
if let syn::TraitItem::Fn(m) = ti {
if m.default.is_none() {
continue;
}
if !include_tests && is_cfg_test(&m.attrs) {
continue;
}
out.push(loc(m.span()));
}
}
}
_ => {}
}
if lazy_unit_emitted(it, include_tests) {
out.push(loc(it.span()));
}
}
}
pub(crate) fn seed_vars(sig: &syn::Signature, self_ty: Option<&str>, uses: &HashMap<String, String>) -> HashMap<String, String> {
let mut vars = HashMap::new();
if let Some(t) = self_ty {
vars.insert("self".to_string(), t.to_string());
}
for arg in &sig.inputs {
if let syn::FnArg::Typed(pt) = arg {
if let syn::Pat::Ident(id) = &*pt.pat {
if let Some(ty) = type_path(&pt.ty, uses) {
vars.insert(id.ident.to_string(), ty);
}
}
}
}
vars
}
pub(crate) fn seed_elem_of(
sig: &syn::Signature,
vars: &mut HashMap<String, String>,
uses: &HashMap<String, String>,
) -> (HashMap<String, String>, TupleElemIndex) {
let mut elem_of = HashMap::new();
let mut tuple_of: TupleElemIndex = HashMap::new();
for arg in &sig.inputs {
let syn::FnArg::Typed(pt) = arg else { continue };
match &*pt.pat {
syn::Pat::Ident(id) => {
if let Some(e) = elem_type(&pt.ty, uses) {
elem_of.insert(id.ident.to_string(), e);
}
if let Some(t) = tuple_types(&pt.ty, uses) {
tuple_of.insert(id.ident.to_string(), t);
}
}
syn::Pat::Tuple(tup) => {
if let syn::Type::Tuple(tty) = &*pt.ty {
for (pat_el, ty_el) in tup.elems.iter().zip(tty.elems.iter()) {
if let Some(name) = single_pat_ident(pat_el) {
if let Some(ty) = type_path(ty_el, uses) {
vars.insert(name.clone(), ty);
}
if let Some(e) = elem_type(ty_el, uses) {
elem_of.insert(name, e);
}
}
}
}
}
_ => {}
}
}
(elem_of, tuple_of)
}
pub(crate) fn seed_trait_vars(sig: &syn::Signature) -> HashMap<String, Vec<String>> {
let gb = generic_bounds_of(sig);
let mut m = HashMap::new();
for arg in &sig.inputs {
if let syn::FnArg::Typed(pt) = arg {
if let syn::Pat::Ident(id) = &*pt.pat {
let leaves = trait_leaves(&pt.ty, &gb);
if !leaves.is_empty() {
m.insert(id.ident.to_string(), leaves);
}
}
}
}
m
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn fninfo(
leaf: &str,
qual: &str,
loc: &str,
sig: &syn::Signature,
block: &syn::Block,
self_ty: Option<&str>,
uses: &HashMap<String, String>,
fields: &FieldIndex,
returns: &ReturnIndex,
traits: TraitIndexes,
elems: ElemIndexes,
lazy_statics: &std::collections::HashSet<String>,
) -> FnInfo {
let mut local_uses = HashMap::new();
for stmt in &block.stmts {
if let syn::Stmt::Item(syn::Item::Use(u)) = stmt {
collect_use(&u.tree, String::new(), &mut local_uses);
}
}
let merged: HashMap<String, String>;
let uses: &HashMap<String, String> = if local_uses.is_empty() {
uses
} else {
let mut m = uses.clone();
m.extend(local_uses);
merged = m;
&merged
};
let trait_vars = seed_trait_vars(sig);
let fn_typed_vars = seed_fn_typed_vars(sig);
let mut vars = seed_vars(sig, self_ty, uses);
for k in trait_vars.keys() {
vars.remove(k);
}
for k in &fn_typed_vars {
vars.remove(k);
}
let (elem_of, tuple_of) = seed_elem_of(sig, &mut vars, uses);
let mut c = CallCollector {
uses,
vars,
trait_vars,
fields,
trait_fields: traits.fields,
trait_impls: traits.impls,
local_traits: traits.decls,
returns,
field_elem: elems.field_elem,
enum_variants: elems.enum_variants,
elem_of,
tuple_of,
calls: Vec::new(),
closure_vars: std::collections::HashSet::new(),
fn_typed_vars,
fn_alias: std::collections::HashMap::new(),
lazy_statics,
forced_lazies: std::collections::HashSet::new(),
unresolved: false,
err_ret_leaf: result_err_leaf(&sig.output, uses),
};
for stmt in &block.stmts {
c.visit_stmt(stmt);
}
FnInfo {
qual: qual.to_string(),
leaf: leaf.to_string(),
loc: loc.to_string(),
calls: c.calls,
unresolved: c.unresolved,
}
}
pub(crate) fn record_return(
sig: &syn::Signature,
uses: &HashMap<String, String>,
rets: &mut HashMap<String, Option<String>>,
self_ty: Option<&str>,
) {
let syn::ReturnType::Type(_, ty) = &sig.output else { return };
if is_callable_type(unwrap_result_option(ty), &generic_bounds_of(sig)) {
let leaf = sig.ident.to_string();
match rets.get(&leaf) {
None => { rets.insert(leaf, Some(RET_FN_TYPED.to_string())); }
Some(Some(prev)) if prev != RET_FN_TYPED => { rets.insert(leaf, None); }
_ => {}
}
return;
}
let Some(mut tp) = type_path(unwrap_result_option(ty), uses) else { return };
if tp == "Self" {
match self_ty {
Some(s) => tp = s.to_string(),
None => return, }
}
let leaf = sig.ident.to_string();
match rets.get(&leaf) {
None => {
rets.insert(leaf, Some(tp));
}
Some(Some(prev)) if *prev != tp => {
rets.insert(leaf, None); }
_ => {}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn collect_decls(
items: &[syn::Item],
include_tests: bool,
uses: &mut HashMap<String, String>,
fields: &mut FieldIndex,
field_elem: &mut FieldElemIndex,
rets: &mut HashMap<String, Option<String>>,
enum_tmp: &mut HashMap<String, Option<String>>,
trait_impls: &mut TraitImplIndex,
local_traits: &mut HashMap<String, LocalTrait>,
trait_fields: &mut TraitFieldIndex,
prim_aliases: &mut std::collections::HashSet<String>,
extern_fns: &mut std::collections::HashSet<String>,
drop_types: &mut std::collections::HashSet<String>,
deref_target: &mut HashMap<String, String>,
lazy_statics: &mut std::collections::HashSet<String>,
) {
for it in items {
if let syn::Item::Use(u) = it {
collect_use(&u.tree, String::new(), uses);
}
}
let no_generics = HashMap::new();
for it in items {
if lazy_unit_emitted(it, include_tests) {
if let Some((name, _)) = lazy_static_unit(it) {
lazy_statics.insert(name);
}
}
match it {
syn::Item::Struct(s) => {
match &s.fields {
syn::Fields::Named(named) => {
let entry = fields.entry(s.ident.to_string()).or_default();
for f in &named.named {
if has_cfg(&f.attrs) {
continue;
}
if let Some(name) = &f.ident {
let leaves = trait_leaves(&f.ty, &no_generics);
if !leaves.is_empty() {
trait_fields
.entry(s.ident.to_string())
.or_default()
.insert(name.to_string(), leaves);
} else if let Some(ty) = type_path(&f.ty, uses) {
entry.insert(name.to_string(), ty);
}
if let Some(e) = elem_type(&f.ty, uses) {
field_elem
.entry(s.ident.to_string())
.or_default()
.insert(name.to_string(), e);
}
}
}
}
syn::Fields::Unnamed(unnamed) => {
let entry = fields.entry(s.ident.to_string()).or_default();
for (i, f) in unnamed.unnamed.iter().enumerate() {
if has_cfg(&f.attrs) {
continue;
}
if let Some(ty) = type_path(&f.ty, uses) {
entry.insert(i.to_string(), ty);
}
if let Some(e) = elem_type(&f.ty, uses) {
field_elem
.entry(s.ident.to_string())
.or_default()
.insert(i.to_string(), e);
}
}
}
syn::Fields::Unit => {}
}
}
syn::Item::Fn(f) => record_return(&f.sig, uses, rets, None),
syn::Item::Enum(en) => {
for v in &en.variants {
if has_cfg(&v.attrs) {
continue;
}
let syn::Fields::Unnamed(unnamed) = &v.fields else { continue };
if unnamed.unnamed.len() != 1 {
continue;
}
let Some(tp) = type_path(&unnamed.unnamed[0].ty, uses) else { continue };
let leaf = v.ident.to_string();
match enum_tmp.get(&leaf) {
None => {
enum_tmp.insert(leaf, Some(tp));
}
Some(Some(prev)) if *prev != tp => {
enum_tmp.insert(leaf, None); }
_ => {}
}
}
}
syn::Item::Type(it) => {
if is_non_nominal_type(&it.ty) {
prim_aliases.insert(it.ident.to_string());
}
}
syn::Item::Trait(t) => {
let e = local_traits.entry(t.ident.to_string()).or_default();
e.count += 1;
for ti in &t.items {
if let syn::TraitItem::Fn(m) = ti {
e.methods.insert(m.sig.ident.to_string());
}
}
}
syn::Item::ForeignMod(fm) => {
for fi in &fm.items {
if let syn::ForeignItem::Fn(f) = fi {
extern_fns.insert(f.sig.ident.to_string());
}
}
}
syn::Item::Impl(im) => {
let self_ty = impl_type_name(&im.self_ty);
if let (Some((_, tr, _)), Some(ty)) = (&im.trait_, &self_ty) {
if let Some(leaf) = tr.segments.last() {
trait_impls.entry(leaf.ident.to_string()).or_default().push(ty.clone());
if leaf.ident == "Drop" {
drop_types.insert(ty.clone());
}
if leaf.ident == "Deref" {
for it in &im.items {
if let syn::ImplItem::Type(at) = it {
if at.ident == "Target" {
if let Some(tp) = type_path(&at.ty, uses) {
let tl = tp.rsplit("::").next().unwrap_or(&tp).to_string();
let kl = ty.rsplit("::").next().unwrap_or(ty).to_string();
deref_target.insert(kl, tl);
}
}
}
}
}
}
}
for ii in &im.items {
if let syn::ImplItem::Fn(m) = ii {
record_return(&m.sig, uses, rets, self_ty.as_deref());
}
}
}
syn::Item::Mod(m) => {
if !include_tests && is_cfg_test(&m.attrs) {
continue;
}
if let Some((_, inner)) = &m.content {
let mut subuses = uses.clone();
collect_decls(inner, include_tests, &mut subuses, fields, field_elem, rets, enum_tmp, trait_impls, local_traits, trait_fields, prim_aliases, extern_fns, drop_types, deref_target, lazy_statics);
}
}
_ => {}
}
}
}