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 } ;
23use if_chain:: if_chain;
34use rustc_ast:: ast;
5+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
46use 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 } ;
811
912declare_clippy_lint ! {
1013 /// **What it does:** Checks for `#[macro_use] use...`.
1114 ///
1215 /// **Why is this bad?** Since the Rust 2018 edition you can import
1316 /// macro's directly, this is considered idiomatic.
1417 ///
15- /// **Known problems:** This lint does not generate an auto-applicable suggestion .
18+ /// **Known problems:** None .
1619 ///
1720 /// **Example:**
1821 /// ```rust
@@ -24,29 +27,205 @@ declare_clippy_lint! {
2427 "#[macro_use] is no longer needed"
2528}
2629
27- declare_lint_pass ! ( MacroUseImports => [ MACRO_USE_IMPORTS ] ) ;
30+ const BRACKETS : & [ char ] = & [ '<' , '>' ] ;
2831
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 < ' _ > ) {
31107 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;
34110 if let Some ( mac_attr) = item
35111 . attrs
36112 . iter( )
37113 . find( |attr| attr. ident( ) . map( |s| s. to_string( ) ) == Some ( "macro_use" . to_string( ) ) ) ;
114+ if let Res :: Def ( DefKind :: Mod , id) = path. res;
38115 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) ;
41220 span_lint_and_sugg (
42- ecx ,
221+ cx ,
43222 MACRO_USE_IMPORTS ,
44- mac_attr . span,
45- msg ,
223+ * span,
224+ "`macro_use` attributes are no longer needed in the Rust 2018 edition" ,
46225 "remove the attribute and import the macro directly, try" ,
47226 help,
48- Applicability :: HasPlaceholders ,
49- ) ;
227+ Applicability :: MaybeIncorrect ,
228+ )
50229 }
51230 }
52231 }
0 commit comments