1
- use clippy_utils:: diagnostics:: span_lint_and_help;
1
+ use clippy_utils:: diagnostics:: { span_lint_and_help, span_lint_and_then } ;
2
2
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 ;
5
5
use rustc_hir:: intravisit:: { walk_impl_item, walk_item, walk_param_bound, walk_ty, Visitor } ;
6
6
use rustc_hir:: {
7
- BodyId , ExprKind , GenericBound , GenericParamKind , Generics , ImplItem , ImplItemKind , Item , ItemKind ,
7
+ BodyId , ExprKind , GenericBound , GenericParam , GenericParamKind , Generics , ImplItem , ImplItemKind , Item , ItemKind ,
8
8
PredicateOrigin , Ty , TyKind , WherePredicate ,
9
9
} ;
10
10
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
@@ -53,13 +53,19 @@ impl ExtraUnusedTypeParameters {
53
53
}
54
54
}
55
55
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 {
59
65
let body = cx. tcx . hir ( ) . body ( body_id) . value ;
60
66
let fn_empty = matches ! ( & body. kind, ExprKind :: Block ( blk, None ) if blk. stmts. is_empty( ) && blk. expr. is_none( ) ) ;
61
67
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 )
63
69
}
64
70
}
65
71
@@ -69,85 +75,129 @@ impl_lint_pass!(ExtraUnusedTypeParameters => [EXTRA_UNUSED_TYPE_PARAMETERS]);
69
75
/// trait bounds those parameters have.
70
76
struct TypeWalker < ' cx , ' tcx > {
71
77
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.
73
80
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 > ,
76
85
/// The entire `Generics` object of the function, useful for querying purposes.
77
86
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 ,
84
87
}
85
88
86
89
impl < ' cx , ' tcx > TypeWalker < ' cx , ' tcx > {
87
90
fn new ( cx : & ' cx LateContext < ' tcx > , generics : & ' tcx Generics < ' tcx > ) -> Self {
88
- let mut all_params_unused = true ;
89
91
let ty_params = generics
90
92
. params
91
93
. 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 ,
101
97
} )
102
98
. collect ( ) ;
103
99
104
100
Self {
105
101
cx,
106
102
ty_params,
107
- bounds : FxHashMap :: default ( ) ,
103
+ inline_bounds : FxHashMap :: default ( ) ,
104
+ where_bounds : FxHashSet :: default ( ) ,
108
105
generics,
109
- all_params_unused,
110
106
}
111
107
}
112
108
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
+ } ) ;
117
124
}
118
125
119
126
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 ( ) {
121
141
0 => return ,
122
142
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
+ ) ,
124
147
"consider removing the parameter" ,
125
148
) ,
126
149
_ => (
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
+ ) ,
128
158
"consider removing the parameters" ,
129
159
) ,
130
160
} ;
131
161
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) ;
135
173
} 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
138
179
. 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
+ }
145
196
} )
146
- . collect ( ) ,
147
- )
197
+ . collect ( )
198
+ } ;
199
+ self . emit_sugg ( spans, & msg, help) ;
148
200
} ;
149
-
150
- span_lint_and_help ( self . cx , EXTRA_UNUSED_TYPE_PARAMETERS , span, msg, None , help) ;
151
201
}
152
202
}
153
203
@@ -162,7 +212,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
162
212
163
213
fn visit_ty ( & mut self , t : & ' tcx Ty < ' tcx > ) {
164
214
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) ;
166
216
} else if let TyKind :: OpaqueDef ( id, _, _) = t. kind {
167
217
// Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls
168
218
// `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> {
176
226
177
227
fn visit_where_predicate ( & mut self , predicate : & ' tcx WherePredicate < ' tcx > ) {
178
228
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.
181
230
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
+
182
241
// If the bound contains non-public traits, err on the safe side and don't lint the
183
242
// corresponding parameter.
184
243
if !predicate
@@ -187,12 +246,10 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
187
246
. filter_map ( bound_to_trait_def_id)
188
247
. all ( |id| self . cx . effective_visibilities . is_exported ( id) )
189
248
{
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) ;
193
250
}
194
251
}
195
- // Only walk the right-hand side of where- bounds
252
+ // Only walk the right-hand side of where bounds
196
253
for bound in predicate. bounds {
197
254
walk_param_bound ( self , bound) ;
198
255
}
@@ -207,7 +264,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
207
264
impl < ' tcx > LateLintPass < ' tcx > for ExtraUnusedTypeParameters {
208
265
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' tcx > ) {
209
266
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)
211
268
{
212
269
let mut walker = TypeWalker :: new ( cx, generics) ;
213
270
walk_item ( & mut walker, item) ;
@@ -219,7 +276,7 @@ impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
219
276
// Only lint on inherent methods, not trait methods.
220
277
if let ImplItemKind :: Fn ( .., body_id) = item. kind
221
278
&& 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)
223
280
{
224
281
let mut walker = TypeWalker :: new ( cx, item. generics ) ;
225
282
walk_impl_item ( & mut walker, item) ;
0 commit comments