use rustc_ast::visit::{self, Visitor};
use rustc_ast::{Crate, Item, ItemKind, UseTree, UseTreeKind};
use rustc_span::{Symbol, kw};
use super::config::{PreferDeriveMoreOverThiserror, path_matches_thiserror};
pub(super) fn collect_aliases(rule: &mut PreferDeriveMoreOverThiserror, krate: &Crate) {
rule.aliases.clear();
rule.crate_aliases.clear();
for phase in [CollectPhase::CrateAliases, CollectPhase::LeafAliases] {
let mut collector = AliasCollector {
rule: &mut *rule,
phase,
};
visit::walk_crate(&mut collector, krate);
}
}
#[derive(Clone, Copy)]
enum CollectPhase {
CrateAliases,
LeafAliases,
}
struct AliasCollector<'a> {
rule: &'a mut PreferDeriveMoreOverThiserror,
phase: CollectPhase,
}
impl<'ast> Visitor<'ast> for AliasCollector<'_> {
fn visit_item(&mut self, item: &'ast Item) {
match &item.kind {
ItemKind::Use(use_tree) => {
walk_use_tree_for_aliases(self.rule, use_tree, &[], self.phase);
}
ItemKind::ExternCrate(orig, ident) => {
if matches!(self.phase, CollectPhase::CrateAliases) {
let original = orig.unwrap_or(ident.name);
if self.rule.thiserror_crates.contains(&original) {
self.rule.crate_aliases.insert(ident.name, original);
}
}
}
_ => {}
}
visit::walk_item(self, item);
}
}
fn walk_use_tree_for_aliases(
rule: &mut PreferDeriveMoreOverThiserror,
tree: &UseTree,
parent: &[Symbol],
phase: CollectPhase,
) {
let mut path: Vec<Symbol> = parent.to_vec();
for (idx, segment) in tree.prefix.segments.iter().enumerate() {
if segment.ident.name == kw::PathRoot {
continue;
}
if !parent.is_empty() && idx == 0 && segment.ident.name == kw::SelfLower {
continue;
}
path.push(segment.ident.name);
}
match &tree.kind {
UseTreeKind::Simple(rename) => match phase {
CollectPhase::CrateAliases => {
if path.len() == 1 && rule.thiserror_crates.contains(&path[0]) {
let local = rename.map(|ident| ident.name).unwrap_or(path[0]);
rule.crate_aliases.insert(local, path[0]);
}
}
CollectPhase::LeafAliases => {
let matches_direct = path_matches_thiserror(&rule.thiserror_paths, &path);
let matches_via_alias = !matches_direct
&& match path.split_first() {
Some((first, rest)) => rule
.crate_aliases
.get(first)
.map(|&expanded| {
let mut expanded_path = Vec::with_capacity(path.len());
expanded_path.push(expanded);
expanded_path.extend_from_slice(rest);
path_matches_thiserror(&rule.thiserror_paths, &expanded_path)
})
.unwrap_or(false),
None => false,
};
if matches_direct || matches_via_alias {
let local = rename
.map(|ident| ident.name)
.or_else(|| path.last().copied());
if let Some(local) = local {
rule.aliases.insert(local);
}
}
}
},
UseTreeKind::Glob(_) => {
if matches!(phase, CollectPhase::CrateAliases) {
return;
}
let expanded_path = match path.split_first() {
Some((first, rest)) => match rule.crate_aliases.get(first) {
Some(&expanded_first) => {
let mut out = Vec::with_capacity(path.len());
out.push(expanded_first);
out.extend_from_slice(rest);
out
}
None => path.clone(),
},
None => path.clone(),
};
for cfg in &rule.thiserror_paths {
if cfg.len() == expanded_path.len() + 1
&& cfg.starts_with(&expanded_path)
&& let Some(&last) = cfg.last()
{
rule.aliases.insert(last);
}
}
}
UseTreeKind::Nested { items, .. } => {
for (nested, _) in items {
walk_use_tree_for_aliases(rule, nested, &path, phase);
}
}
}
}