@@ -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 , MultiSpan } ;
66use rustc_hir:: def:: { DefKind , Res } ;
77use rustc_hir:: def_id:: DefId ;
88use rustc_hir:: { Block , Body , HirId , Path , PathSegment , StabilityLevel , StableSince } ;
9- use rustc_lint:: { LateContext , LateLintPass , Lint , LintContext } ;
9+ use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
1010use rustc_session:: impl_lint_pass;
1111use rustc_span:: symbol:: kw;
12- use rustc_span:: { Span , sym} ;
12+ use rustc_span:: { Span , Symbol , sym} ;
1313
1414declare_clippy_lint ! {
1515 /// ### What it does
@@ -116,44 +116,33 @@ impl StdReexports {
116116}
117117
118118#[ derive( Debug ) ]
119- enum LintPoint {
120- Available ( Span , & ' static Lint , & ' static str , & ' static str ) ,
121- Conflict ,
119+ struct LintPoint {
120+ span : Span ,
121+ used_from : Symbol ,
122+ available_from_core : bool ,
123+ available_from_alloc : bool ,
122124}
123125
124126impl < ' tcx > LateLintPass < ' tcx > for StdReexports {
125127 fn check_path ( & mut self , cx : & LateContext < ' tcx > , path : & Path < ' tcx > , _: HirId ) {
126- if let Res :: Def ( def_kind , def_id) = path. res
128+ if let Some ( def_id) = path. res . opt_def_id ( )
127129 && let Some ( first_segment) = get_first_segment ( path)
128- && is_stable ( cx, def_id, self . msrv )
129130 && !path. span . in_external_macro ( cx. sess ( ) . source_map ( ) )
130131 && !is_from_proc_macro ( cx, & first_segment. ident )
131- && !matches ! ( def_kind, DefKind :: Macro ( _) )
132132 && let Some ( last_segment) = path. segments . last ( )
133133 && let Res :: Def ( DefKind :: Mod , crate_def_id) = first_segment. res
134134 && crate_def_id. is_crate_root ( )
135135 {
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- } ,
136+ let is_stable = is_stable ( cx, def_id, self . msrv ) ;
137+
138+ let lint_point = LintPoint {
139+ span : last_segment. ident . span ,
140+ used_from : first_segment. ident . name ,
141+ available_from_core : is_stable && is_available_from ( cx, sym:: core, path) ,
142+ available_from_alloc : is_stable && is_available_from ( cx, sym:: alloc, path) ,
150143 } ;
151144
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- ) ;
145+ self . lint_if_finish ( cx, first_segment. ident . span , lint_point) ;
157146 }
158147 }
159148
@@ -171,27 +160,77 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
171160}
172161
173162fn emit_lints ( cx : & LateContext < ' _ > , lint_points : Option < ( Span , Vec < LintPoint > ) > ) {
174- let Some ( ( krate_span, lint_points) ) = lint_points else {
163+ let Some ( ( krate_span, mut lint_points) ) = lint_points else {
175164 return ;
176165 } ;
177166
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- } ,
167+ // It's possible for multiple items to come from the same path.
168+ // For example, `std::vec` refers to a macro and a module.
169+ // In these cases, it's possible for them to have different availabilities.
170+ // `std::vec` as a macro and a module are both defined in `alloc` and unavailable in `core`.
171+ // Whereas, `std::env` is not available in `alloc`, and only the macro is available in `core`.
172+ // Since we aren't checking which of the shadowed items the user needs, we take the intersection
173+ // of these availabilities to ensure we don't provide the user a false positive.
174+ lint_points. sort_by_key ( |lint_point| lint_point. span ) ;
175+ lint_points. dedup_by ( |a, b| {
176+ if a. span == b. span {
177+ b. available_from_alloc &= a. available_from_alloc ;
178+ b. available_from_core &= a. available_from_core ;
179+ true
180+ } else {
181+ false
182+ }
183+ } ) ;
184+
185+ let mut core_span = MultiSpan :: new ( ) ;
186+ let mut alloc_span = MultiSpan :: new ( ) ;
187+ let mut all_from_std = true ;
188+ let mut all_from_alloc = true ;
189+ let mut all_core = true ;
190+ let mut all_alloc = true ;
191+
192+ for lint_point in lint_points {
193+ all_from_std &= lint_point. used_from == sym:: std;
194+ all_from_alloc &= lint_point. used_from == sym:: alloc;
195+
196+ if lint_point. available_from_core {
197+ core_span. push_primary_span ( lint_point. span ) ;
198+ } else {
199+ all_core = false ;
200+ }
201+
202+ if !lint_point. available_from_alloc {
203+ all_alloc = false ;
204+ }
205+
206+ if !lint_point. available_from_core && lint_point. available_from_alloc {
207+ alloc_span. push_primary_span ( lint_point. span ) ;
191208 }
192209 }
193210
194- if !has_conflict && let Some ( ( lint, used_mod, replace_with) ) = lint {
211+ let mut helps = Vec :: new ( ) ;
212+ let mut suggestions = Vec :: new ( ) ;
213+
214+ if all_from_std {
215+ if all_core {
216+ suggestions. push ( ( STD_INSTEAD_OF_CORE , & sym:: std, & sym:: core) ) ;
217+ helps. push ( ( STD_INSTEAD_OF_ALLOC , & sym:: std, & sym:: alloc, alloc_span) ) ;
218+ } else if all_alloc {
219+ suggestions. push ( ( STD_INSTEAD_OF_ALLOC , & sym:: std, & sym:: alloc) ) ;
220+ helps. push ( ( STD_INSTEAD_OF_CORE , & sym:: std, & sym:: core, core_span) ) ;
221+ } else {
222+ helps. push ( ( STD_INSTEAD_OF_CORE , & sym:: std, & sym:: core, core_span) ) ;
223+ helps. push ( ( STD_INSTEAD_OF_ALLOC , & sym:: std, & sym:: alloc, alloc_span) ) ;
224+ }
225+ } else if all_from_alloc {
226+ if all_core {
227+ suggestions. push ( ( ALLOC_INSTEAD_OF_CORE , & sym:: alloc, & sym:: core) ) ;
228+ } else {
229+ helps. push ( ( ALLOC_INSTEAD_OF_CORE , & sym:: alloc, & sym:: core, core_span) ) ;
230+ }
231+ }
232+
233+ for ( lint, used_mod, replace_with) in suggestions {
195234 span_lint_and_sugg (
196235 cx,
197236 lint,
@@ -201,13 +240,12 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
201240 ( * replace_with) . to_string ( ) ,
202241 Applicability :: MachineApplicable ,
203242 ) ;
204- return ;
205243 }
206244
207- for lint_point in lint_points {
208- let LintPoint :: Available ( span, lint , used_mod , replace_with ) = lint_point else {
245+ for ( lint , used_mod , replace_with , span ) in helps {
246+ if span. primary_spans ( ) . is_empty ( ) {
209247 continue ;
210- } ;
248+ }
211249 span_lint_and_help (
212250 cx,
213251 lint,
@@ -219,6 +257,75 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
219257 }
220258}
221259
260+ /// Checks if the provided `path` could have its crate replaced with `krate` and still be valid.
261+ fn is_available_from ( cx : & LateContext < ' _ > , krate : Symbol , path : & Path < ' _ > ) -> bool {
262+ // Determine DefId for krate
263+ let Some ( krate) = cx
264+ . tcx
265+ . crates ( ( ) )
266+ . iter ( )
267+ . find ( |& & krate_num| cx. tcx . is_user_visible_dep ( krate_num) && cx. tcx . crate_name ( krate_num) == krate)
268+ . map ( |& krate| {
269+ Res :: Def (
270+ DefKind :: Mod ,
271+ DefId {
272+ index : rustc_span:: def_id:: CRATE_DEF_INDEX ,
273+ krate,
274+ } ,
275+ )
276+ } )
277+ else {
278+ return false ;
279+ } ;
280+
281+ path. segments
282+ . iter ( )
283+ . enumerate ( )
284+ . skip_while ( |( _, segment) | {
285+ // Skip `::crate` and `crate` segments
286+ segment. ident . name == kw:: PathRoot || segment. res . opt_def_id ( ) . is_some_and ( DefId :: is_crate_root)
287+ } )
288+ . scan ( krate, |cursor, ( index, segment) | {
289+ // Some, but not all, items from core and reexported by alloc.
290+ // To find these cases, we must walk the alloc and core crates along the provided path
291+ // to see if the desired item is accessible.
292+ * cursor = cx
293+ . tcx
294+ . module_children ( {
295+ // TyCtxt::module_children will ICE if the provided DefId isn't "module like".
296+ cursor. module_like_def_id ( ) ?
297+ } )
298+ . iter ( )
299+ . filter ( |child| {
300+ // Since this lint is only designed to work on `std`, `core`, and `alloc` from a
301+ // user's crate, only public items are of concern.
302+ child. vis . is_public ( )
303+ } )
304+ . filter ( |child| {
305+ // We search by name to allow cases like `core::str` and `std::str`.
306+ // `std::str` is actually a reexport of `alloc::str`, but `alloc::str`
307+ // reexports the contents of `core::str`, so many instances of `std::str`
308+ // can be replaced with `core::str`, even if they are distinct items.
309+ child. ident . name == segment. ident . name
310+ } )
311+ . map ( |child| child. res . map_id ( |x| x) )
312+ . find ( |child| {
313+ if index + 1 == path. segments . len ( ) {
314+ // The last segment must resolve to the same item as the original path.
315+ * child == path. res
316+ } else {
317+ // We allow intermediate segments to resolve to different items,
318+ // but we only care about module-like items.
319+ child. module_like_def_id ( ) . is_some ( )
320+ }
321+ } ) ?;
322+
323+ Some ( * cursor)
324+ } )
325+ . last ( )
326+ . is_some_and ( |cursor| cursor == path. res )
327+ }
328+
222329/// Returns the first named segment of a [`Path`].
223330///
224331/// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`]
0 commit comments