use rustc_ast::{UseTree, UseTreeKind};
use rustc_span::kw;
#[derive(Clone, PartialEq, Eq)]
pub(super) enum LeafItem {
Named(String),
Glob,
SelfMod,
}
#[derive(Clone)]
pub(super) struct Leaf {
pub(super) module: Vec<String>,
pub(super) item: LeafItem,
pub(super) rename: Option<String>,
}
impl Leaf {
pub(super) fn is_crate_item(&self) -> bool {
self.module.is_empty() && matches!(self.item, LeafItem::Named(_))
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum TopKind {
Simple,
Glob,
Nested,
}
pub(super) struct StmtInfo {
pub(super) leaves: Vec<Leaf>,
pub(super) top_kind: TopKind,
pub(super) prefix: Vec<String>,
pub(super) collapsed: bool,
}
impl StmtInfo {
pub(super) fn is_crate_item(&self) -> bool {
self.leaves.len() == 1 && self.leaves[0].is_crate_item()
}
pub(super) fn common_module(&self) -> Option<&[String]> {
let first = self.leaves.first()?;
self.leaves
.iter()
.all(|leaf| leaf.module == first.module)
.then_some(first.module.as_slice())
}
pub(super) fn crate_root(&self) -> Option<&str> {
self.prefix.first().map(String::as_str)
}
}
pub(super) fn has_bare_item_dual(leaves: &[Leaf]) -> bool {
leaves.iter().enumerate().any(|(index, leaf)| {
let LeafItem::Named(name) = &leaf.item else {
return false;
};
let mut deeper = leaf.module.clone();
deeper.push(name.clone());
leaves
.iter()
.enumerate()
.any(|(other, sibling)| other != index && sibling.module.starts_with(&deeper))
})
}
pub(super) fn has_self_dual(leaves: &[Leaf]) -> bool {
leaves.iter().enumerate().any(|(index, leaf)| {
matches!(leaf.item, LeafItem::SelfMod) && self_has_splittable_sibling(leaves, index)
})
}
pub(super) fn self_has_splittable_sibling(leaves: &[Leaf], index: usize) -> bool {
let module = &leaves[index].module;
leaves.iter().enumerate().any(|(other, sibling)| {
other != index
&& sibling.module.starts_with(module)
&& !(sibling.module.len() == module.len() && matches!(sibling.item, LeafItem::SelfMod))
})
}
pub(super) fn stmt_info(tree: &UseTree) -> Option<StmtInfo> {
let leaves = flatten(tree)?;
let mut prefix = Vec::with_capacity(tree.prefix.segments.len());
for segment in &tree.prefix.segments {
if segment.ident.name == kw::PathRoot {
return None;
}
prefix.push(segment.ident.name.to_string());
}
let top_kind = match tree.kind {
UseTreeKind::Simple(_) => TopKind::Simple,
UseTreeKind::Glob(_) => TopKind::Glob,
UseTreeKind::Nested { .. } => TopKind::Nested,
};
Some(StmtInfo {
leaves,
top_kind,
prefix,
collapsed: is_collapsed(tree),
})
}
fn flatten(tree: &UseTree) -> Option<Vec<Leaf>> {
let mut out = Vec::new();
flatten_into(tree, &[], &mut out)?;
Some(out)
}
fn flatten_into(tree: &UseTree, prefix: &[String], out: &mut Vec<Leaf>) -> Option<()> {
let mut combined = prefix.to_vec();
for segment in &tree.prefix.segments {
if segment.ident.name == kw::PathRoot {
return None;
}
combined.push(segment.ident.name.to_string());
}
match &tree.kind {
UseTreeKind::Simple(rename) => {
let rename = rename.map(|ident| ident.name.to_string());
let last = combined.last()?;
if last == "self" {
combined.pop();
if combined.is_empty() {
return None;
}
out.push(Leaf {
module: combined,
item: LeafItem::SelfMod,
rename,
});
} else {
let name = combined.pop()?;
out.push(Leaf {
module: combined,
item: LeafItem::Named(name),
rename,
});
}
}
UseTreeKind::Glob(_) => {
if combined.is_empty() {
return None;
}
out.push(Leaf {
module: combined,
item: LeafItem::Glob,
rename: None,
});
}
UseTreeKind::Nested { items, .. } => {
for (subtree, _) in items {
flatten_into(subtree, &combined, out)?;
}
}
}
Some(())
}
fn is_collapsed(tree: &UseTree) -> bool {
let UseTreeKind::Nested { items, .. } = &tree.kind else {
return true;
};
if items.len() == 1 {
let (sub, _) = &items[0];
return is_lone_self(sub) && is_collapsed(sub);
}
let firsts: Vec<_> = items
.iter()
.map(|(sub, _)| sub.prefix.segments.first().map(|seg| seg.ident.name))
.collect();
for index in 0..items.len() {
let Some(first) = firsts[index] else {
continue;
};
if firsts[..index].contains(&Some(first)) {
continue;
}
let mut continuing = 0usize;
let mut bare_terminal = false;
for (other_index, other) in items.iter().enumerate() {
if firsts[other_index] == Some(first) {
if continues(&other.0) {
continuing += 1;
} else {
bare_terminal = true;
}
}
}
if continuing >= 2 && !bare_terminal {
return false;
}
}
items.iter().all(|(sub, _)| is_collapsed(sub))
}
fn continues(tree: &UseTree) -> bool {
tree.prefix.segments.len() > 1 || !matches!(tree.kind, UseTreeKind::Simple(_))
}
fn is_lone_self(tree: &UseTree) -> bool {
matches!(tree.kind, UseTreeKind::Simple(_))
&& tree.prefix.segments.len() == 1
&& tree.prefix.segments[0].ident.name == kw::SelfLower
}