Skip to content

Commit 2998706

Browse files
committed
Auto merge of #10536 - mkrasnitski:suggestions, r=flip1995
Add suggestions to `extra_unused_type_parameters` Change the `extra_unused_type_parameters` lint to provide machine applicable suggestions rather than just help messages. Exception to this are cases when any unused type parameters appear bounded in where clauses - for now I've deemed these cases unfixable and separated them out. Future work might be able to provide suggestions in these cases. Also, added a test case for the `avoid_breaking_exported_api` config option. r? `@flip1995` changelog: [`extra_unused_type_parameters`]: Now provides fixable suggestions.
2 parents 89160b6 + 50d92d0 commit 2998706

8 files changed

+320
-113
lines changed

clippy_lints/src/extra_unused_type_parameters.rs

Lines changed: 117 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use clippy_utils::diagnostics::span_lint_and_help;
1+
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
22
use clippy_utils::trait_ref_of_method;
3-
use rustc_data_structures::fx::FxHashMap;
4-
use rustc_errors::MultiSpan;
3+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
4+
use rustc_errors::Applicability;
55
use rustc_hir::intravisit::{walk_impl_item, walk_item, walk_param_bound, walk_ty, Visitor};
66
use rustc_hir::{
7-
BodyId, ExprKind, GenericBound, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind,
7+
BodyId, ExprKind, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind,
88
PredicateOrigin, Ty, TyKind, WherePredicate,
99
};
1010
use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -53,13 +53,19 @@ impl ExtraUnusedTypeParameters {
5353
}
5454
}
5555

56-
/// Don't lint external macros or functions with empty bodies. Also, don't lint public items if
57-
/// the `avoid_breaking_exported_api` config option is set.
58-
fn check_false_positive(&self, cx: &LateContext<'_>, span: Span, def_id: LocalDefId, body_id: BodyId) -> bool {
56+
/// Don't lint external macros or functions with empty bodies. Also, don't lint exported items
57+
/// if the `avoid_breaking_exported_api` config option is set.
58+
fn is_empty_exported_or_macro(
59+
&self,
60+
cx: &LateContext<'_>,
61+
span: Span,
62+
def_id: LocalDefId,
63+
body_id: BodyId,
64+
) -> bool {
5965
let body = cx.tcx.hir().body(body_id).value;
6066
let fn_empty = matches!(&body.kind, ExprKind::Block(blk, None) if blk.stmts.is_empty() && blk.expr.is_none());
6167
let is_exported = cx.effective_visibilities.is_exported(def_id);
62-
in_external_macro(cx.sess(), span) || (self.avoid_breaking_exported_api && is_exported) || fn_empty
68+
in_external_macro(cx.sess(), span) || fn_empty || (is_exported && self.avoid_breaking_exported_api)
6369
}
6470
}
6571

@@ -69,85 +75,129 @@ impl_lint_pass!(ExtraUnusedTypeParameters => [EXTRA_UNUSED_TYPE_PARAMETERS]);
6975
/// trait bounds those parameters have.
7076
struct TypeWalker<'cx, 'tcx> {
7177
cx: &'cx LateContext<'tcx>,
72-
/// Collection of all the function's type parameters.
78+
/// Collection of the function's type parameters. Once the function has been walked, this will
79+
/// contain only unused type parameters.
7380
ty_params: FxHashMap<DefId, Span>,
74-
/// Collection of any (inline) trait bounds corresponding to each type parameter.
75-
bounds: FxHashMap<DefId, Span>,
81+
/// Collection of any inline trait bounds corresponding to each type parameter.
82+
inline_bounds: FxHashMap<DefId, Span>,
83+
/// Collection of any type parameters with trait bounds that appear in a where clause.
84+
where_bounds: FxHashSet<DefId>,
7685
/// The entire `Generics` object of the function, useful for querying purposes.
7786
generics: &'tcx Generics<'tcx>,
78-
/// The value of this will remain `true` if *every* parameter:
79-
/// 1. Is a type parameter, and
80-
/// 2. Goes unused in the function.
81-
/// Otherwise, if any type parameters end up being used, or if any lifetime or const-generic
82-
/// parameters are present, this will be set to `false`.
83-
all_params_unused: bool,
8487
}
8588

