|
1 | | -use clippy_utils::diagnostics::span_lint_and_help; |
| 1 | +use clippy_utils::diagnostics::span_lint_and_then; |
| 2 | +use rustc_errors::{Applicability, MultiSpan}; |
2 | 3 | use rustc_hir::def_id::DefId; |
3 | 4 | use rustc_hir::hir_id::OwnerId; |
4 | 5 | use rustc_hir::{ImplItem, ImplItemKind, ItemKind, Node}; |
5 | 6 | use rustc_lint::LateContext; |
6 | | -use rustc_span::symbol::{Ident, Symbol}; |
| 7 | +use rustc_span::symbol::{kw, Ident, Symbol}; |
7 | 8 | use rustc_span::Span; |
8 | 9 |
|
9 | 10 | use super::RENAMED_FUNCTION_PARAMS; |
10 | 11 |
|
11 | 12 | pub(super) fn check_impl_item(cx: &LateContext<'_>, item: &ImplItem<'_>) { |
12 | | - if let ImplItemKind::Fn(_, body_id) = item.kind && |
13 | | - let Some(did) = impled_item_def_id(cx, item.owner_id) |
| 13 | + if !item.span.from_expansion() |
| 14 | + && let ImplItemKind::Fn(_, body_id) = item.kind |
| 15 | + && let Some(did) = trait_item_def_id_of_impl(cx, item.owner_id) |
14 | 16 | { |
15 | 17 | let mut param_idents_iter = cx.tcx.hir().body_param_names(body_id); |
16 | 18 | let mut default_param_idents_iter = cx.tcx.fn_arg_names(did).iter().copied(); |
17 | 19 |
|
18 | | - let renames = renamed_params(&mut default_param_idents_iter, &mut param_idents_iter); |
19 | | - // FIXME: Should we use `MultiSpan` to combine output together? |
20 | | - // But how should we display help message if so. |
21 | | - for rename in renames { |
22 | | - span_lint_and_help( |
| 20 | + let renames = RenamedFnArgs::new(&mut default_param_idents_iter, &mut param_idents_iter); |
| 21 | + if !renames.0.is_empty() { |
| 22 | + let multi_span = renames.multi_span(); |
| 23 | + let plural = if renames.0.len() == 1 { "" } else { "s" }; |
| 24 | + span_lint_and_then( |
23 | 25 | cx, |
24 | 26 | RENAMED_FUNCTION_PARAMS, |
25 | | - rename.renamed_span, |
26 | | - "function parameter name was renamed from its trait default", |
27 | | - None, |
28 | | - &format!("consider changing the name to: '{}'", rename.default_name.as_str()) |
| 27 | + multi_span, |
| 28 | + &format!("renamed function parameter{plural} of trait impl"), |
| 29 | + |diag| { |
| 30 | + diag.multipart_suggestion( |
| 31 | + format!("consider using the default name{plural}"), |
| 32 | + renames.0, |
| 33 | + Applicability::Unspecified, |
| 34 | + ); |
| 35 | + }, |
29 | 36 | ); |
30 | 37 | } |
31 | 38 | } |
32 | 39 | } |
33 | 40 |
|
34 | | -struct RenamedParam { |
35 | | - renamed_span: Span, |
36 | | - default_name: Symbol, |
37 | | -} |
| 41 | +struct RenamedFnArgs(Vec<(Span, String)>); |
38 | 42 |
|
39 | | -fn renamed_params<I, T>(default_names: &mut I, current_names: &mut T) -> Vec<RenamedParam> |
40 | | -where |
41 | | - I: Iterator<Item = Ident>, |
42 | | - T: Iterator<Item = Ident>, |
43 | | -{ |
44 | | - let mut renamed = vec![]; |
45 | | - // FIXME: Should we stop if they have different length? |
46 | | - while let (Some(def_name), Some(cur_name)) = (default_names.next(), current_names.next()) { |
47 | | - let current_name = cur_name.name; |
48 | | - let default_name = def_name.name; |
49 | | - if is_ignored_or_empty_symbol(current_name) || is_ignored_or_empty_symbol(default_name) { |
50 | | - continue; |
51 | | - } |
52 | | - if current_name != default_name { |
53 | | - renamed.push(RenamedParam { |
54 | | - renamed_span: cur_name.span, |
55 | | - default_name, |
56 | | - }); |
| 43 | +impl RenamedFnArgs { |
| 44 | + /// Comparing between an iterator of default names and one with current names, |
| 45 | + /// then collect the ones that got renamed. |
| 46 | + fn new<I, T>(default_names: &mut I, current_names: &mut T) -> Self |
| 47 | + where |
| 48 | + I: Iterator<Item = Ident>, |
| 49 | + T: Iterator<Item = Ident>, |
| 50 | + { |
| 51 | + let mut renamed: Vec<(Span, String)> = vec![]; |
| 52 | + |
| 53 | + debug_assert!(default_names.size_hint() == current_names.size_hint()); |
| 54 | + while let (Some(def_name), Some(cur_name)) = (default_names.next(), current_names.next()) { |
| 55 | + let current_name = cur_name.name; |
| 56 | + let default_name = def_name.name; |
| 57 | + if is_unused_or_empty_symbol(current_name) || is_unused_or_empty_symbol(default_name) { |
| 58 | + continue; |
| 59 | + } |
| 60 | + if current_name != default_name { |
| 61 | + renamed.push((cur_name.span, default_name.to_string())); |
| 62 | + } |
57 | 63 | } |
| 64 | + |
| 65 | + Self(renamed) |
| 66 | + } |
| 67 | + |
| 68 | + fn multi_span(&self) -> MultiSpan { |
| 69 | + self.0 |
| 70 | + .iter() |
| 71 | + .map(|(span, _)| span) |
| 72 | + .copied() |
| 73 | + .collect::<Vec<Span>>() |
| 74 | + .into() |
58 | 75 | } |
59 | | - renamed |
60 | 76 | } |
61 | 77 |
|
62 | | -fn is_ignored_or_empty_symbol(symbol: Symbol) -> bool { |
63 | | - let s = symbol.as_str(); |
64 | | - s.is_empty() || s.starts_with('_') |
| 78 | +fn is_unused_or_empty_symbol(symbol: Symbol) -> bool { |
| 79 | + // FIXME: `body_param_names` currently returning empty symbols for `wild` as well, |
| 80 | + // so we need to check if the symbol is empty first. |
| 81 | + // Therefore the check of whether it's equal to [`kw::Underscore`] has no use for now, |
| 82 | + // but it would be nice to keep it here just to be future-proof. |
| 83 | + symbol.is_empty() || symbol == kw::Underscore || symbol.as_str().starts_with('_') |
65 | 84 | } |
66 | 85 |
|
67 | | -fn impled_item_def_id(cx: &LateContext<'_>, impl_item_id: OwnerId) -> Option<DefId> { |
68 | | - let trait_node = cx.tcx.hir().find_parent(impl_item_id.into())?; |
69 | | - if let Node::Item(item) = trait_node && |
70 | | - let ItemKind::Impl(impl_) = &item.kind |
| 86 | +/// Get the [`trait_item_def_id`](rustc_hir::hir::ImplItemRef::trait_item_def_id) of an impl item. |
| 87 | +fn trait_item_def_id_of_impl(cx: &LateContext<'_>, impl_item_id: OwnerId) -> Option<DefId> { |
| 88 | + let trait_node = cx.tcx.parent_hir_node(impl_item_id.into()); |
| 89 | + if let Node::Item(item) = trait_node |
| 90 | + && let ItemKind::Impl(impl_) = &item.kind |
71 | 91 | { |
72 | 92 | impl_.items.iter().find_map(|item| { |
73 | 93 | if item.id.owner_id == impl_item_id { |
|
0 commit comments