use std::collections::{HashMap, HashSet};
use crate::syn_util::{FlatItem, reexport_externs_for, reexport_renames_for};
mod shape;
pub(crate) use shape::*;
pub(crate) type UseMap = HashMap<String, Vec<String>>;
pub(crate) type ReexportMap = HashMap<String, Vec<String>>;
pub(crate) type AliasMap = HashMap<String, Vec<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 has_empty_path_segment(operand: &str) -> bool {
operand.split("::").any(str::is_empty)
}
pub(crate) fn validate_path_operands(operands: &[String]) -> Result<(), String> {
if let Some(bad) = operands.iter().find(|op| has_empty_path_segment(op)) {
return Err(crate::errors::malformed_path_operand_error(bad));
}
Ok(())
}
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 push_candidate(map: &mut HashMap<String, Vec<String>>, key: String, value: String) {
let candidates = map.entry(key).or_default();
if !candidates.contains(&value) {
candidates.push(value);
}
}
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()) {
push_candidate(map, last.to_string(), prefix.clone());
}
} else {
push_candidate(map, 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() {
push_candidate(map, alias, prefix.clone());
}
} else {
push_candidate(map, 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_all(
path: &syn::Path,
uses: &UseMap,
module: &str,
bare: BareFallback,
) -> Vec<String> {
let segs: Vec<String> = path
.segments
.iter()
.map(|s| strip_raw(&s.ident.to_string()))
.collect();
let Some(head) = segs.first() else {
return Vec::new();
};
if let Some(canonical) = resolve_crate_relative(&segs, module) {
return vec![canonical];
}
match uses.get(head) {
Some(candidates) => candidates
.iter()
.map(|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();
resolve_crate_relative(&combined_segs, module).unwrap_or(combined)
})
.collect(),
None => match bare {
BareFallback::Ignore => Vec::new(),
BareFallback::CurrentModule => {
vec![if module.is_empty() {
format!("crate::{}", segs.join("::"))
} else {
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 apply_crate_root_rename(canonical: String, renames: &ExternRenameMap) -> String {
let segs: Vec<&str> = canonical.split("::").collect();
if segs.len() >= 2 && segs[0] == "crate" {
if let Some(real) = renames.get(segs[1]) {
let mut out = vec![real.as_str()];
out.extend_from_slice(&segs[2..]);
return out.join("::");
}
}
canonical
}
pub(crate) fn apply_bare_alias_rename(canonical: String, renames: &ExternRenameMap) -> String {
let mut segs: Vec<&str> = canonical.split("::").collect();
if let Some(real) = segs.first().and_then(|h| renames.get(*h)) {
segs[0] = real.as_str();
return segs.join("::");
}
canonical
}
pub(crate) fn renames_shadowed(
renames: &ExternRenameMap,
child_mods: &HashSet<String>,
) -> ExternRenameMap {
renames
.iter()
.filter(|(alias, _)| !child_mods.contains(*alias))
.map(|(a, b)| (a.clone(), b.clone()))
.collect()
}
pub(crate) fn collect_reexports(
items: &[FlatItem],
module: &str,
externs: &HashSet<String>,
child_mods: &[(String, FlatItem)],
renames: &ExternRenameMap,
out: &mut ReexportMap,
) {
for flat in items {
let syn::Item::Use(use_item) = &flat.item else {
continue;
};
if matches!(use_item.vis, syn::Visibility::Inherited) {
continue;
}
let mut local = UseMap::new();
collect_use_tree(&use_item.tree, String::new(), &mut local);
let externs_bare = reexport_externs_for(externs, child_mods, flat);
let renames_bare = reexport_renames_for(renames, child_mods, flat);
let (head_externs, head_renames) = if use_item.leading_colon.is_some() {
(externs, renames)
} else {
(&externs_bare, &renames_bare)
};
for (name, written) in local {
let alias = format!("{module}::{name}");
for written in &written {
if let Some(target) =
canonicalize_use_target(written, module, head_externs, head_renames)
{
if target != alias && !is_strict_path_prefix(&alias, &target) {
push_candidate(out, alias.clone(), 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))
}
fn is_strict_path_prefix(prefix: &str, path: &str) -> bool {
path.len() > prefix.len() && path.starts_with(prefix) && path[prefix.len()..].starts_with("::")
}
fn rewrite_longest_alias_prefixes(path: &str, map: &AliasMap) -> Option<Vec<String>> {
let segments: Vec<&str> = path.split("::").collect();
for end in (1..=segments.len()).rev() {
let prefix = segments[..end].join("::");
if let Some(targets) = map.get(&prefix) {
let tail = if end == segments.len() {
""
} else {
&path[prefix.len()..]
};
return Some(targets.iter().map(|t| format!("{t}{tail}")).collect());
}
}
None
}
fn rewrite_targets(
current: &str,
aliases: &AliasMap,
reexports: &ReexportMap,
) -> Option<Vec<String>> {
rewrite_longest_alias_prefixes(current, aliases)
.or_else(|| rewrite_longest_alias_prefixes(current, reexports))
}
pub(crate) fn expand_canonical_paths(
path: &str,
aliases: &AliasMap,
reexports: &ReexportMap,
) -> Vec<String> {
if aliases.is_empty() && reexports.is_empty() {
return vec![path.to_string()];
}
let max_steps = aliases.len() + reexports.len() + 1;
let mut memo: std::collections::HashMap<String, Vec<String>> = std::collections::HashMap::new();
let mut in_stack: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut work: Vec<(String, bool, usize)> = vec![(path.to_string(), false, 0)];
while let Some((current, returning, depth)) = work.pop() {
if returning {
in_stack.remove(¤t);
if memo.contains_key(¤t) {
continue;
}
let mut results = Vec::new();
if let Some(targets) = rewrite_targets(¤t, aliases, reexports) {
for t in &targets {
let child = memo
.get(t.as_str())
.cloned()
.unwrap_or_else(|| vec![t.clone()]);
for r in child {
if !results.contains(&r) {
results.push(r);
}
}
}
} else {
results.push(current.clone());
}
memo.insert(current, results);
continue;
}
if memo.contains_key(¤t) {
continue;
}
if in_stack.contains(¤t) || depth >= max_steps {
continue;
}
in_stack.insert(current.clone());
work.push((current.clone(), true, depth));
if let Some(targets) = rewrite_targets(¤t, aliases, reexports) {
for target in targets.into_iter().rev() {
if !memo.contains_key(&target) {
work.push((target, false, depth + 1));
}
}
}
}
memo.remove(path).unwrap_or_else(|| vec![path.to_string()])
}
pub(crate) fn alias_nominal_targets<'a>(ty: &'a syn::Type, acc: &mut Vec<&'a syn::Path>) {
let mut pending = vec![ty];
while let Some(ty) = pending.pop() {
match ty {
syn::Type::Path(tp) => {
if tp.qself.is_none()
&& tp
.path
.segments
.iter()
.all(|s| matches!(s.arguments, syn::PathArguments::None))
{
acc.push(&tp.path);
}
}
syn::Type::Reference(tr) => pending.push(&tr.elem),
syn::Type::Ptr(tp) => pending.push(&tp.elem),
syn::Type::Tuple(tt) => pending.extend(tt.elems.iter().rev()),
syn::Type::Slice(ts) => pending.push(&ts.elem),
syn::Type::Array(ta) => pending.push(&ta.elem),
syn::Type::Group(tg) => pending.push(&tg.elem),
syn::Type::Paren(tp) => pending.push(&tp.elem),
_ => {}
}
}
}
pub(crate) fn bare_local_alias(
path: &syn::Path,
module: &str,
aliases: &AliasMap,
) -> Option<String> {
bare_single_segment_ident(path)
.map(|n| format!("{module}::{n}"))
.filter(|key| aliases.contains_key(key))
}
pub(crate) fn bare_single_segment_ident(path: &syn::Path) -> 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;
}
Some(strip_raw(&seg.ident.to_string()))
}
#[cfg(test)]
mod tests {
use super::alias_nominal_targets;
#[test]
fn deeply_nested_alias_targets_use_a_bounded_native_stack() {
const DEPTH: usize = 32_768;
let mut ty: syn::Type = syn::parse_quote!(Leaf);
for _ in 0..DEPTH {
ty = syn::Type::Paren(syn::TypeParen {
paren_token: syn::token::Paren::default(),
elem: Box::new(ty),
});
}
let ty = Box::leak(Box::new(ty));
let mut targets = Vec::new();
alias_nominal_targets(ty, &mut targets);
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].segments[0].ident, "Leaf");
}
}