8689
impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> {
8790
fn new(cx: &'cx LateContext<'tcx>, generics: &'tcx Generics<'tcx>) -> Self {
88-
let mut all_params_unused = true;
8991
let ty_params = generics
9092
.params
9193
.iter()
92-
.filter_map(|param| {
93-
if let GenericParamKind::Type { synthetic, .. } = param.kind {
94-
(!synthetic).then_some((param.def_id.into(), param.span))
95-
} else {
96-
if !param.is_elided_lifetime() {
97-
all_params_unused = false;
98-
}
99-
None
100-
}
94+
.filter_map(|param| match param.kind {
95+
GenericParamKind::Type { synthetic, .. } if !synthetic => Some((param.def_id.into(), param.span)),
96+
_ => None,
10197
})
10298
.collect();
10399

104100
Self {
105101
cx,
106102
ty_params,
107-
bounds: FxHashMap::default(),
103+
inline_bounds: FxHashMap::default(),
104+
where_bounds: FxHashSet::default(),
108105
generics,
109-
all_params_unused,
110106
}
111107
}
112108

113-
fn mark_param_used(&mut self, def_id: DefId) {
114-
if self.ty_params.remove(&def_id).is_some() {
115-
self.all_params_unused = false;
116-
}
109+
fn get_bound_span(&self, param: &'tcx GenericParam<'tcx>) -> Span {
110+
self.inline_bounds
111+
.get(&param.def_id.to_def_id())
112+
.map_or(param.span, |bound_span| param.span.with_hi(bound_span.hi()))
113+
}
114+
115+
fn emit_help(&self, spans: Vec<Span>, msg: &str, help: &'static str) {
116+
span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, None, help);
117+
}
118+
119+
fn emit_sugg(&self, spans: Vec<Span>, msg: &str, help: &'static str) {
120+
let suggestions: Vec<(Span, String)> = spans.iter().copied().zip(std::iter::repeat(String::new())).collect();
121+
span_lint_and_then(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, |diag| {
122+
diag.multipart_suggestion(help, suggestions, Applicability::MachineApplicable);
123+
});
117124
}
118125

119126
fn emit_lint(&self) {
120-
let (msg, help) = match self.ty_params.len() {
127+
let explicit_params = self
128+
.generics
129+
.params
130+
.iter()
131+
.filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait())
132+
.collect::<Vec<_>>();
133+
134+
let extra_params = explicit_params
135+
.iter()
136+
.enumerate()
137+
.filter(|(_, param)| self.ty_params.contains_key(&param.def_id.to_def_id()))
138+
.collect::<Vec<_>>();
139+
140+
let (msg, help) = match extra_params.len() {
121141
0 => return,
122142
1 => (
123-
"type parameter goes unused in function definition",
143+
format!(
144+
"type parameter `{}` goes unused in function definition",
145+
extra_params[0].1.name.ident()
146+
),
124147
"consider removing the parameter",
125148
),
126149
_ => (
127-
"type parameters go unused in function definition",
150+
format!(
151+
"type parameters go unused in function definition: {}",
152+
extra_params
153+
.iter()
154+
.map(|(_, param)| param.name.ident().to_string())
155+
.collect::<Vec<_>>()
156+
.join(", ")
157+
),
128158
"consider removing the parameters",
129159
),
130160
};
131161

132-
let source_map = self.cx.sess().source_map();
133-
let span = if self.all_params_unused {
134-
self.generics.span.into() // Remove the entire list of generics
162+
// If any parameters are bounded in where clauses, don't try to form a suggestion.
163+
// Otherwise, the leftover where bound would produce code that wouldn't compile.
164+
if extra_params
165+
.iter()
166+
.any(|(_, param)| self.where_bounds.contains(&param.def_id.to_def_id()))
167+
{
168+
let spans = extra_params
169+
.iter()
170+
.map(|(_, param)| self.get_bound_span(param))
171+
.collect::<Vec<_>>();
172+
self.emit_help(spans, &msg, help);
135173
} else {
136-
MultiSpan::from_spans(
137-
self.ty_params
174+
let spans = if explicit_params.len() == extra_params.len() {
175+
vec![self.generics.span] // Remove the entire list of generics
176+
} else {
177+
let mut end: Option<LocalDefId> = None;
178+
extra_params
138179
.iter()
139-
.map(|(def_id, &span)| {
140-
// Extend the span past any trait bounds, and include the comma at the end.
141-
let span_to_extend = self.bounds.get(def_id).copied().map_or(span, Span::shrink_to_hi);
142-
let comma_range = source_map.span_extend_to_next_char(span_to_extend, '>', false);
143-
let comma_span = source_map.span_through_char(comma_range, ',');
144-
span.with_hi(comma_span.hi())
180+
.rev()
181+
.map(|(idx, param)| {
182+
if let Some(next) = explicit_params.get(idx + 1) && end != Some(next.def_id) {
183+
// Extend the current span forward, up until the next param in the list.
184+
param.span.until(next.span)
185+
} else {
186+
// Extend the current span back to include the comma following the previous
187+
// param. If the span of the next param in the list has already been
188+
// extended, we continue the chain. This is why we're iterating in reverse.
189+
end = Some(param.def_id);
190+
191+
// idx will never be 0, else we'd be removing the entire list of generics
192+
let prev = explicit_params[idx - 1];
193+
let prev_span = self.get_bound_span(prev);
194+
self.get_bound_span(param).with_lo(prev_span.hi())
195+
}
145196
})
146-
.collect(),
147-
)
197+
.collect()
198+
};
199+
self.emit_sugg(spans, &msg, help);
148200
};
149-
150-
span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, span, msg, None, help);
151201
}
152202
}
153203

@@ -162,7 +212,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
162212

163213
fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) {
164214
if let Some((def_id, _)) = t.peel_refs().as_generic_param() {
165-
self.mark_param_used(def_id);
215+
self.ty_params.remove(&def_id);
166216
} else if let TyKind::OpaqueDef(id, _, _) = t.kind {
167217
// Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls
168218
// `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're
@@ -176,9 +226,18 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
176226

177227
fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) {
178228
if let WherePredicate::BoundPredicate(predicate) = predicate {
179-
// Collect spans for any bounds on type parameters. We only keep bounds that appear in
180-
// the list of generics (not in a where-clause).
229+
// Collect spans for any bounds on type parameters.
181230
if let Some((def_id, _)) = predicate.bounded_ty.peel_refs().as_generic_param() {
231+
match predicate.origin {
232+
PredicateOrigin::GenericParam => {
233+
self.inline_bounds.insert(def_id, predicate.span);
234+
},
235+
PredicateOrigin::WhereClause => {
236+
self.where_bounds.insert(def_id);
237+
},
238+
PredicateOrigin::ImplTrait => (),
239+
}
240+
182241
// If the bound contains non-public traits, err on the safe side and don't lint the
183242
// corresponding parameter.
184243
if !predicate
@@ -187,12 +246,10 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
187246
.filter_map(bound_to_trait_def_id)
188247
.all(|id| self.cx.effective_visibilities.is_exported(id))
189248
{
190-
self.mark_param_used(def_id);
191-
} else if let PredicateOrigin::GenericParam = predicate.origin {
192-
self.bounds.insert(def_id, predicate.span);
249+
self.ty_params.remove(&def_id);
193250
}
194251
}
195-
// Only walk the right-hand side of where-bounds
252+
// Only walk the right-hand side of where bounds
196253
for bound in predicate.bounds {
197254
walk_param_bound(self, bound);
198255
}
@@ -207,7 +264,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
207264
impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
208265
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
209266
if let ItemKind::Fn(_, generics, body_id) = item.kind
210-
&& !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id)
267+
&& !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id)
211268
{
212269
let mut walker = TypeWalker::new(cx, generics);
213270
walk_item(&mut walker, item);
@@ -219,7 +276,7 @@ impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
219276
// Only lint on inherent methods, not trait methods.
220277
if let ImplItemKind::Fn(.., body_id) = item.kind
221278
&& trait_ref_of_method(cx, item.owner_id.def_id).is_none()
222-
&& !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id)
279+
&& !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id)
223280
{
224281
let mut walker = TypeWalker::new(cx, item.generics);
225282
walk_impl_item(&mut walker, item);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
avoid-breaking-exported-api = true
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub struct S;
2+
3+
impl S {
4+
pub fn exported_fn<T>() {
5+
unimplemented!();
6+
}
7+
}
8+
9+
fn main() {}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// run-rustfix
2+
3+
#![allow(unused, clippy::needless_lifetimes)]
4+
#![warn(clippy::extra_unused_type_parameters)]
5+
6+
fn unused_ty(x: u8) {
7+
unimplemented!()
8+
}
9+
10+
fn unused_multi(x: u8) {
11+
unimplemented!()
12+
}
13+
14+
fn unused_with_lt<'a>(x: &'a u8) {
15+
unimplemented!()
16+
}
17+
18+
fn used_ty<T>(x: T, y: u8) {}
19+
20+
fn used_ref<'a, T>(x: &'a T) {}
21+
22+
fn used_ret<T: Default>(x: u8) -> T {
23+
T::default()
24+
}
25+
26+
fn unused_bounded<U>(x: U) {
27+
unimplemented!();
28+
}
29+
30+
fn some_unused<B, C>(b: B, c: C) {
31+
unimplemented!();
32+
}
33+
34+
fn used_opaque<A>(iter: impl Iterator<Item = A>) -> usize {
35+
iter.count()
36+
}
37+
38+
fn used_ret_opaque<A>() -> impl Iterator<Item = A> {
39+
std::iter::empty()
40+
}
41+
42+
fn used_vec_box<T>(x: Vec<Box<T>>) {}
43+
44+
fn used_body<T: Default + ToString>() -> String {
45+
T::default().to_string()
46+
}
47+
48+
fn used_closure<T: Default + ToString>() -> impl Fn() {
49+
|| println!("{}", T::default().to_string())
50+
}
51+
52+
struct S;
53+
54+
impl S {
55+
fn unused_ty_impl(&self) {
56+
unimplemented!()
57+
}
58+
}
59+
60+
// Don't lint on trait methods
61+
trait Foo {
62+
fn bar<T>(&self);
63+
}
64+
65+
impl Foo for S {
66+
fn bar<T>(&self) {}
67+
}
68+
69+
fn skip_index<A, Iter>(iter: Iter, index: usize) -> impl Iterator<Item = A>
70+
where
71+
Iter: Iterator<Item = A>,
72+
{
73+
iter.enumerate()
74+
.filter_map(move |(i, a)| if i == index { None } else { Some(a) })
75+
}
76+
77+
fn unused_opaque(dummy: impl Default) {
78+
unimplemented!()
79+
}
80+
81+
mod unexported_trait_bounds {
82+
mod private {
83+
pub trait Private {}
84+
}
85+
86+
fn priv_trait_bound<T: private::Private>() {
87+
unimplemented!();
88+
}
89+
90+
fn unused_with_priv_trait_bound<T: private::Private>() {
91+
unimplemented!();
92+
}
93+
}
94+
95+
mod issue10319 {
96+
fn assert_send<T: Send>() {}
97+
98+
fn assert_send_where<T>()
99+
where
100+
T: Send,
101+
{
102+
}
103+
}
104+
105+
fn main() {}

0 commit comments

Comments
 (0)