use syn::visit::Visit;
use crate::finding::{
AssocKind, ItemKind, MemberKind, PathExposure, PublicSeam, SemanticFact, field_seam, fn_seam,
inherent_assoc_seam, inherent_method_seam, item_seam, member_label, render_sig_tail, tag_paths,
trait_assoc_seam, trait_method_seam,
};
use crate::resolve::{
ImplTraitCollector, PathCollector, ShapeExposure, UseMap, canonical_self_owner,
canonical_self_owner_without_fallback, stamp_seam, strip_raw,
};
use crate::syn_util::{GenericsPosition, impl_generics_positions, is_public};
pub(crate) fn collect_item_return_impl_traits(
item: &syn::Item,
module: &str,
uses: &UseMap,
ordinal: usize,
out: &mut Vec<ShapeExposure>,
) {
match item {
syn::Item::Fn(item) if is_public(&item.vis) => {
let seam = fn_seam(module, &item.sig.ident);
out.extend(stamp_seam(impl_traits_in_return(&item.sig), &seam));
}
syn::Item::Trait(item) if is_public(&item.vis) => {
let trait_name = strip_raw(&item.ident.to_string());
for trait_item in &item.items {
if let syn::TraitItem::Fn(method) = trait_item {
let seam = trait_method_seam(module, &trait_name, &method.sig.ident);
out.extend(stamp_seam(impl_traits_in_return(&method.sig), &seam));
}
}
}
syn::Item::Impl(item) if item.trait_.is_none() => {
let owner = canonical_self_owner(
&item.self_ty,
uses,
module,
ordinal,
&type_param_names(&item.generics),
);
for impl_item in &item.items {
if let syn::ImplItem::Fn(method) = impl_item {
if is_public(&method.vis) {
let seam = inherent_method_seam(module, &owner, &method.sig.ident);
out.extend(stamp_seam(impl_traits_in_return(&method.sig), &seam));
}
}
}
}
_ => {}
}
}
pub(crate) fn impl_traits_in_return(sig: &syn::Signature) -> Vec<ShapeExposure> {
let mut collector = ImplTraitCollector::default();
if let syn::ReturnType::Type(_, ty) = &sig.output {
collector.visit_type(ty);
}
collector.exposures
}
pub(crate) fn collect_item_async_exposures(
item: &syn::Item,
module: &str,
uses: &UseMap,
_ordinal: usize,
out: &mut Vec<SemanticFact>,
) -> Result<(), String> {
match item {
syn::Item::Fn(item) if is_public(&item.vis) => {
if item.sig.asyncness.is_some() {
out.push(SemanticFact::AsyncFreeFn {
module: module.to_string(),
name: strip_raw(&item.sig.ident.to_string()),
tail: render_sig_tail(&item.sig),
});
}
}
syn::Item::Trait(item) if is_public(&item.vis) => {
let trait_name = strip_raw(&item.ident.to_string());
for trait_item in &item.items {
if let syn::TraitItem::Fn(method) = trait_item {
if method.sig.asyncness.is_some() {
out.push(SemanticFact::AsyncTraitMethod {
module: module.to_string(),
trait_name: trait_name.clone(),
name: strip_raw(&method.sig.ident.to_string()),
tail: render_sig_tail(&method.sig),
});
}
}
}
}
syn::Item::Impl(item) if item.trait_.is_none() => {
let async_methods: Vec<&syn::ImplItemFn> = item
.items
.iter()
.filter_map(|impl_item| match impl_item {
syn::ImplItem::Fn(method)
if is_public(&method.vis) && method.sig.asyncness.is_some() =>
{
Some(method)
}
_ => None,
})
.collect();
if async_methods.is_empty() {
return Ok(());
}
let owner = canonical_self_owner_without_fallback(
&item.self_ty,
uses,
module,
&type_param_names(&item.generics),
)
.map_err(|why| {
format!(
"cannot identify public async method owner in {module} — {}; no positional fallback \
is invented for it, because a label that names a traversal position is not an \
identity",
why.cause()
)
})?;
for method in async_methods {
out.push(SemanticFact::AsyncInherentMethod {
module: module.to_string(),
owner: owner.clone(),
name: strip_raw(&method.sig.ident.to_string()),
tail: render_sig_tail(&method.sig),
});
}
}
_ => {}
}
Ok(())
}
pub(crate) fn type_param_names(generics: &syn::Generics) -> std::collections::HashSet<String> {
generics
.params
.iter()
.filter_map(|p| match p {
syn::GenericParam::Type(tp) => Some(strip_raw(&tp.ident.to_string())),
_ => None,
})
.collect()
}
pub(crate) fn paths_in_signature(sig: &syn::Signature) -> Vec<syn::Path> {
paths_in_signature_scoped(sig, &std::collections::HashSet::new())
}
pub(crate) fn paths_in_signature_scoped(
sig: &syn::Signature,
enclosing: &std::collections::HashSet<String>,
) -> Vec<syn::Path> {
let mut shadowed = enclosing.clone();
shadowed.extend(type_param_names(&sig.generics));
let mut c = PathCollector::shadowing(shadowed);
c.visit_signature(sig);
c.paths
}
pub(crate) fn paths_in_type(ty: &syn::Type) -> Vec<syn::Path> {
let mut c = PathCollector::default();
c.visit_type(ty);
c.paths
}
pub(crate) fn paths_in_type_scoped(
ty: &syn::Type,
params: &std::collections::HashSet<String>,
) -> Vec<syn::Path> {
let mut c = PathCollector::shadowing(params.clone());
c.visit_type(ty);
c.paths
}
pub(crate) fn paths_in_generics_scoped(
generics: &syn::Generics,
params: &std::collections::HashSet<String>,
) -> Vec<syn::Path> {
let mut c = PathCollector::shadowing(params.clone());
c.visit_generics(generics);
c.paths
}
pub(crate) fn collect_named_field_exposures<'f, E>(
fields: impl Iterator<Item = &'f syn::Field>,
kind: MemberKind,
module: &str,
owner: &str,
is_governed: impl Fn(&syn::Field) -> bool,
mut extract: impl FnMut(&syn::Type, &PublicSeam) -> Vec<E>,
out: &mut Vec<E>,
) {
for (index, field) in fields.enumerate() {
if is_governed(field) {
let seam = field_seam(kind, module, owner, &member_label(index, field));
out.extend(extract(&field.ty, &seam));
}
}
}
pub(crate) fn collect_item_exposures(
item: &syn::Item,
module: &str,
uses: &UseMap,
ordinal: usize,
out: &mut Vec<PathExposure>,
) {
match item {
syn::Item::Fn(item) if is_public(&item.vis) => {
let seam = fn_seam(module, &item.sig.ident);
out.extend(tag_paths(paths_in_signature(&item.sig), &seam));
}
syn::Item::Struct(item) if is_public(&item.vis) => {
let name = strip_raw(&item.ident.to_string());
let params = type_param_names(&item.generics);
out.extend(tag_paths(
paths_in_generics_scoped(&item.generics, ¶ms),
&item_seam(ItemKind::Struct, module, &item.ident),
));
collect_named_field_exposures(
item.fields.iter(),
MemberKind::Field,
module,
&name,
|field| is_public(&field.vis),
|ty, seam| tag_paths(paths_in_type_scoped(ty, ¶ms), seam),
out,
);
}
syn::Item::Enum(item) if is_public(&item.vis) => {
let name = strip_raw(&item.ident.to_string());
let params = type_param_names(&item.generics);
out.extend(tag_paths(
paths_in_generics_scoped(&item.generics, ¶ms),
&item_seam(ItemKind::Enum, module, &item.ident),
));
for variant in &item.variants {
let owner = format!("{name}::{}", strip_raw(&variant.ident.to_string()));
collect_named_field_exposures(
variant.fields.iter(),
MemberKind::Variant,
module,
&owner,
|_| true,
|ty, seam| tag_paths(paths_in_type_scoped(ty, ¶ms), seam),
out,
);
}
}
syn::Item::Union(item) if is_public(&item.vis) => {
let name = strip_raw(&item.ident.to_string());
let params = type_param_names(&item.generics);
out.extend(tag_paths(
paths_in_generics_scoped(&item.generics, ¶ms),
&item_seam(ItemKind::Union, module, &item.ident),
));
collect_named_field_exposures(
item.fields.named.iter(),
MemberKind::Field,
module,
&name,
|field| is_public(&field.vis),
|ty, seam| tag_paths(paths_in_type_scoped(ty, ¶ms), seam),
out,
);
}
syn::Item::Type(item) if is_public(&item.vis) => {
let seam = item_seam(ItemKind::Type, module, &item.ident);
let params = type_param_names(&item.generics);
out.extend(tag_paths(
paths_in_generics_scoped(&item.generics, ¶ms),
&seam,
));
out.extend(tag_paths(paths_in_type_scoped(&item.ty, ¶ms), &seam));
}
syn::Item::Const(item) if is_public(&item.vis) => {
out.extend(tag_paths(
paths_in_type(&item.ty),
&item_seam(ItemKind::Const, module, &item.ident),
));
}
syn::Item::Static(item) if is_public(&item.vis) => {
out.extend(tag_paths(
paths_in_type(&item.ty),
&item_seam(ItemKind::Static, module, &item.ident),
));
}
syn::Item::Trait(item) if is_public(&item.vis) => {
let trait_name = strip_raw(&item.ident.to_string());
let trait_seam = item_seam(ItemKind::Trait, module, &item.ident);
let trait_params = type_param_names(&item.generics);
out.extend(tag_paths(
paths_in_generics_scoped(&item.generics, &trait_params),
&trait_seam,
));
out.extend(tag_paths(paths_in_bounds(&item.supertraits), &trait_seam));
for trait_item in &item.items {
match trait_item {
syn::TraitItem::Fn(method) => {
let seam = trait_method_seam(module, &trait_name, &method.sig.ident);
out.extend(tag_paths(
paths_in_signature_scoped(&method.sig, &trait_params),
&seam,
));
}
syn::TraitItem::Type(assoc) => {
let seam =
trait_assoc_seam(AssocKind::Type, module, &trait_name, &assoc.ident);
let mut assoc_params = trait_params.clone();
assoc_params.extend(type_param_names(&assoc.generics));
out.extend(tag_paths(
paths_in_bounds_scoped(&assoc.bounds, &assoc_params),
&seam,
));
out.extend(tag_paths(
paths_in_generics_scoped(&assoc.generics, &assoc_params),
&seam,
));
if let Some((_, ty)) = &assoc.default {
out.extend(tag_paths(paths_in_type_scoped(ty, &assoc_params), &seam));
}
}
syn::TraitItem::Const(assoc) => {
let seam =
trait_assoc_seam(AssocKind::Const, module, &trait_name, &assoc.ident);
out.extend(tag_paths(
paths_in_type_scoped(&assoc.ty, &trait_params),
&seam,
));
}
_ => {}
}
}
}
syn::Item::Impl(item) if item.trait_.is_none() => {
let impl_params = type_param_names(&item.generics);
let owner = canonical_self_owner(&item.self_ty, uses, module, ordinal, &impl_params);
for (bound, positions) in impl_generics_positions(&item.generics, ordinal) {
let seam = PublicSeam::InherentGenerics {
module: module.to_string(),
owner: owner.clone(),
bound,
};
for position in positions {
let paths = match position {
GenericsPosition::Bounds(bounds) => {
paths_in_bounds_scoped(bounds, &impl_params)
}
GenericsPosition::Type(ty) => paths_in_type_scoped(ty, &impl_params),
};
out.extend(tag_paths(paths, &seam));
}
}
for impl_item in &item.items {
match impl_item {
syn::ImplItem::Fn(method) if is_public(&method.vis) => {
let seam = inherent_method_seam(module, &owner, &method.sig.ident);
out.extend(tag_paths(
paths_in_signature_scoped(&method.sig, &impl_params),
&seam,
));
}
syn::ImplItem::Const(assoc) if is_public(&assoc.vis) => {
let seam =
inherent_assoc_seam(AssocKind::Const, module, &owner, &assoc.ident);
out.extend(tag_paths(
paths_in_type_scoped(&assoc.ty, &impl_params),
&seam,
));
}
syn::ImplItem::Type(assoc) if is_public(&assoc.vis) => {
let seam =
inherent_assoc_seam(AssocKind::Type, module, &owner, &assoc.ident);
out.extend(tag_paths(
paths_in_type_scoped(&assoc.ty, &impl_params),
&seam,
));
}
_ => {}
}
}
}
syn::Item::Use(item) if is_public(&item.vis) => {
walk_reexport_tree(
&item.tree,
Vec::new(),
module,
item.leading_colon.is_some(),
out,
);
}
syn::Item::ExternCrate(item) if is_public(&item.vis) && item.ident != "self" => {
let name = strip_raw(&item.ident.to_string());
out.push(PathExposure {
seam: PublicSeam::ExternCrate {
module: module.to_string(),
name,
},
path: syn::Path::from(item.ident.clone()),
is_reexport: true,
});
}
syn::Item::ForeignMod(item) => {
for foreign_item in &item.items {
match foreign_item {
syn::ForeignItem::Fn(f) if is_public(&f.vis) => {
let seam = fn_seam(module, &f.sig.ident);
out.extend(tag_paths(paths_in_signature(&f.sig), &seam));
}
syn::ForeignItem::Static(s) if is_public(&s.vis) => {
out.extend(tag_paths(
paths_in_type(&s.ty),
&item_seam(ItemKind::Static, module, &s.ident),
));
}
_ => {}
}
}
}
_ => {}
}
}
pub(crate) fn is_self_segment(ident: &syn::Ident) -> bool {
ident == "self"
}
pub(crate) fn walk_reexport_tree(
tree: &syn::UseTree,
prefix: Vec<syn::Ident>,
module: &str,
leading_colon: bool,
out: &mut Vec<PathExposure>,
) {
match tree {
syn::UseTree::Path(path) => {
let mut segs = prefix;
segs.push(path.ident.clone());
walk_reexport_tree(&path.tree, segs, module, leading_colon, out);
}
syn::UseTree::Name(name) => {
if is_self_segment(&name.ident) {
let exported = prefix.last().map(seg_name);
push_reexport(&prefix, exported.as_deref(), module, leading_colon, out);
} else {
let exported = seg_name(&name.ident);
let mut segs = prefix;
segs.push(name.ident.clone());
push_reexport(&segs, Some(&exported), module, leading_colon, out);
}
}
syn::UseTree::Rename(rename) => {
let alias = seg_name(&rename.rename);
if alias == "_" {
return; }
if is_self_segment(&rename.ident) {
push_reexport(&prefix, Some(&alias), module, leading_colon, out);
} else {
let mut segs = prefix;
segs.push(rename.ident.clone());
push_reexport(&segs, Some(&alias), module, leading_colon, out);
}
}
syn::UseTree::Glob(_) => {
push_reexport(&prefix, Some("*"), module, leading_colon, out);
}
syn::UseTree::Group(group) => {
for item in &group.items {
walk_reexport_tree(item, prefix.clone(), module, leading_colon, out);
}
}
}
}
pub(crate) fn seg_name(ident: &syn::Ident) -> String {
strip_raw(&ident.to_string())
}
pub(crate) fn push_reexport(
segs: &[syn::Ident],
exported: Option<&str>,
module: &str,
leading_colon: bool,
out: &mut Vec<PathExposure>,
) {
let (Some(exported), false) = (exported, segs.is_empty()) else {
return;
};
let segments = segs
.iter()
.map(|ident| syn::PathSegment {
ident: ident.clone(),
arguments: syn::PathArguments::None,
})
.collect();
out.push(PathExposure {
path: syn::Path {
leading_colon: leading_colon.then(<syn::Token![::]>::default),
segments,
},
seam: PublicSeam::Reexport {
module: module.to_string(),
exported: exported.to_string(),
},
is_reexport: true,
});
}
pub(crate) fn paths_in_bounds(
bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus>,
) -> Vec<syn::Path> {
paths_in_bounds_scoped(bounds, &std::collections::HashSet::new())
}
pub(crate) fn paths_in_bounds_scoped(
bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus>,
params: &std::collections::HashSet<String>,
) -> Vec<syn::Path> {
let mut c = PathCollector::shadowing(params.clone());
for bound in bounds {
c.visit_type_param_bound(bound);
}
c.paths
}
pub(crate) fn paths_in_return_scoped(
sig: &syn::Signature,
enclosing: &std::collections::HashSet<String>,
) -> Vec<syn::Path> {
let mut shadowed = enclosing.clone();
shadowed.extend(type_param_names(&sig.generics));
let mut c = PathCollector::shadowing(shadowed);
if let syn::ReturnType::Type(_, ty) = &sig.output {
c.visit_type(ty);
}
c.paths
}