use std::collections::HashMap;
use syn::visit::Visit;
pub(crate) type UseMap = HashMap<String, String>;
pub(crate) type ReexportMap = 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());
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());
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 collect_reexports(items: &[syn::Item], module: &str, 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) {
if target != alias {
out.insert(alias, target);
}
}
}
}
}
}
fn canonicalize_use_target(written: &str, module: &str) -> Option<String> {
let segs: Vec<String> = written.split("::").map(strip_raw).collect();
resolve_crate_relative(&segs, module)
}
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
}
#[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) 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),
_ => None,
}
}
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(|arg| match arg {
syn::GenericArgument::Type(ty) => type_to_string(ty),
_ => Some("_".to_string()),
})
.collect();
segs.push(format!("{ident}<{}>", rendered?.join(", ")));
}
syn::PathArguments::Parenthesized(_) => return None,
}
}
Some(segs.join("::"))
}