1- use clippy_utils:: diagnostics:: span_lint_and_help;
1+ use clippy_utils:: diagnostics:: { span_lint_and_help, span_lint_and_then } ;
22use 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 ;
55use rustc_hir:: intravisit:: { walk_impl_item, walk_item, walk_param_bound, walk_ty, Visitor } ;
66use 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} ;
1010use 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.
7076struct 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
8689impl < ' 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> {
207264impl < ' 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) ;
0 commit comments