1
- use crate :: utils:: { snippet, span_lint_and_sugg, in_macro} ;
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;
4
- use rustc_data_structures:: fx:: FxHashSet ;
5
+ use rustc_data_structures:: fx:: FxHashMap ;
5
6
use rustc_errors:: Applicability ;
6
- use rustc_lint:: { EarlyContext , EarlyLintPass } ;
7
- use rustc_session:: { impl_lint_pass, declare_tool_lint} ;
7
+ use rustc_hir as hir;
8
+ use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
9
+ use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
8
10
use rustc_span:: { edition:: Edition , Span } ;
9
11
10
12
declare_clippy_lint ! {
@@ -20,82 +22,226 @@ declare_clippy_lint! {
20
22
/// #[macro_use]
21
23
/// use lazy_static;
22
24
/// ```
23
- pub MACRO_USE_IMPORT ,
25
+ pub MACRO_USE_IMPORTS ,
24
26
pedantic,
25
27
"#[macro_use] is no longer needed"
26
28
}
27
29
28
- #[ derive( Default ) ]
29
- pub struct MacroUseImport {
30
- collected : FxHashSet < Span > ,
30
+ const BRACKETS : & [ char ] = & [ '<' , '>' ] ;
31
+
32
+ /// MacroRefData includes the name of the macro
33
+ /// and the path from `SourceMap::span_to_filename`.
34
+ #[ derive( Debug , Clone ) ]
35
+ pub struct MacroRefData {
36
+ name : String ,
37
+ path : String ,
31
38
}
32
39
33
- impl_lint_pass ! ( MacroUseImport => [ MACRO_USE_IMPORT ] ) ;
40
+ impl MacroRefData {
41
+ pub fn new ( name : String , span : Span , ecx : & LateContext < ' _ , ' _ > ) -> Self {
42
+ let mut path = ecx. sess ( ) . source_map ( ) . span_to_filename ( span) . to_string ( ) ;
34
43
35
- impl EarlyLintPass for MacroUseImport {
44
+ // std lib paths are <::std::module::file type>
45
+ // so remove brackets and space
46
+ if path. contains ( '<' ) {
47
+ path = path. replace ( BRACKETS , "" ) ;
48
+ }
49
+ if path. contains ( ' ' ) {
50
+ path = path. split ( ' ' ) . next ( ) . unwrap ( ) . to_string ( ) ;
51
+ }
52
+ Self {
53
+ name : name. to_string ( ) ,
54
+ path,
55
+ }
56
+ }
57
+ }
36
58
37
- fn check_item ( & mut self , ecx : & EarlyContext < ' _ > , item : & ast:: Item ) {
59
+ #[ derive( Default ) ]
60
+ pub struct MacroUseImports {
61
+ /// the actual import path used and the span of the attribute above it.
62
+ imports : Vec < ( String , Span ) > ,
63
+ /// the span of the macro reference and the `MacroRefData`
64
+ /// for the use of the macro.
65
+ /// TODO make this FxHashSet<Span> to guard against inserting already found macros
66
+ collected : FxHashMap < Span , MacroRefData > ,
67
+ mac_refs : Vec < ( Span , MacroRefData ) > ,
68
+ }
69
+
70
+ impl_lint_pass ! ( MacroUseImports => [ MACRO_USE_IMPORTS ] ) ;
71
+
72
+ impl < ' l , ' txc > LateLintPass < ' l , ' txc > for MacroUseImports {
73
+ fn check_item ( & mut self , lcx : & LateContext < ' _ , ' _ > , item : & hir:: Item < ' _ > ) {
38
74
if_chain ! {
39
- if ecx . sess. opts. edition == Edition :: Edition2018 ;
40
- if let ast :: ItemKind :: Use ( use_tree ) = & item. kind;
75
+ if lcx . sess( ) . opts. edition == Edition :: Edition2018 ;
76
+ if let hir :: ItemKind :: Use ( path , _kind ) = & item. kind;
41
77
if let Some ( mac_attr) = item
42
78
. attrs
43
79
. iter( )
44
80
. find( |attr| attr. ident( ) . map( |s| s. to_string( ) ) == Some ( "macro_use" . to_string( ) ) ) ;
81
+ if let Res :: Def ( DefKind :: Mod , id) = path. res;
45
82
then {
46
- let import_path = snippet( ecx, use_tree. span, "_" ) ;
47
- let mac_names = find_used_macros( ecx, & import_path) ;
48
- let msg = "`macro_use` attributes are no longer needed in the Rust 2018 edition" ;
49
- let help = format!( "use {}::<macro name>" , import_path) ;
50
- span_lint_and_sugg(
51
- ecx,
52
- MACRO_USE_IMPORT ,
53
- mac_attr. span,
54
- msg,
55
- // "remove the attribute and import the macro directly, try",
56
- "" ,
57
- help,
58
- Applicability :: HasPlaceholders ,
59
- ) ;
83
+ // println!("{:#?}", lcx.tcx.def_path_str(id));
84
+ for kid in lcx. tcx. item_children( id) . iter( ) {
85
+ // println!("{:#?}", kid);
86
+ if let Res :: Def ( DefKind :: Macro ( _mac_type) , mac_id) = kid. res {
87
+ let span = mac_attr. span. clone( ) ;
88
+
89
+ // println!("{:#?}", lcx.tcx.def_path_str(mac_id));
90
+
91
+ self . imports. push( ( lcx. tcx. def_path_str( mac_id) , span) ) ;
92
+ }
93
+ }
94
+ } else {
95
+ if in_macro( item. span) {
96
+ let call_site = item. span. source_callsite( ) ;
97
+ let name = snippet( lcx, lcx. sess( ) . source_map( ) . span_until_char( call_site, '!' ) , "_" ) ;
98
+ if let Some ( callee) = item. span. source_callee( ) {
99
+ if !self . collected. contains_key( & call_site) {
100
+ let mac = MacroRefData :: new( name. to_string( ) , callee. def_site, lcx) ;
101
+ self . mac_refs. push( ( call_site, mac. clone( ) ) ) ;
102
+ self . collected. insert( call_site, mac) ;
103
+ }
104
+ }
105
+ }
60
106
}
61
107
}
62
108
}
109
+ fn check_attribute ( & mut self , lcx : & LateContext < ' _ , ' _ > , attr : & ast:: Attribute ) {
110
+ if in_macro ( attr. span ) {
111
+ let call_site = attr. span . source_callsite ( ) ;
112
+ let name = snippet ( lcx, lcx. sess ( ) . source_map ( ) . span_until_char ( call_site, '!' ) , "_" ) ;
113
+ if let Some ( callee) = attr. span . source_callee ( ) {
114
+ if !self . collected . contains_key ( & call_site) {
115
+ println ! ( "{:?}\n {:#?}" , call_site, attr) ;
116
+
117
+ let name = if name. contains ( "::" ) {
118
+ name. split ( "::" ) . last ( ) . unwrap ( ) . to_string ( )
119
+ } else {
120
+ name. to_string ( )
121
+ } ;
63
122
64
- fn check_expr ( & mut self , ecx : & EarlyContext < ' _ > , expr : & ast:: Expr ) {
123
+ let mac = MacroRefData :: new ( name, callee. def_site , lcx) ;
124
+ self . mac_refs . push ( ( call_site, mac. clone ( ) ) ) ;
125
+ self . collected . insert ( call_site, mac) ;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ fn check_expr ( & mut self , lcx : & LateContext < ' _ , ' _ > , expr : & hir:: Expr < ' _ > ) {
65
131
if in_macro ( expr. span ) {
66
- let name = snippet ( ecx, ecx. sess . source_map ( ) . span_until_char ( expr. span . source_callsite ( ) , '!' ) , "_" ) ;
132
+ let call_site = expr. span . source_callsite ( ) ;
133
+ let name = snippet ( lcx, lcx. sess ( ) . source_map ( ) . span_until_char ( call_site, '!' ) , "_" ) ;
67
134
if let Some ( callee) = expr. span . source_callee ( ) {
68
- if self . collected . insert ( callee. def_site ) {
69
- println ! ( "EXPR {:#?}" , name) ;
135
+ if !self . collected . contains_key ( & call_site) {
136
+ let name = if name. contains ( "::" ) {
137
+ name. split ( "::" ) . last ( ) . unwrap ( ) . to_string ( )
138
+ } else {
139
+ name. to_string ( )
140
+ } ;
141
+
142
+ let mac = MacroRefData :: new ( name, callee. def_site , lcx) ;
143
+ self . mac_refs . push ( ( call_site, mac. clone ( ) ) ) ;
144
+ self . collected . insert ( call_site, mac) ;
70
145
}
71
146
}
72
147
}
73
148
}
74
- fn check_stmt ( & mut self , ecx : & EarlyContext < ' _ > , stmt : & ast :: Stmt ) {
149
+ fn check_stmt ( & mut self , lcx : & LateContext < ' _ , ' _ > , stmt : & hir :: Stmt < ' _ > ) {
75
150
if in_macro ( stmt. span ) {
76
- let name = snippet ( ecx, ecx. sess . source_map ( ) . span_until_char ( stmt. span . source_callsite ( ) , '!' ) , "_" ) ;
151
+ let call_site = stmt. span . source_callsite ( ) ;
152
+ let name = snippet ( lcx, lcx. sess ( ) . source_map ( ) . span_until_char ( call_site, '!' ) , "_" ) ;
77
153
if let Some ( callee) = stmt. span . source_callee ( ) {
78
- println ! ( "EXPR {:#?}" , name) ;
154
+ if !self . collected . contains_key ( & call_site) {
155
+ let name = if name. contains ( "::" ) {
156
+ name. split ( "::" ) . last ( ) . unwrap ( ) . to_string ( )
157
+ } else {
158
+ name. to_string ( )
159
+ } ;
160
+
161
+ let mac = MacroRefData :: new ( name, callee. def_site , lcx) ;
162
+ self . mac_refs . push ( ( call_site, mac. clone ( ) ) ) ;
163
+ self . collected . insert ( call_site, mac) ;
164
+ }
79
165
}
80
166
}
81
167
}
82
- fn check_pat ( & mut self , ecx : & EarlyContext < ' _ > , pat : & ast :: Pat ) {
168
+ fn check_pat ( & mut self , lcx : & LateContext < ' _ , ' _ > , pat : & hir :: Pat < ' _ > ) {
83
169
if in_macro ( pat. span ) {
84
- let name = snippet ( ecx, ecx. sess . source_map ( ) . span_until_char ( pat. span . source_callsite ( ) , '!' ) , "_" ) ;
170
+ let call_site = pat. span . source_callsite ( ) ;
171
+ let name = snippet ( lcx, lcx. sess ( ) . source_map ( ) . span_until_char ( call_site, '!' ) , "_" ) ;
85
172
if let Some ( callee) = pat. span . source_callee ( ) {
86
- println ! ( "EXPR {:#?}" , name) ;
173
+ if !self . collected . contains_key ( & call_site) {
174
+ let mac = MacroRefData :: new ( name. to_string ( ) , callee. def_site , lcx) ;
175
+ self . mac_refs . push ( ( call_site, mac. clone ( ) ) ) ;
176
+ self . collected . insert ( call_site, mac) ;
177
+ }
87
178
}
88
179
}
89
180
}
90
- }
181
+ fn check_ty ( & mut self , lcx : & LateContext < ' _ , ' _ > , ty : & hir:: Ty < ' _ > ) {
182
+ if in_macro ( ty. span ) {
183
+ let call_site = ty. span . source_callsite ( ) ;
184
+ let name = snippet ( lcx, lcx. sess ( ) . source_map ( ) . span_until_char ( call_site, '!' ) , "_" ) ;
185
+ if let Some ( callee) = ty. span . source_callee ( ) {
186
+ if !self . collected . contains_key ( & call_site) {
187
+ let mac = MacroRefData :: new ( name. to_string ( ) , callee. def_site , lcx) ;
188
+ self . mac_refs . push ( ( call_site, mac. clone ( ) ) ) ;
189
+ self . collected . insert ( call_site, mac) ;
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ fn check_crate_post ( & mut self , lcx : & LateContext < ' _ , ' _ > , _krate : & hir:: Crate < ' _ > ) {
196
+ for ( import, span) in self . imports . iter ( ) {
197
+ let matched = self
198
+ . mac_refs
199
+ . iter ( )
200
+ . find ( |( _span, mac) | import. ends_with ( & mac. name ) )
201
+ . is_some ( ) ;
91
202
92
- fn find_used_macros ( ecx : & EarlyContext < ' _ > , path : & str ) {
93
- for it in ecx. krate . module . items . iter ( ) {
94
- if in_macro ( it. span ) {
95
- // println!("{:#?}", it)
203
+ if matched {
204
+ self . mac_refs . retain ( |( _span, mac) | !import. ends_with ( & mac. name ) ) ;
205
+ let msg = "`macro_use` attributes are no longer needed in the Rust 2018 edition" ;
206
+ let help = format ! ( "use {}" , import) ;
207
+ span_lint_and_sugg (
208
+ lcx,
209
+ MACRO_USE_IMPORTS ,
210
+ * span,
211
+ msg,
212
+ "remove the attribute and import the macro directly, try" ,
213
+ help,
214
+ Applicability :: HasPlaceholders ,
215
+ )
216
+ }
217
+ }
218
+ if !self . mac_refs . is_empty ( ) {
219
+ // TODO if not empty we found one we could not make a suggestion for
220
+ // such as std::prelude::v1 or something else I haven't thought of.
221
+ // println!("{:#?}", self.mac_refs);
96
222
}
97
223
}
98
- for x in ecx. sess . imported_macro_spans . borrow ( ) . iter ( ) {
99
- // println!("{:?}", x);
224
+ }
225
+
226
+ const PRELUDE : & [ & str ] = & [
227
+ "marker" , "ops" , "convert" , "iter" , "option" , "result" , "borrow" , "boxed" , "string" , "vec" , "macros" ,
228
+ ] ;
229
+
230
+ /// This is somewhat of a fallback for imports from `std::prelude` because they
231
+ /// are not recognized by `LateLintPass::check_item` `lcx.tcx.item_children(id)`
232
+ fn make_path ( mac : & MacroRefData , use_path : & str ) -> String {
233
+ let segs = mac. path . split ( "::" ) . filter ( |s| * s != "" ) . collect :: < Vec < _ > > ( ) ;
234
+
235
+ if segs. starts_with ( & [ "std" ] ) && PRELUDE . iter ( ) . any ( |m| segs. contains ( m) ) {
236
+ return format ! (
237
+ "std::prelude::{} is imported by default, remove `use` statement" ,
238
+ mac. name
239
+ ) ;
240
+ }
241
+
242
+ if use_path. split ( "::" ) . count ( ) == 1 {
243
+ return format ! ( "{}::{}" , use_path, mac. name) ;
100
244
}
245
+
246
+ mac. path . clone ( )
101
247
}
0 commit comments