1
- use crate :: utils:: { snippet, span_lint_and_sugg} ;
1
+ use crate :: utils:: { in_macro, snippet, span_lint_and_sugg} ;
2
+ use hir:: def:: { DefKind , Res } ;
2
3
use if_chain:: if_chain;
3
4
use rustc_ast:: ast;
5
+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
4
6
use rustc_errors:: Applicability ;
5
- use rustc_lint:: { EarlyContext , EarlyLintPass } ;
6
- use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
7
- use rustc_span:: edition:: Edition ;
7
+ use rustc_hir as hir;
8
+ use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
9
+ use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
10
+ use rustc_span:: { edition:: Edition , Span } ;
8
11
9
12
declare_clippy_lint ! {
10
13
/// **What it does:** Checks for `#[macro_use] use...`.
11
14
///
12
15
/// **Why is this bad?** Since the Rust 2018 edition you can import
13
16
/// macro's directly, this is considered idiomatic.
14
17
///
15
- /// **Known problems:** This lint does not generate an auto-applicable suggestion .
18
+ /// **Known problems:** None .
16
19
///
17
20
/// **Example:**
18
21
/// ```rust
@@ -24,29 +27,205 @@ declare_clippy_lint! {
24
27
"#[macro_use] is no longer needed"
25
28
}
26
29
27
- declare_lint_pass ! ( MacroUseImports => [ MACRO_USE_IMPORTS ] ) ;
30
+ const BRACKETS : & [ char ] = & [ '<' , '>' ] ;
28
31
29
- impl EarlyLintPass for MacroUseImports {
30
- fn check_item ( & mut self , ecx : & EarlyContext < ' _ > , item : & ast:: Item ) {
32
+ #[ derive( Clone , Debug , PartialEq , Eq ) ]
33
+ struct PathAndSpan {
34
+ path : String ,
35
+ span : Span ,
36
+ }
37
+
38
+ /// `MacroRefData` includes the name of the macro
39
+ /// and the path from `SourceMap::span_to_filename`.
40
+ #[ derive( Debug , Clone ) ]
41
+ pub struct MacroRefData {
42
+ name : String ,
43
+ path : String ,
44
+ }
45
+
46
+ impl MacroRefData {
47
+ pub fn new ( name : String , callee : Span , cx : & LateContext < ' _ , ' _ > ) -> Self {
48
+ let mut path = cx. sess ( ) . source_map ( ) . span_to_filename ( callee) . to_string ( ) ;
49
+
50
+ // std lib paths are <::std::module::file type>
51
+ // so remove brackets, space and type.
52
+ if path. contains ( '<' ) {
53
+ path = path. replace ( BRACKETS , "" ) ;
54
+ }
55
+ if path. contains ( ' ' ) {
56
+ path = path. split ( ' ' ) . next ( ) . unwrap ( ) . to_string ( ) ;
57
+ }
58
+ Self { name, path }
59
+ }
60
+ }
61
+
62
+ #[ derive( Default ) ]
63
+ #[ allow( clippy:: module_name_repetitions) ]
64
+ pub struct MacroUseImports {
65
+ /// the actual import path used and the span of the attribute above it.
66
+ imports : Vec < ( String , Span ) > ,
67
+ /// the span of the macro reference, kept to ensure only one reference is used per macro call.
68
+ collected : FxHashSet < Span > ,
69
+ mac_refs : Vec < MacroRefData > ,
70
+ }
71
+
72
+ impl_lint_pass ! ( MacroUseImports => [ MACRO_USE_IMPORTS ] ) ;
73
+
74
+ impl MacroUseImports {
75
+ fn push_unique_macro ( & mut self , cx : & LateContext < ' _ , ' _ > , span : Span ) {
76
+ let call_site = span. source_callsite ( ) ;
77
+ let name = snippet ( cx, cx. sess ( ) . source_map ( ) . span_until_char ( call_site, '!' ) , "_" ) ;
78
+ if let Some ( callee) = span. source_callee ( ) {
79
+ if !self . collected . contains ( & call_site) {
80
+ let name = if name. contains ( "::" ) {
81
+ name. split ( "::" ) . last ( ) . unwrap ( ) . to_string ( )
82
+ } else {
83
+ name. to_string ( )
84
+ } ;
85
+
86
+ self . mac_refs . push ( MacroRefData :: new ( name, callee. def_site , cx) ) ;
87
+ self . collected . insert ( call_site) ;
88
+ }
89
+ }
90
+ }
91
+
92
+ fn push_unique_macro_pat_ty ( & mut self , cx : & LateContext < ' _ , ' _ > , span : Span ) {
93
+ let call_site = span. source_callsite ( ) ;
94
+ let name = snippet ( cx, cx. sess ( ) . source_map ( ) . span_until_char ( call_site, '!' ) , "_" ) ;
95
+ if let Some ( callee) = span. source_callee ( ) {
96
+ if !self . collected . contains ( & call_site) {
97
+ self . mac_refs
98
+ . push ( MacroRefData :: new ( name. to_string ( ) , callee. def_site , cx) ) ;
99
+ self . collected . insert ( call_site) ;
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ impl < ' l , ' txc > LateLintPass < ' l , ' txc > for MacroUseImports {
106
+ fn check_item ( & mut self , cx : & LateContext < ' _ , ' _ > , item : & hir:: Item < ' _ > ) {
31
107
if_chain ! {
32
- if ecx . sess. opts. edition == Edition :: Edition2018 ;
33
- if let ast :: ItemKind :: Use ( use_tree ) = & item. kind;
108
+ if cx . sess( ) . opts. edition == Edition :: Edition2018 ;
109
+ if let hir :: ItemKind :: Use ( path , _kind ) = & item. kind;
34
110
if let Some ( mac_attr) = item
35
111
. attrs
36
112
. iter( )
37
113
. find( |attr| attr. ident( ) . map( |s| s. to_string( ) ) == Some ( "macro_use" . to_string( ) ) ) ;
114
+ if let Res :: Def ( DefKind :: Mod , id) = path. res;
38
115
then {
39
- let msg = "`macro_use` attributes are no longer needed in the Rust 2018 edition" ;
40
- let help = format!( "use {}::<macro name>" , snippet( ecx, use_tree. span, "_" ) ) ;
116
+ for kid in cx. tcx. item_children( id) . iter( ) {
117
+ if let Res :: Def ( DefKind :: Macro ( _mac_type) , mac_id) = kid. res {
118
+ let span = mac_attr. span;
119
+ let def_path = cx. tcx. def_path_str( mac_id) ;
120
+ self . imports. push( ( def_path, span) ) ;
121
+ }
122
+ }
123
+ } else {
124
+ if in_macro( item. span) {
125
+ self . push_unique_macro_pat_ty( cx, item. span) ;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ fn check_attribute ( & mut self , cx : & LateContext < ' _ , ' _ > , attr : & ast:: Attribute ) {
131
+ if in_macro ( attr. span ) {
132
+ self . push_unique_macro ( cx, attr. span ) ;
133
+ }
134
+ }
135
+ fn check_expr ( & mut self , cx : & LateContext < ' _ , ' _ > , expr : & hir:: Expr < ' _ > ) {
136
+ if in_macro ( expr. span ) {
137
+ self . push_unique_macro ( cx, expr. span ) ;
138
+ }
139
+ }
140
+ fn check_stmt ( & mut self , cx : & LateContext < ' _ , ' _ > , stmt : & hir:: Stmt < ' _ > ) {
141
+ if in_macro ( stmt. span ) {
142
+ self . push_unique_macro ( cx, stmt. span ) ;
143
+ }
144
+ }
145
+ fn check_pat ( & mut self , cx : & LateContext < ' _ , ' _ > , pat : & hir:: Pat < ' _ > ) {
146
+ if in_macro ( pat. span ) {
147
+ self . push_unique_macro_pat_ty ( cx, pat. span ) ;
148
+ }
149
+ }
150
+ fn check_ty ( & mut self , cx : & LateContext < ' _ , ' _ > , ty : & hir:: Ty < ' _ > ) {
151
+ if in_macro ( ty. span ) {
152
+ self . push_unique_macro_pat_ty ( cx, ty. span ) ;
153
+ }
154
+ }
155
+ #[ allow( clippy:: too_many_lines) ]
156
+ fn check_crate_post ( & mut self , cx : & LateContext < ' _ , ' _ > , _krate : & hir:: Crate < ' _ > ) {
157
+ let mut used = FxHashMap :: default ( ) ;
158
+ let mut check_dup = vec ! [ ] ;
159
+ for ( import, span) in & self . imports {
160
+ let found_idx = self . mac_refs . iter ( ) . position ( |mac| import. ends_with ( & mac. name ) ) ;
161
+
162
+ if let Some ( idx) = found_idx {
163
+ let _ = self . mac_refs . remove ( idx) ;
164
+ let seg = import. split ( "::" ) . collect :: < Vec < _ > > ( ) ;
165
+
166
+ match seg. as_slice ( ) {
167
+ // an empty path is impossible
168
+ // a path should always consist of 2 or more segments
169
+ [ ] | [ _] => return ,
170
+ [ root, item] => {
171
+ if !check_dup. contains ( & ( * item) . to_string ( ) ) {
172
+ used. entry ( ( ( * root) . to_string ( ) , span) )
173
+ . or_insert_with ( Vec :: new)
174
+ . push ( ( * item) . to_string ( ) ) ;
175
+ check_dup. push ( ( * item) . to_string ( ) ) ;
176
+ }
177
+ } ,
178
+ [ root, rest @ ..] => {
179
+ if rest. iter ( ) . all ( |item| !check_dup. contains ( & ( * item) . to_string ( ) ) ) {
180
+ let filtered = rest
181
+ . iter ( )
182
+ . filter_map ( |item| {
183
+ if check_dup. contains ( & ( * item) . to_string ( ) ) {
184
+ None
185
+ } else {
186
+ Some ( ( * item) . to_string ( ) )
187
+ }
188
+ } )
189
+ . collect :: < Vec < _ > > ( ) ;
190
+ used. entry ( ( ( * root) . to_string ( ) , span) )
191
+ . or_insert_with ( Vec :: new)
192
+ . push ( filtered. join ( "::" ) ) ;
193
+ check_dup. extend ( filtered) ;
194
+ } else {
195
+ let rest = rest. to_vec ( ) ;
196
+ used. entry ( ( ( * root) . to_string ( ) , span) )
197
+ . or_insert_with ( Vec :: new)
198
+ . push ( rest. join ( "::" ) ) ;
199
+ check_dup. extend ( rest. iter ( ) . map ( ToString :: to_string) ) ;
200
+ }
201
+ } ,
202
+ }
203
+ }
204
+ }
205
+
206
+ let mut suggestions = vec ! [ ] ;
207
+ for ( ( root, span) , path) in used {
208
+ if path. len ( ) == 1 {
209
+ suggestions. push ( ( span, format ! ( "{}::{}" , root, path[ 0 ] ) ) )
210
+ } else {
211
+ suggestions. push ( ( span, format ! ( "{}::{{{}}}" , root, path. join( ", " ) ) ) )
212
+ }
213
+ }
214
+
215
+ // If mac_refs is not empty we have encountered an import we could not handle
216
+ // such as `std::prelude::v1::foo` or some other macro that expands to an import.
217
+ if self . mac_refs . is_empty ( ) {
218
+ for ( span, import) in suggestions {
219
+ let help = format ! ( "use {};" , import) ;
41
220
span_lint_and_sugg (
42
- ecx ,
221
+ cx ,
43
222
MACRO_USE_IMPORTS ,
44
- mac_attr . span,
45
- msg ,
223
+ * span,
224
+ "`macro_use` attributes are no longer needed in the Rust 2018 edition" ,
46
225
"remove the attribute and import the macro directly, try" ,
47
226
help,
48
- Applicability :: HasPlaceholders ,
49
- ) ;
227
+ Applicability :: MaybeIncorrect ,
228
+ )
50
229
}
51
230
}
52
231
}
0 commit comments