@@ -2,14 +2,14 @@ use clippy_config::Conf;
22use clippy_utils:: diagnostics:: { span_lint_and_help, span_lint_and_sugg} ;
33use clippy_utils:: is_from_proc_macro;
44use clippy_utils:: msrvs:: Msrv ;
5- use rustc_errors:: Applicability ;
5+ use rustc_errors:: { Applicability , DiagMessage } ;
66use rustc_hir:: def:: { DefKind , Res } ;
77use rustc_hir:: def_id:: DefId ;
88use rustc_hir:: { Block , Body , HirId , Path , PathSegment , StabilityLevel , StableSince } ;
99use rustc_lint:: { LateContext , LateLintPass , Lint , LintContext } ;
1010use rustc_session:: impl_lint_pass;
1111use rustc_span:: symbol:: kw;
12- use rustc_span:: { Span , sym} ;
12+ use rustc_span:: { Ident , Span , Symbol , sym} ;
1313
1414declare_clippy_lint ! {
1515 /// ### What it does
@@ -93,129 +93,181 @@ impl_lint_pass!(StdReexports => [
9393] ) ;
9494
9595pub struct StdReexports {
96- lint_points : Option < ( Span , Vec < LintPoint > ) > ,
96+ root : Span ,
97+ lint_points : Vec < LintPoint > ,
98+ has_conflicts : bool ,
9799 msrv : Msrv ,
98100}
99101
100102impl StdReexports {
101103 pub fn new ( conf : & ' static Conf ) -> Self {
102104 Self {
103- lint_points : Option :: default ( ) ,
105+ root : Span :: default ( ) ,
106+ lint_points : Vec :: new ( ) ,
107+ has_conflicts : false ,
104108 msrv : conf. msrv ,
105109 }
106110 }
107111
108- fn lint_if_finish ( & mut self , cx : & LateContext < ' _ > , krate : Span , lint_point : LintPoint ) {
109- match & mut self . lint_points {
110- Some ( ( prev_krate, prev_lints) ) if prev_krate. overlaps ( krate) => {
111- prev_lints. push ( lint_point) ;
112- } ,
113- _ => emit_lints ( cx, self . lint_points . replace ( ( krate, vec ! [ lint_point] ) ) ) ,
112+ /// If all the contained [`LintPoint`]s refer to the same lint _and_ there
113+ /// aren't any conflicts (e.g, multi-imports which aren't covered by this lint),
114+ /// combine them into a single lint suitable for a suggestion.
115+ fn combine_lints ( & self ) -> Option < LintPoint > {
116+ if !self . has_conflicts
117+ && let Some ( lint_point) = self
118+ . lint_points
119+ . iter ( )
120+ . copied ( )
121+ . map ( Some )
122+ . reduce ( |prev, next| prev?. combine ( next?) )
123+ . flatten ( )
124+ {
125+ Some ( LintPoint {
126+ span : self . root ,
127+ ..lint_point
128+ } )
129+ } else {
130+ None
114131 }
115132 }
116- }
117133
118- #[ derive( Debug ) ]
119- enum LintPoint {
120- Available ( Span , & ' static Lint , & ' static str , & ' static str ) ,
121- Conflict ,
134+ /// Emit any currently stored [`LintPoint`]s and reset internal storage.
135+ fn emit ( & mut self , cx : & LateContext < ' _ > ) {
136+ if let Some ( lint_point) = self . combine_lints ( ) {
137+ span_lint_and_sugg (
138+ cx,
139+ lint_point. lint ,
140+ lint_point. span ,
141+ lint_point. message ( ) ,
142+ lint_point. help ( ) ,
143+ lint_point. replace_with . to_string ( ) ,
144+ Applicability :: MachineApplicable ,
145+ ) ;
146+ } else {
147+ for lint_point in & self . lint_points {
148+ span_lint_and_help (
149+ cx,
150+ lint_point. lint ,
151+ lint_point. span ,
152+ lint_point. message ( ) ,
153+ None ,
154+ lint_point. help ( ) ,
155+ ) ;
156+ }
157+ }
158+
159+ self . root = Span :: default ( ) ;
160+ self . lint_points . clear ( ) ;
161+ self . has_conflicts = false ;
162+ }
122163}
123164
124165impl < ' tcx > LateLintPass < ' tcx > for StdReexports {
125166 fn check_path ( & mut self , cx : & LateContext < ' tcx > , path : & Path < ' tcx > , _: HirId ) {
126- if let Res :: Def ( def_kind, def_id) = path. res
127- && let Some ( first_segment) = get_first_segment ( path)
128- && is_stable ( cx, def_id, self . msrv )
129- && !path. span . in_external_macro ( cx. sess ( ) . source_map ( ) )
130- && !is_from_proc_macro ( cx, & first_segment. ident )
131- && !matches ! ( def_kind, DefKind :: Macro ( _) )
132- && let Some ( last_segment) = path. segments . last ( )
133- && let Res :: Def ( DefKind :: Mod , crate_def_id) = first_segment. res
134- && crate_def_id. is_crate_root ( )
135- {
136- let ( lint, used_mod, replace_with) = match first_segment. ident . name {
137- sym:: std => match cx. tcx . crate_name ( def_id. krate ) {
138- sym:: core => ( STD_INSTEAD_OF_CORE , "std" , "core" ) ,
139- sym:: alloc => ( STD_INSTEAD_OF_ALLOC , "std" , "alloc" ) ,
140- _ => {
141- self . lint_if_finish ( cx, first_segment. ident . span , LintPoint :: Conflict ) ;
142- return ;
143- } ,
144- } ,
145- sym:: alloc if cx. tcx . crate_name ( def_id. krate ) == sym:: core => ( ALLOC_INSTEAD_OF_CORE , "alloc" , "core" ) ,
146- _ => {
147- self . lint_if_finish ( cx, first_segment. ident . span , LintPoint :: Conflict ) ;
148- return ;
149- } ,
150- } ;
167+ // Path must resolve to a definition where the first segment is at least a module.
168+ let (
169+ Res :: Def ( def_kind, def_id) ,
170+ Some ( & PathSegment {
171+ ident : ref first_segment @ Ident { name : used, .. } ,
172+ res : Res :: Def ( DefKind :: Mod , crate_def_id) ,
173+ ..
174+ } ) ,
175+ Some ( PathSegment {
176+ ident : last_segment, ..
177+ } ) ,
178+ ) = ( path. res , get_first_segment ( path) , path. segments . last ( ) )
179+ else {
180+ return ;
181+ } ;
151182
152- self . lint_if_finish (
153- cx,
154- first_segment. ident . span ,
155- LintPoint :: Available ( last_segment. ident . span , lint, used_mod, replace_with) ,
156- ) ;
183+ // Paths within or from macros do not need to be linted.
184+ if path. span . in_external_macro ( cx. sess ( ) . source_map ( ) ) || is_from_proc_macro ( cx, first_segment) {
185+ return ;
186+ }
187+
188+ // If the first segment of the path is not a crate (std, core, alloc, etc.), then
189+ // we cannot determine if it should be replaced.
190+ if !crate_def_id. is_crate_root ( ) {
191+ return ;
192+ }
193+
194+ // Exclude paths that refer to macros.
195+ if matches ! ( def_kind, DefKind :: Macro ( _) ) {
196+ return ;
197+ }
198+
199+ // Determine if this path may be a candidate for linting.
200+ let potential_lint_point = LintPoint :: new ( last_segment. span , used, cx. tcx . crate_name ( def_id. krate ) ) ;
201+
202+ // Emit existing lint points if this lint refers to a new path.
203+ if !self . root . overlaps ( first_segment. span ) {
204+ self . emit ( cx) ;
205+ self . root = first_segment. span ;
206+ }
207+
208+ match potential_lint_point {
209+ Some ( lint_point) if is_stable ( cx, def_id, self . msrv ) => {
210+ self . lint_points . push ( lint_point) ;
211+ } ,
212+ _ => {
213+ self . has_conflicts = true ;
214+ } ,
157215 }
158216 }
159217
160218 fn check_block_post ( & mut self , cx : & LateContext < ' tcx > , _: & Block < ' tcx > ) {
161- emit_lints ( cx , self . lint_points . take ( ) ) ;
219+ self . emit ( cx ) ;
162220 }
163221
164222 fn check_body_post ( & mut self , cx : & LateContext < ' tcx > , _: & Body < ' tcx > ) {
165- emit_lints ( cx , self . lint_points . take ( ) ) ;
223+ self . emit ( cx ) ;
166224 }
167225
168226 fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
169- emit_lints ( cx , self . lint_points . take ( ) ) ;
227+ self . emit ( cx ) ;
170228 }
171229}
172230
173- fn emit_lints ( cx : & LateContext < ' _ > , lint_points : Option < ( Span , Vec < LintPoint > ) > ) {
174- let Some ( ( krate_span, lint_points) ) = lint_points else {
175- return ;
176- } ;
231+ #[ derive( Debug , Clone , Copy ) ]
232+ struct LintPoint {
233+ span : Span ,
234+ lint : & ' static Lint ,
235+ used : Symbol ,
236+ replace_with : Symbol ,
237+ }
177238
178- let mut lint: Option < ( & ' static Lint , & ' static str , & ' static str ) > = None ;
179- let mut has_conflict = false ;
180- for lint_point in & lint_points {
181- match lint_point {
182- LintPoint :: Available ( _, l, used_mod, replace_with)
183- if lint. is_none_or ( |( prev_l, ..) | l. name == prev_l. name ) =>
184- {
185- lint = Some ( ( l, used_mod, replace_with) ) ;
186- } ,
187- _ => {
188- has_conflict = true ;
189- break ;
190- } ,
191- }
192- }
239+ impl LintPoint {
240+ fn new ( span : Span , used : Symbol , replace_with : Symbol ) -> Option < Self > {
241+ let lint = match ( used, replace_with) {
242+ ( sym:: std, sym:: core) => Some ( STD_INSTEAD_OF_CORE ) ,
243+ ( sym:: std, sym:: alloc) => Some ( STD_INSTEAD_OF_ALLOC ) ,
244+ ( sym:: alloc, sym:: core) => Some ( ALLOC_INSTEAD_OF_CORE ) ,
245+ _ => None ,
246+ } ?;
193247
194- if !has_conflict && let Some ( ( lint, used_mod, replace_with) ) = lint {
195- span_lint_and_sugg (
196- cx,
248+ Some ( Self {
249+ span,
197250 lint,
198- krate_span,
199- format ! ( "used import from `{used_mod}` instead of `{replace_with}`" ) ,
200- format ! ( "consider importing the item from `{replace_with}`" ) ,
201- ( * replace_with) . to_string ( ) ,
202- Applicability :: MachineApplicable ,
203- ) ;
204- return ;
251+ used,
252+ replace_with,
253+ } )
205254 }
206255
207- for lint_point in lint_points {
208- let LintPoint :: Available ( span, lint, used_mod, replace_with) = lint_point else {
209- continue ;
210- } ;
211- span_lint_and_help (
212- cx,
213- lint,
214- span,
215- format ! ( "used import from `{used_mod}` instead of `{replace_with}`" ) ,
216- None ,
217- format ! ( "consider importing the item from `{replace_with}`" ) ,
218- ) ;
256+ /// Combine two [`LintPoint`]s if they cover the same lint.
257+ /// Does not check if they refer to a meaningfully similar span.
258+ fn combine ( self , other : Self ) -> Option < Self > {
259+ core:: ptr:: eq ( self . lint , other. lint ) . then_some ( Self {
260+ span : Span :: default ( ) ,
261+ ..self
262+ } )
263+ }
264+
265+ fn message ( & self ) -> impl Into < DiagMessage > {
266+ format ! ( "used import from `{}` instead of `{}`" , self . used, self . replace_with)
267+ }
268+
269+ fn help ( & self ) -> impl Into < DiagMessage > {
270+ format ! ( "consider importing the item from `{}`" , self . replace_with)
219271 }
220272}
221273
@@ -236,29 +288,30 @@ fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>>
236288/// or now stable moves that were once unstable.
237289///
238290/// Does not catch individually moved items
239- fn is_stable ( cx : & LateContext < ' _ > , mut def_id : DefId , msrv : Msrv ) -> bool {
240- loop {
241- if let Some ( stability) = cx. tcx . lookup_stability ( def_id) {
242- match stability. level {
243- // Workaround for items from `core::intrinsics` with a stable export in a different module.
244- // Not that we ignore the `since` field as we are already accessing the item in question.
245- StabilityLevel :: Stable {
246- allowed_through_unstable_modules : Some ( _) ,
247- ..
248- } => return true ,
249- StabilityLevel :: Stable { since, .. } => match since {
250- StableSince :: Version ( v) if !msrv. meets ( cx, v) => return false ,
251- StableSince :: Current if msrv. current ( cx) . is_none ( ) => return false ,
252- StableSince :: Err ( _) => return false ,
253- StableSince :: Version ( _) | StableSince :: Current => { } ,
254- } ,
255- StabilityLevel :: Unstable { .. } => return false ,
256- }
257- }
291+ fn is_stable ( cx : & LateContext < ' _ > , def_id : DefId , msrv : Msrv ) -> bool {
292+ iter_parents ( cx, def_id)
293+ . find_map ( |def_id| match cx. tcx . lookup_stability ( def_id) ?. level {
294+ StabilityLevel :: Unstable { .. } => Some ( false ) ,
295+ StabilityLevel :: Stable {
296+ since,
297+ allowed_through_unstable_modules,
298+ } => {
299+ let stable = match since {
300+ StableSince :: Version ( v) => msrv. meets ( cx, v) ,
301+ StableSince :: Current => msrv. current ( cx) . is_none ( ) ,
302+ StableSince :: Err ( _) => false ,
303+ } ;
258304
259- match cx. tcx . opt_parent ( def_id) {
260- Some ( parent) => def_id = parent,
261- None => return true ,
262- }
263- }
305+ ( !stable || allowed_through_unstable_modules. is_some ( ) ) . then_some ( stable)
306+ } ,
307+ } )
308+ . unwrap_or ( true )
309+ }
310+
311+ /// Iterate (inclusively) over all ancestors of the provided [`DefId`].
312+ fn iter_parents ( cx : & LateContext < ' _ > , mut def_id : DefId ) -> impl Iterator < Item = DefId > {
313+ core:: iter:: once ( def_id) . chain ( core:: iter:: from_fn ( move || {
314+ def_id = cx. tcx . opt_parent ( def_id) ?;
315+ Some ( def_id)
316+ } ) )
264317}
0 commit comments