use std::collections::{HashMap, HashSet};
use syn::visit::Visit;
pub(crate) type UseMap = HashMap<String, String>;
pub(crate) type ReexportMap = HashMap<String, String>;
pub(crate) type AliasMap = HashMap<String, String>;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum BareFallback {
Ignore,
CurrentModule,
}
pub(crate) fn strip_raw(ident: &str) -> String {
ident.strip_prefix("r#").unwrap_or(ident).to_string()
}
pub(crate) fn canonical_path_str(path: &str) -> String {
path.split("::")
.map(strip_raw)
.collect::<Vec<_>>()
.join("::")
}
pub(crate) fn collect_uses(items: &[syn::Item]) -> UseMap {
let mut map = UseMap::new();
for item in items {
if let syn::Item::Use(use_item) = item {
collect_use_tree(&use_item.tree, String::new(), &mut map);
}
}
map
}
fn collect_use_tree(tree: &syn::UseTree, prefix: String, map: &mut UseMap) {
let join = |prefix: &str, ident: &str| {
if prefix.is_empty() {
ident.to_string()
} else {
format!("{prefix}::{ident}")
}
};
match tree {
syn::UseTree::Path(path) => {
let ident = strip_raw(&path.ident.to_string());
collect_use_tree(&path.tree, join(&prefix, &ident), map);
}
syn::UseTree::Name(name) => {
let ident = strip_raw(&name.ident.to_string());
if ident == "self" {
if let Some(last) = prefix.rsplit("::").next().filter(|s| !s.is_empty()) {
map.insert(last.to_string(), prefix.clone());
}
} else {
map.insert(ident.clone(), join(&prefix, &ident));
}
}
syn::UseTree::Rename(rename) => {
let ident = strip_raw(&rename.ident.to_string());
let alias = strip_raw(&rename.rename.to_string());
if alias == "_" {
} else if ident == "self" {
if !prefix.is_empty() {
map.insert(alias, prefix.clone());
}
} else {
map.insert(alias, join(&prefix, &ident));
}
}
syn::UseTree::Glob(_) => {}
syn::UseTree::Group(group) => {
for item in &group.items {
collect_use_tree(item, prefix.clone(), map);
}
}
}
}
fn resolve_crate_relative(segs: &[String], module: &str) -> Option<String> {
let head = segs.first()?;
match head.as_str() {
"crate" => Some(segs.join("::")),
"self" | "super" => {
let mut parts: Vec<&str> = module.split("::").collect();
let mut i = 0;
while i < segs.len() {
match segs[i].as_str() {
"self" => i += 1,
"super" => {
if parts.len() <= 1 {
return None;
}
parts.pop();
i += 1;
}
_ => break,
}
}
let rest = &segs[i..];
if rest.is_empty() {
Some(parts.join("::"))
} else {
Some(format!("{}::{}", parts.join("::"), rest.join("::")))
}
}
_ => None,
}
}
pub(crate) fn resolve_path(
path: &syn::Path,
uses: &UseMap,
module: &str,
bare: BareFallback,
) -> Option<String> {
let segs: Vec<String> = path
.segments
.iter()
.map(|s| strip_raw(&s.ident.to_string()))
.collect();
let head = segs.first()?;
if let Some(canonical) = resolve_crate_relative(&segs, module) {
return Some(canonical);
}
match uses.get(head) {
Some(full) => {
let rest = &segs[1..];
let combined = if rest.is_empty() {
full.clone()
} else {
format!("{full}::{}", rest.join("::"))
};
let combined_segs: Vec<String> = combined.split("::").map(strip_raw).collect();
Some(resolve_crate_relative(&combined_segs, module).unwrap_or(combined))
}
None => match bare {
BareFallback::Ignore => None,
BareFallback::CurrentModule => {
if module.is_empty() {
Some(format!("crate::{}", segs.join("::")))
} else {
Some(format!("{module}::{}", segs.join("::")))
}
}
},
}
}
pub(crate) fn extern_verbatim_segs(segs: &[String], externs: &HashSet<String>) -> Option<String> {
let head = segs.first()?;
externs.contains(head).then(|| segs.join("::"))
}
pub(crate) type ExternRenameMap = HashMap<String, String>;
pub(crate) fn extern_verbatim_renamed(
path: &syn::Path,
externs: &HashSet<String>,
renames: &ExternRenameMap,
) -> Option<String> {
let mut segs: Vec<String> = path
.segments
.iter()
.map(|s| strip_raw(&s.ident.to_string()))
.collect();
if let Some(real) = segs.first().and_then(|h| renames.get(h)).cloned() {
segs[0] = real;
return Some(segs.join("::"));
}
extern_verbatim_segs(&segs, externs)
}
pub(crate) fn collect_reexports(
items: &[syn::Item],
module: &str,
externs: &HashSet<String>,
renames: &ExternRenameMap,
out: &mut ReexportMap,
) {
for item in items {
if let syn::Item::Use(use_item) = item {
if matches!(use_item.vis, syn::Visibility::Inherited) {
continue;
}
let mut local = UseMap::new();
collect_use_tree(&use_item.tree, String::new(), &mut local);
for (name, written) in local {
let alias = format!("{module}::{name}");
if let Some(target) = canonicalize_use_target(&written, module, externs, renames) {
if target != alias {
out.insert(alias, target);
}
}
}
}
}
}
fn canonicalize_use_target(
written: &str,
module: &str,
externs: &HashSet<String>,
renames: &ExternRenameMap,
) -> Option<String> {
let segs: Vec<String> = written.split("::").map(strip_raw).collect();
if let Some(real) = segs.first().and_then(|h| renames.get(h)).cloned() {
let mut renamed = segs.clone();
renamed[0] = real;
return Some(renamed.join("::"));
}
resolve_crate_relative(&segs, module).or_else(|| extern_verbatim_segs(&segs, externs))
}
pub(crate) fn canonicalize_through_reexports(path: &str, reexports: &ReexportMap) -> String {
let mut current = path.to_string();
let mut seen = std::collections::HashSet::new();
while seen.insert(current.clone()) {
match reexports.get(¤t) {
Some(next) => current = next.clone(),
None => break,
}
}
current
}
pub(crate) fn canonicalize_through_aliases(
path: &str,
aliases: &AliasMap,
reexports: &ReexportMap,
) -> String {
let mut current = path.to_string();
let mut seen = std::collections::HashSet::new();
while seen.insert(current.clone()) {
if let Some(next) = aliases.get(¤t) {
current = next.clone();
continue;
}
if let Some(next) = reexports.get(¤t) {
current = next.clone();
continue;
}
break;
}
current
}
pub(crate) fn alias_nominal_target(ty: &syn::Type) -> Option<&syn::Path> {
if let syn::Type::Path(tp) = ty {
if tp.qself.is_none()
&& tp
.path
.segments
.iter()
.all(|s| matches!(s.arguments, syn::PathArguments::None))
{
return Some(&tp.path);
}
}
None
}
pub(crate) fn bare_local_alias(
path: &syn::Path,
module: &str,
aliases: &AliasMap,
) -> Option<String> {
if path.leading_colon.is_some() || path.segments.len() != 1 {
return None;
}
let seg = &path.segments[0];
if !matches!(seg.arguments, syn::PathArguments::None) {
return None;
}
let key = format!("{module}::{}", strip_raw(&seg.ident.to_string()));
aliases.contains_key(&key).then_some(key)
}
#[derive(Default)]
pub(crate) struct PathCollector {
pub(crate) paths: Vec<syn::Path>,
}
impl<'ast> Visit<'ast> for PathCollector {
fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
self.paths.push(node.path.clone());
syn::visit::visit_type_path(self, node);
}
fn visit_trait_bound(&mut self, node: &'ast syn::TraitBound) {
self.paths.push(node.path.clone());
syn::visit::visit_trait_bound(self, node);
}
}
pub(crate) struct ShapeExposure {
pub(crate) shape: String,
pub(crate) principal: Option<syn::Path>,
pub(crate) seam: String,
}
pub(crate) fn stamp_seam(mut exposures: Vec<ShapeExposure>, seam: &str) -> Vec<ShapeExposure> {
for exposure in &mut exposures {
exposure.seam = seam.to_string();
}
exposures
}
#[derive(Default)]
pub(crate) struct DynCollector {
pub(crate) exposures: Vec<ShapeExposure>,
}
impl<'ast> Visit<'ast> for DynCollector {
fn visit_type_trait_object(&mut self, node: &'ast syn::TypeTraitObject) {
self.exposures.push(ShapeExposure {
shape: trait_object_to_string(node),
principal: principal_trait_path(&node.bounds),
seam: String::new(),
});
syn::visit::visit_type_trait_object(self, node);
}
}
pub(crate) fn principal_trait_path(
bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus>,
) -> Option<syn::Path> {
bounds.iter().find_map(|bound| match bound {
syn::TypeParamBound::Trait(trait_bound) => Some(trait_bound.path.clone()),
_ => None,
})
}
pub(crate) fn trait_object_to_string(node: &syn::TypeTraitObject) -> String {
let parts: Vec<String> = node.bounds.iter().map(bound_to_string).collect();
format!("dyn {}", parts.join(" + "))
}
pub(crate) fn impl_trait_to_string(node: &syn::TypeImplTrait) -> String {
let parts: Vec<String> = node.bounds.iter().map(bound_to_string).collect();
format!("impl {}", parts.join(" + "))
}
#[derive(Default)]
pub(crate) struct ImplTraitCollector {
pub(crate) exposures: Vec<ShapeExposure>,
}
impl<'ast> Visit<'ast> for ImplTraitCollector {
fn visit_type_impl_trait(&mut self, node: &'ast syn::TypeImplTrait) {
self.exposures.push(ShapeExposure {
shape: impl_trait_to_string(node),
principal: principal_trait_path(&node.bounds),
seam: String::new(),
});
syn::visit::visit_type_impl_trait(self, node);
}
}
fn bound_to_string(bound: &syn::TypeParamBound) -> String {
match bound {
syn::TypeParamBound::Trait(tb) => {
let modifier = match tb.modifier {
syn::TraitBoundModifier::Maybe(_) => "?",
_ => "",
};
let path = path_to_string(&tb.path).unwrap_or_else(|| "_".to_string());
format!("{modifier}{path}")
}
syn::TypeParamBound::Lifetime(lt) => format!("'{}", strip_raw(<.ident.to_string())),
_ => "_".to_string(),
}
}
fn expr_to_string(expr: &syn::Expr) -> Option<String> {
match expr {
syn::Expr::Lit(lit) => match &lit.lit {
syn::Lit::Int(i) => Some(i.base10_digits().to_string()),
syn::Lit::Bool(b) => Some(b.value.to_string()),
syn::Lit::Char(c) => Some(format!("{:?}", c.value())),
syn::Lit::Str(s) => Some(format!("{:?}", s.value())),
_ => None,
},
syn::Expr::Path(p) => path_to_string(&p.path),
_ => None,
}
}
fn generic_argument_to_string(arg: &syn::GenericArgument) -> Option<String> {
match arg {
syn::GenericArgument::Type(ty) => type_to_string(ty),
syn::GenericArgument::Lifetime(lt) => {
Some(format!("'{}", strip_raw(<.ident.to_string())))
}
syn::GenericArgument::AssocType(binding) => Some(format!(
"{} = {}",
strip_raw(&binding.ident.to_string()),
type_to_string(&binding.ty)?
)),
syn::GenericArgument::AssocConst(binding) => Some(format!(
"{} = {}",
strip_raw(&binding.ident.to_string()),
expr_to_string(&binding.value)?
)),
syn::GenericArgument::Const(expr) => expr_to_string(expr),
syn::GenericArgument::Constraint(c) => {
let bounds: Vec<String> = c.bounds.iter().map(bound_to_string).collect();
Some(format!(
"{}: {}",
strip_raw(&c.ident.to_string()),
bounds.join(" + ")
))
}
_ => None,
}
}
pub(crate) fn type_to_string(ty: &syn::Type) -> Option<String> {
match ty {
syn::Type::Path(tp) => {
let mut out = String::new();
if let Some(qself) = &tp.qself {
out.push('<');
out.push_str(&type_to_string(&qself.ty)?);
out.push('>');
out.push_str("::");
}
out.push_str(&path_to_string(&tp.path)?);
Some(out)
}
syn::Type::Reference(r) => {
let inner = type_to_string(&r.elem)?;
if r.mutability.is_some() {
Some(format!("&mut {inner}"))
} else {
Some(format!("&{inner}"))
}
}
syn::Type::Tuple(t) => {
let parts: Option<Vec<String>> = t.elems.iter().map(type_to_string).collect();
Some(format!("({})", parts?.join(", ")))
}
syn::Type::Slice(s) => Some(format!("[{}]", type_to_string(&s.elem)?)),
syn::Type::Array(a) => Some(format!("[{}; _]", type_to_string(&a.elem)?)),
syn::Type::Group(g) => type_to_string(&g.elem),
syn::Type::Paren(p) => type_to_string(&p.elem),
syn::Type::TraitObject(t) => Some(trait_object_to_string(t)),
syn::Type::Ptr(p) => {
let inner = type_to_string(&p.elem)?;
if p.mutability.is_some() {
Some(format!("*mut {inner}"))
} else {
Some(format!("*const {inner}"))
}
}
syn::Type::Never(_) => Some("!".to_string()),
syn::Type::Macro(m) => Some(format!("{}!", path_to_string(&m.mac.path)?)),
syn::Type::BareFn(f) => {
let inputs: Option<Vec<String>> =
f.inputs.iter().map(|a| type_to_string(&a.ty)).collect();
let output = match &f.output {
syn::ReturnType::Default => String::new(),
syn::ReturnType::Type(_, ty) => format!(" -> {}", type_to_string(ty)?),
};
Some(format!("fn({}){output}", inputs?.join(", ")))
}
_ => None,
}
}
pub(crate) fn canonical_self_owner(
self_ty: &syn::Type,
uses: &UseMap,
module: &str,
ordinal: usize,
) -> String {
if let syn::Type::Path(tp) = self_ty {
if tp.qself.is_none() {
if let Some(base) = resolve_path(&tp.path, uses, module, BareFallback::CurrentModule) {
return match render_last_segment_args(&tp.path) {
Some(args) => format!("{base}{args}"),
None => format!("{base}<_#{ordinal}>"),
};
}
}
}
type_to_string(self_ty).unwrap_or_else(|| format!("_#{ordinal}"))
}
fn render_last_segment_args(path: &syn::Path) -> Option<String> {
match &path.segments.last()?.arguments {
syn::PathArguments::None => Some(String::new()),
syn::PathArguments::AngleBracketed(args) => {
let rendered: Option<Vec<String>> =
args.args.iter().map(generic_argument_to_string).collect();
Some(format!("<{}>", rendered?.join(", ")))
}
syn::PathArguments::Parenthesized(_) => None,
}
}
pub(crate) fn path_to_string(path: &syn::Path) -> Option<String> {
let mut segs = Vec::with_capacity(path.segments.len());
if path.leading_colon.is_some() {
segs.push(String::new());
}
for seg in &path.segments {
let ident = strip_raw(&seg.ident.to_string());
match &seg.arguments {
syn::PathArguments::None => segs.push(ident),
syn::PathArguments::AngleBracketed(args) => {
let rendered: Option<Vec<String>> =
args.args.iter().map(generic_argument_to_string).collect();
segs.push(format!("{ident}<{}>", rendered?.join(", ")));
}
syn::PathArguments::Parenthesized(args) => {
let inputs: Option<Vec<String>> = args.inputs.iter().map(type_to_string).collect();
let output = match &args.output {
syn::ReturnType::Default => String::new(),
syn::ReturnType::Type(_, ty) => format!(" -> {}", type_to_string(ty)?),
};
segs.push(format!("{ident}({}){output}", inputs?.join(", ")));
}
}
}
Some(segs.join("::"))
}