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 } ;
23use if_chain:: if_chain;
34use rustc_ast:: ast;
4- use rustc_data_structures:: fx:: FxHashSet ;
5+ use rustc_data_structures:: fx:: FxHashMap ;
56use 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} ;
810use rustc_span:: { edition:: Edition , Span } ;
911
1012declare_clippy_lint ! {
@@ -20,82 +22,226 @@ declare_clippy_lint! {
2022 /// #[macro_use]
2123 /// use lazy_static;
2224 /// ```
23- pub MACRO_USE_IMPORT ,
25+ pub MACRO_USE_IMPORTS ,
2426 pedantic,
2527 "#[macro_use] is no longer needed"
2628}
2729
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 ,
3138}
3239
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 ( ) ;
3443
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+ }
3658
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 < ' _ > ) {
3874 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;
4177 if let Some ( mac_attr) = item
4278 . attrs
4379 . iter( )
4480 . find( |attr| attr. ident( ) . map( |s| s. to_string( ) ) == Some ( "macro_use" . to_string( ) ) ) ;
81+ if let Res :: Def ( DefKind :: Mod , id) = path. res;
4582 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+ }
60106 }
61107 }
62108 }
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+ } ;
63122
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 < ' _ > ) {
65131 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, '!' ) , "_" ) ;
67134 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) ;
70145 }
71146 }
72147 }
73148 }
74- fn check_stmt ( & mut self , ecx : & EarlyContext < ' _ > , stmt : & ast :: Stmt ) {
149+ fn check_stmt ( & mut self , lcx : & LateContext < ' _ , ' _ > , stmt : & hir :: Stmt < ' _ > ) {
75150 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, '!' ) , "_" ) ;
77153 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+ }
79165 }
80166 }
81167 }
82- fn check_pat ( & mut self , ecx : & EarlyContext < ' _ > , pat : & ast :: Pat ) {
168+ fn check_pat ( & mut self , lcx : & LateContext < ' _ , ' _ > , pat : & hir :: Pat < ' _ > ) {
83169 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, '!' ) , "_" ) ;
85172 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+ }
87178 }
88179 }
89180 }
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 ( ) ;
91202
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);
96222 }
97223 }
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) ;
100244 }
245+
246+ mac. path . clone ( )
101247}
0 commit comments