| 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)) |
| } |