|
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