blob: e25611d48817af645abdd6a2323d742244157a7f [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::def_id::DefIdSet;
use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind, Node, TraitRef};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::symbol::{Ident, kw};
use std::iter;
use super::RENAMED_FUNCTION_PARAMS;
pub(super) fn check_impl_item(cx: &LateContext<'_>, item: &ImplItem<'_>, ignored_traits: &DefIdSet) {
if !item.span.from_expansion()
&& let ImplItemKind::Fn(_, body_id) = item.kind
&& let parent_node = cx.tcx.parent_hir_node(item.hir_id())
&& let Node::Item(parent_item) = parent_node
&& let ItemKind::Impl(Impl {
of_trait: Some(of_trait),
..
}) = &parent_item.kind
&& let Some(did) = cx.tcx.trait_item_of(item.owner_id)
&& !is_from_ignored_trait(&of_trait.trait_ref, ignored_traits)
{
let mut param_idents_iter = cx.tcx.hir_body_param_idents(body_id);
let mut default_param_idents_iter = cx.tcx.fn_arg_idents(did).iter().copied();
let renames = RenamedFnArgs::new(&mut default_param_idents_iter, &mut param_idents_iter);
if !renames.0.is_empty() {
let multi_span = renames.multi_span();
let plural = if renames.0.len() == 1 { "" } else { "s" };
span_lint_and_then(
cx,
RENAMED_FUNCTION_PARAMS,
multi_span,
format!("renamed function parameter{plural} of trait impl"),
|diag| {
diag.multipart_suggestion(
format!("consider using the default name{plural}"),
renames.0,
Applicability::Unspecified,
);
},
);
}
}
}
struct RenamedFnArgs(Vec<(Span, String)>);
impl RenamedFnArgs {
/// Comparing between an iterator of default names and one with current names,
/// then collect the ones that got renamed.
fn new<I1, I2>(default_idents: &mut I1, current_idents: &mut I2) -> Self
where
I1: Iterator<Item = Option<Ident>>,
I2: Iterator<Item = Option<Ident>>,
{
let mut renamed: Vec<(Span, String)> = vec![];
debug_assert!(default_idents.size_hint() == current_idents.size_hint());
for (default_ident, current_ident) in iter::zip(default_idents, current_idents) {
let has_name_to_check = |ident: Option<Ident>| {
ident
.filter(|ident| ident.name != kw::Underscore)
.filter(|ident| !ident.name.as_str().starts_with('_'))
};
if let Some(default_ident) = has_name_to_check(default_ident)
&& let Some(current_ident) = has_name_to_check(current_ident)
&& default_ident.name != current_ident.name
{
renamed.push((current_ident.span, default_ident.to_string()));
}
}
Self(renamed)
}
fn multi_span(&self) -> MultiSpan {
self.0
.iter()
.map(|(span, _)| span)
.copied()
.collect::<Vec<Span>>()
.into()
}
}
fn is_from_ignored_trait(of_trait: &TraitRef<'_>, ignored_traits: &DefIdSet) -> bool {
of_trait
.trait_def_id()
.is_some_and(|trait_did| ignored_traits.contains(&trait_did))
}