use std::fmt::Write;
use syn::{File, Item};
use crate::hash::HashSet;
#[derive(Debug, Default, Clone)]
pub struct FileScope {
pub in_scope_idents: HashSet<String>,
pub existing_use_paths: HashSet<String>,
}
pub fn collect_file_scope(file: &File) -> FileScope {
let mut scope = FileScope::default();
let mut buf = String::with_capacity(64);
for item in &file.items {
let ident = match item {
Item::Fn(f) => Some(&f.sig.ident),
Item::Struct(s) => Some(&s.ident),
Item::Enum(e) => Some(&e.ident),
Item::Const(c) => Some(&c.ident),
Item::Static(s) => Some(&s.ident),
Item::Trait(t) => Some(&t.ident),
Item::Type(t) => Some(&t.ident),
Item::Mod(m) => Some(&m.ident),
Item::ExternCrate(ec) => Some(ec.rename.as_ref().map_or(&ec.ident, |(_, r)| r)),
Item::Use(u) => {
buf.clear();
walk_use_tree(&u.tree, &mut buf, &mut scope);
None
}
_ => None,
};
if let Some(ident) = ident {
scope.in_scope_idents.insert(ident.to_string());
}
}
scope
}
fn walk_use_tree(tree: &syn::UseTree, prefix: &mut String, scope: &mut FileScope) {
let prev_len = prefix.len();
match tree {
syn::UseTree::Path(p) => {
if !prefix.is_empty() {
prefix.push_str("::");
}
let _ = write!(prefix, "{}", p.ident);
walk_use_tree(&p.tree, prefix, scope);
prefix.truncate(prev_len);
}
syn::UseTree::Name(n) => {
if n.ident == "self" {
if !prefix.is_empty() {
if let Some(tail) = prefix.rsplit("::").next() {
scope.in_scope_idents.insert(tail.to_string());
}
scope.existing_use_paths.insert(prefix.clone());
}
} else {
let ident = n.ident.to_string();
let full_path = if prefix.is_empty() {
ident.clone()
} else {
format!("{prefix}::{ident}")
};
scope.in_scope_idents.insert(ident);
scope.existing_use_paths.insert(full_path);
}
}
syn::UseTree::Rename(r) => {
if r.rename != "_" {
scope.in_scope_idents.insert(r.rename.to_string());
}
if r.ident != "_" && r.rename == r.ident {
let full_path = if r.ident == "self" {
prefix.clone()
} else if prefix.is_empty() {
r.ident.to_string()
} else {
format!("{prefix}::{}", r.ident)
};
scope.existing_use_paths.insert(full_path);
}
}
syn::UseTree::Group(g) => {
for item in &g.items {
walk_use_tree(item, prefix, scope);
}
}
syn::UseTree::Glob(_) => {
if !prefix.is_empty() {
scope.existing_use_paths.insert(format!("{prefix}::*"));
}
}
}
}