use rustc_ast::{
AttrVec, EnumDef, Item, MetaItemInner, MetaItemKind, UseTree, UseTreeKind, VariantData,
};
use rustc_lint::EarlyContext;
use rustc_span::{Symbol, kw, sym};
use super::config::{PreferDeriveMoreOverThiserror, path_matches_thiserror};
use super::emit::{
emit_derive, emit_use, flag_enum_error_attrs, flag_error_attrs, flag_variant_data_error_attrs,
};
impl PreferDeriveMoreOverThiserror {
pub(super) fn check_struct(&self, cx: &EarlyContext<'_>, attrs: &AttrVec, data: &VariantData) {
if !self.check_derive_attrs(cx, attrs) {
return;
}
flag_error_attrs(cx, attrs);
flag_variant_data_error_attrs(cx, data);
}
pub(super) fn check_enum(&self, cx: &EarlyContext<'_>, attrs: &AttrVec, def: &EnumDef) {
if !self.check_derive_attrs(cx, attrs) {
return;
}
flag_error_attrs(cx, attrs);
flag_enum_error_attrs(cx, def);
}
pub(super) fn check_use(&self, cx: &EarlyContext<'_>, item: &Item, use_tree: &UseTree) {
if self.use_tree_references_thiserror(use_tree) {
emit_use(cx, item.span);
}
}
fn use_tree_references_thiserror(&self, tree: &UseTree) -> bool {
let first = tree
.prefix
.segments
.iter()
.map(|segment| segment.ident.name)
.find(|name| *name != kw::PathRoot);
if let Some(name) = first {
return self.thiserror_crates.contains(&name) || self.crate_aliases.contains_key(&name);
}
if let UseTreeKind::Nested { items, .. } = &tree.kind {
return items
.iter()
.any(|(nested, _)| self.use_tree_references_thiserror(nested));
}
false
}
fn check_derive_attrs(&self, cx: &EarlyContext<'_>, attrs: &AttrVec) -> bool {
let mut thiserror_derived = false;
for attr in attrs {
if attr.has_name(sym::derive)
&& let Some(entries) = attr.meta_item_list()
&& self.check_derive_entries(cx, &entries)
{
thiserror_derived = true;
} else if attr.has_name(sym::cfg_attr)
&& let Some(args) = attr.meta_item_list()
&& self.check_cfg_attr_payload(cx, args.get(1..).unwrap_or(&[]))
{
thiserror_derived = true;
}
}
thiserror_derived
}
fn check_cfg_attr_payload(&self, cx: &EarlyContext<'_>, items: &[MetaItemInner]) -> bool {
let mut found = false;
for item in items {
let Some(meta) = item.meta_item() else {
continue;
};
if meta.has_name(sym::derive)
&& let MetaItemKind::List(entries) = &meta.kind
&& self.check_derive_entries(cx, entries)
{
found = true;
} else if meta.has_name(sym::cfg_attr)
&& let MetaItemKind::List(args) = &meta.kind
&& self.check_cfg_attr_payload(cx, args.get(1..).unwrap_or(&[]))
{
found = true;
}
}
found
}
fn check_derive_entries(&self, cx: &EarlyContext<'_>, entries: &[MetaItemInner]) -> bool {
let mut thiserror_derived = false;
for entry in entries {
let Some(meta) = entry.meta_item() else {
continue;
};
let segments: Vec<Symbol> = meta
.path
.segments
.iter()
.map(|segment| segment.ident.name)
.filter(|name| *name != kw::PathRoot)
.collect();
if self.is_thiserror_derive(&segments) {
thiserror_derived = true;
emit_derive(cx, entry.span());
}
}
thiserror_derived
}
fn is_thiserror_derive(&self, segments: &[Symbol]) -> bool {
if path_matches_thiserror(&self.thiserror_paths, segments) {
return true;
}
if let [name] = segments
&& self.aliases.contains(name)
{
return true;
}
if let [first, rest @ ..] = segments
&& let Some(&expanded_first) = self.crate_aliases.get(first)
{
let mut expanded = Vec::with_capacity(segments.len());
expanded.push(expanded_first);
expanded.extend_from_slice(rest);
return path_matches_thiserror(&self.thiserror_paths, &expanded);
}
false
}
}