1
- use clippy_utils:: diagnostics:: span_lint_and_then;
1
+ use clippy_utils:: diagnostics:: { span_lint_and_help , span_lint_and_then} ;
2
2
use clippy_utils:: source:: snippet_with_applicability;
3
3
use clippy_utils:: { def_path_res, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs} ;
4
4
use if_chain:: if_chain;
5
5
use rustc_ast:: ast:: LitKind ;
6
+ use rustc_data_structures:: fx:: FxHashSet ;
6
7
use rustc_errors:: Applicability ;
7
8
use rustc_hir as hir;
8
9
use rustc_hir:: def:: { DefKind , Namespace , Res } ;
9
10
use rustc_hir:: def_id:: DefId ;
10
- use rustc_hir:: { ExprKind , Local , Mutability , Node } ;
11
+ use rustc_hir:: { Expr , ExprKind , Local , Mutability , Node } ;
11
12
use rustc_lint:: { LateContext , LateLintPass } ;
12
13
use rustc_middle:: mir:: interpret:: { Allocation , ConstValue , GlobalAlloc } ;
13
14
use rustc_middle:: ty:: { self , AssocKind , DefIdTree , Ty } ;
14
- use rustc_session:: { declare_lint_pass , declare_tool_lint } ;
15
+ use rustc_session:: { declare_tool_lint , impl_lint_pass } ;
15
16
use rustc_span:: symbol:: { Ident , Symbol } ;
17
+ use rustc_span:: Span ;
16
18
17
19
use std:: str;
18
20
@@ -38,11 +40,56 @@ declare_clippy_lint! {
38
40
"using a def path when a diagnostic item or a `LangItem` is available"
39
41
}
40
42
41
- declare_lint_pass ! ( UnnecessaryDefPath => [ UNNECESSARY_DEF_PATH ] ) ;
43
+ impl_lint_pass ! ( UnnecessaryDefPath => [ UNNECESSARY_DEF_PATH ] ) ;
44
+
45
+ #[ derive( Default ) ]
46
+ pub struct UnnecessaryDefPath {
47
+ array_def_ids : FxHashSet < ( DefId , Span ) > ,
48
+ linted_def_ids : FxHashSet < DefId > ,
49
+ }
42
50
43
- #[ allow( clippy:: too_many_lines) ]
44
51
impl < ' tcx > LateLintPass < ' tcx > for UnnecessaryDefPath {
45
52
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx hir:: Expr < ' _ > ) {
53
+ if is_lint_allowed ( cx, UNNECESSARY_DEF_PATH , expr. hir_id ) {
54
+ return ;
55
+ }
56
+
57
+ match expr. kind {
58
+ ExprKind :: Call ( func, args) => self . check_call ( cx, func, args, expr. span ) ,
59
+ ExprKind :: Array ( elements) => self . check_array ( cx, elements, expr. span ) ,
60
+ _ => { } ,
61
+ }
62
+ }
63
+
64
+ fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
65
+ for & ( def_id, span) in & self . array_def_ids {
66
+ if self . linted_def_ids . contains ( & def_id) {
67
+ continue ;
68
+ }
69
+
70
+ let ( msg, sugg) = if let Some ( sym) = cx. tcx . get_diagnostic_name ( def_id) {
71
+ ( "diagnostic item" , format ! ( "sym::{sym}" ) )
72
+ } else if let Some ( sym) = get_lang_item_name ( cx, def_id) {
73
+ ( "language item" , format ! ( "LangItem::{sym}" ) )
74
+ } else {
75
+ continue ;
76
+ } ;
77
+
78
+ span_lint_and_help (
79
+ cx,
80
+ UNNECESSARY_DEF_PATH ,
81
+ span,
82
+ & format ! ( "hardcoded path to a {msg}" ) ,
83
+ None ,
84
+ & format ! ( "convert all references to use `{sugg}`" ) ,
85
+ ) ;
86
+ }
87
+ }
88
+ }
89
+
90
+ impl UnnecessaryDefPath {
91
+ #[ allow( clippy:: too_many_lines) ]
92
+ fn check_call ( & mut self , cx : & LateContext < ' _ > , func : & Expr < ' _ > , args : & [ Expr < ' _ > ] , span : Span ) {
46
93
enum Item {
47
94
LangItem ( Symbol ) ,
48
95
DiagnosticItem ( Symbol ) ,
@@ -54,42 +101,17 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
54
101
& [ "clippy_utils" , "is_expr_path_def_path" ] ,
55
102
] ;
56
103
57
- if is_lint_allowed ( cx, UNNECESSARY_DEF_PATH , expr. hir_id ) {
58
- return ;
59
- }
60
-
61
104
if_chain ! {
62
- if let ExprKind :: Call ( func , [ cx_arg, def_arg, args@..] ) = expr . kind ;
105
+ if let [ cx_arg, def_arg, args@..] = args ;
63
106
if let ExprKind :: Path ( path) = & func. kind;
64
107
if let Some ( id) = cx. qpath_res( path, func. hir_id) . opt_def_id( ) ;
65
108
if let Some ( which_path) = match_any_def_paths( cx, id, PATHS ) ;
66
109
let item_arg = if which_path == 4 { & args[ 1 ] } else { & args[ 0 ] } ;
67
110
// Extract the path to the matched type
68
111
if let Some ( segments) = path_to_matched_type( cx, item_arg) ;
69
112
let segments: Vec <& str > = segments. iter( ) . map( |sym| & * * sym) . collect( ) ;
70
- if let Some ( def_id) = def_path_res ( cx, & segments[ ..] , None ) . opt_def_id ( ) ;
113
+ if let Some ( def_id) = inherent_def_path_res ( cx, & segments[ ..] ) ;
71
114
then {
72
- // def_path_res will match field names before anything else, but for this we want to match
73
- // inherent functions first.
74
- let def_id = if cx. tcx. def_kind( def_id) == DefKind :: Field {
75
- let method_name = * segments. last( ) . unwrap( ) ;
76
- cx. tcx. def_key( def_id) . parent
77
- . and_then( |parent_idx|
78
- cx. tcx. inherent_impls( DefId { index: parent_idx, krate: def_id. krate } ) . iter( )
79
- . find_map( |impl_id| cx. tcx. associated_items( * impl_id)
80
- . find_by_name_and_kind(
81
- cx. tcx,
82
- Ident :: from_str( method_name) ,
83
- AssocKind :: Fn ,
84
- * impl_id,
85
- )
86
- )
87
- )
88
- . map_or( def_id, |item| item. def_id)
89
- } else {
90
- def_id
91
- } ;
92
-
93
115
// Check if the target item is a diagnostic item or LangItem.
94
116
let ( msg, item) = if let Some ( item_name)
95
117
= cx. tcx. diagnostic_items( def_id. krate) . id_to_name. get( & def_id)
@@ -98,9 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
98
120
"use of a def path to a diagnostic item" ,
99
121
Item :: DiagnosticItem ( * item_name) ,
100
122
)
101
- } else if let Some ( lang_item) = cx. tcx. lang_items( ) . items( ) . iter( ) . position( |id| * id == Some ( def_id) ) {
102
- let lang_items = def_path_res( cx, & [ "rustc_hir" , "lang_items" , "LangItem" ] , Some ( Namespace :: TypeNS ) ) . def_id( ) ;
103
- let item_name = cx. tcx. adt_def( lang_items) . variants( ) . iter( ) . nth( lang_item) . unwrap( ) . name;
123
+ } else if let Some ( item_name) = get_lang_item_name( cx, def_id) {
104
124
(
105
125
"use of a def path to a `LangItem`" ,
106
126
Item :: LangItem ( item_name) ,
@@ -168,10 +188,10 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
168
188
span_lint_and_then(
169
189
cx,
170
190
UNNECESSARY_DEF_PATH ,
171
- expr . span,
191
+ span,
172
192
msg,
173
193
|diag| {
174
- diag. span_suggestion( expr . span, "try" , sugg, app) ;
194
+ diag. span_suggestion( span, "try" , sugg, app) ;
175
195
if with_note {
176
196
diag. help(
177
197
"if this `DefId` came from a constructor expression or pattern then the \
@@ -180,9 +200,19 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
180
200
}
181
201
} ,
182
202
) ;
203
+
204
+ self . linted_def_ids. insert( def_id) ;
183
205
}
184
206
}
185
207
}
208
+
209
+ fn check_array ( & mut self , cx : & LateContext < ' _ > , elements : & [ Expr < ' _ > ] , span : Span ) {
210
+ let Some ( path) = path_from_array ( elements) else { return } ;
211
+
212
+ if let Some ( def_id) = inherent_def_path_res ( cx, & path. iter ( ) . map ( AsRef :: as_ref) . collect :: < Vec < _ > > ( ) ) {
213
+ self . array_def_ids . insert ( ( def_id, span) ) ;
214
+ }
215
+ }
186
216
}
187
217
188
218
fn path_to_matched_type ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> Option < Vec < String > > {
@@ -209,18 +239,7 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
209
239
} ,
210
240
_ => None ,
211
241
} ,
212
- ExprKind :: Array ( exprs) => exprs
213
- . iter ( )
214
- . map ( |expr| {
215
- if let ExprKind :: Lit ( lit) = & expr. kind {
216
- if let LitKind :: Str ( sym, _) = lit. node {
217
- return Some ( ( * sym. as_str ( ) ) . to_owned ( ) ) ;
218
- }
219
- }
220
-
221
- None
222
- } )
223
- . collect ( ) ,
242
+ ExprKind :: Array ( exprs) => path_from_array ( exprs) ,
224
243
_ => None ,
225
244
}
226
245
}
@@ -258,3 +277,67 @@ fn read_mir_alloc_def_path<'tcx>(cx: &LateContext<'tcx>, alloc: &'tcx Allocation
258
277
None
259
278
}
260
279
}
280
+
281
+ fn path_from_array ( exprs : & [ Expr < ' _ > ] ) -> Option < Vec < String > > {
282
+ exprs
283
+ . iter ( )
284
+ . map ( |expr| {
285
+ if let ExprKind :: Lit ( lit) = & expr. kind {
286
+ if let LitKind :: Str ( sym, _) = lit. node {
287
+ return Some ( ( * sym. as_str ( ) ) . to_owned ( ) ) ;
288
+ }
289
+ }
290
+
291
+ None
292
+ } )
293
+ . collect ( )
294
+ }
295
+
296
+ // def_path_res will match field names before anything else, but for this we want to match
297
+ // inherent functions first.
298
+ fn inherent_def_path_res ( cx : & LateContext < ' _ > , segments : & [ & str ] ) -> Option < DefId > {
299
+ def_path_res ( cx, segments, None ) . opt_def_id ( ) . map ( |def_id| {
300
+ if cx. tcx . def_kind ( def_id) == DefKind :: Field {
301
+ let method_name = * segments. last ( ) . unwrap ( ) ;
302
+ cx. tcx
303
+ . def_key ( def_id)
304
+ . parent
305
+ . and_then ( |parent_idx| {
306
+ cx. tcx
307
+ . inherent_impls ( DefId {
308
+ index : parent_idx,
309
+ krate : def_id. krate ,
310
+ } )
311
+ . iter ( )
312
+ . find_map ( |impl_id| {
313
+ cx. tcx . associated_items ( * impl_id) . find_by_name_and_kind (
314
+ cx. tcx ,
315
+ Ident :: from_str ( method_name) ,
316
+ AssocKind :: Fn ,
317
+ * impl_id,
318
+ )
319
+ } )
320
+ } )
321
+ . map_or ( def_id, |item| item. def_id )
322
+ } else {
323
+ def_id
324
+ }
325
+ } )
326
+ }
327
+
328
+ fn get_lang_item_name ( cx : & LateContext < ' _ > , def_id : DefId ) -> Option < Symbol > {
329
+ if let Some ( lang_item) = cx. tcx . lang_items ( ) . items ( ) . iter ( ) . position ( |id| * id == Some ( def_id) ) {
330
+ let lang_items = def_path_res ( cx, & [ "rustc_hir" , "lang_items" , "LangItem" ] , Some ( Namespace :: TypeNS ) ) . def_id ( ) ;
331
+ let item_name = cx
332
+ . tcx
333
+ . adt_def ( lang_items)
334
+ . variants ( )
335
+ . iter ( )
336
+ . nth ( lang_item)
337
+ . unwrap ( )
338
+ . name ;
339
+ Some ( item_name)
340
+ } else {
341
+ None
342
+ }
343
+ }
0 commit comments