use std::collections::BTreeSet;
use std::fmt::Write;
use crate::ir::PATH_SEPARATOR;
use crate::ir::facts::{IrSpan, UsePathFact};
use super::syn_helpers::span_from;
const MAX_USE_TREE_DEPTH: usize = 32;
pub(super) struct UsePathCollector {
paths: Vec<UsePathFact>,
seen: BTreeSet<Box<str>>,
buf: String,
}
impl UsePathCollector {
pub(super) fn new() -> Self {
Self {
paths: Vec::new(),
seen: BTreeSet::new(),
buf: String::new(),
}
}
pub(super) fn collect_use_tree(&mut self, tree: &syn::UseTree, span: IrSpan) {
self.buf.clear();
walk_use_tree(tree, &mut self.buf, 0, &mut self.paths, span);
}
pub(super) fn emit_multi_segment_path(&mut self, path: &syn::Path) {
if path.segments.len() <= 1 {
return;
}
self.buf.clear();
push_segment(&mut self.buf, &path.segments[0].ident);
for seg in path.segments.iter().skip(1) {
write!(self.buf, "{PATH_SEPARATOR}{}", seg.ident).ok();
}
let span = path.segments.first().map_or_else(
|| {
path.leading_colon
.map_or(proc_macro2::Span::call_site(), |c| c.spans[0])
},
|s| s.ident.span(),
);
let path_str = self.buf.as_str();
if self.seen.contains(path_str) {
return;
}
self.seen.insert(Box::from(path_str));
self.paths.push(UsePathFact {
path: Box::from(path_str),
span: span_from(span.start()),
});
}
pub(super) fn finish(self) -> Box<[UsePathFact]> {
self.paths.into_boxed_slice()
}
}
fn push_segment(buf: &mut String, ident: &impl std::fmt::Display) {
match buf.is_empty() {
true => {
write!(buf, "{ident}").ok();
}
false => {
write!(buf, "{PATH_SEPARATOR}{ident}").ok();
}
}
}
fn walk_use_tree(
tree: &syn::UseTree,
buf: &mut String,
depth: usize,
paths: &mut Vec<UsePathFact>,
span: IrSpan,
) {
if depth > MAX_USE_TREE_DEPTH {
return;
}
let restore_len = buf.len();
match tree {
syn::UseTree::Path(syn::UsePath { ident, tree, .. }) => {
push_segment(buf, ident);
walk_use_tree(tree, buf, depth + 1, paths, span);
}
syn::UseTree::Name(syn::UseName { ident, .. }) => {
push_segment(buf, ident);
paths.push(UsePathFact {
path: Box::from(buf.as_str()),
span,
});
}
syn::UseTree::Rename(syn::UseRename { ident, .. }) => {
push_segment(buf, ident);
paths.push(UsePathFact {
path: Box::from(buf.as_str()),
span,
});
}
syn::UseTree::Glob(_) => {
paths.push(UsePathFact {
path: Box::from(&buf[..restore_len]),
span,
});
}
syn::UseTree::Group(syn::UseGroup { items, .. }) => {
for item in items {
walk_use_tree(item, buf, depth + 1, paths, span);
}
}
}
buf.truncate(restore_len);
}