@@ -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,34 @@ 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 ) {
126128 if let Res :: Def ( def_kind, def_id) = path. res
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 )
131132 && !matches ! ( def_kind, DefKind :: Macro ( _) )
132133 && let Some ( last_segment) = path. segments . last ( )
133134 && let Res :: Def ( DefKind :: Mod , crate_def_id) = first_segment. res
134135 && crate_def_id. is_crate_root ( )
135136 {
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- } ,
137+ let is_stable = is_stable ( cx, def_id, self . msrv ) ;
138+
139+ let lint_point = LintPoint {
140+ span : last_segment. ident . span ,
141+ used_from : first_segment. ident . name ,
142+ available_from_core : is_stable && is_available_from ( cx, sym:: core, path) ,
143+ available_from_alloc : is_stable && is_available_from ( cx, sym:: alloc, path) ,
150144 } ;
151145
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- ) ;
146+ self . lint_if_finish ( cx, first_segment. ident . span , lint_point) ;
157147 }
158148 }
159149
@@ -171,27 +161,77 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
171161}
172162
173163fn emit_lints ( cx : & LateContext < ' _ > , lint_points : Option < ( Span , Vec < LintPoint > ) > ) {
174- let Some ( ( krate_span, lint_points) ) = lint_points else {
164+ let Some ( ( krate_span, mut lint_points) ) = lint_points else {
175165 return ;
176166 } ;
177167
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- } ,
168+ // It's possible for multiple items to come from the same path.
169+ // For example, `std::vec` refers to a macro and a module.
170+ // In these cases, it's possible for them to have different availabilities.
171+ // `std::vec` as a macro and a module are both defined in `alloc` and unavailable in `core`.
172+ // Whereas, `std::env` is not available in `alloc`, and only the macro is available in `core`.
173+ // Since we aren't checking which of the shadowed items the user needs, we take the intersection
174+ // of these availabilities to ensure we don't provide the user a false positive.
175+ lint_points. sort_by_key ( |lint_point| lint_point. span ) ;
176+ lint_points. dedup_by ( |a, b| {
177+ if a. span == b. span {
178+ b. available_from_alloc &= a. available_from_alloc ;
179+ b. available_from_core &= a. available_from_core ;
180+ true
181+ } else {
182+ false
183+ }
184+ } ) ;
185+
186+ let mut core_span = MultiSpan :: new ( ) ;
187+ let mut alloc_span = MultiSpan :: new ( ) ;
188+ let mut all_from_std = true ;
189+ let mut all_from_alloc = true ;
190+ let mut all_core = true ;
191+ let mut all_alloc = true ;
192+
193+ for lint_point in lint_points {
194+ all_from_std &= lint_point. used_from == sym:: std;
195+ all_from_alloc &= lint_point. used_from == sym:: alloc;
196+
197+ if lint_point. available_from_core {
198+ core_span. push_primary_span ( lint_point. span ) ;
199+ } else {
200+ all_core = false ;
201+ }
202+
203+ if !lint_point. available_from_alloc {
204+ all_alloc = false ;
205+ }
206+
207+ if !lint_point. available_from_core && lint_point. available_from_alloc {
208+ alloc_span. push_primary_span ( lint_point. span ) ;
191209 }
192210 }
193211
194- if !has_conflict && let Some ( ( lint, used_mod, replace_with) ) = lint {
212+ let mut helps = Vec :: new ( ) ;
213+ let mut suggestions = Vec :: new ( ) ;
214+
215+ if all_from_std {
216+ if all_core {
217+ suggestions. push ( ( STD_INSTEAD_OF_CORE , & sym:: std, & sym:: core) ) ;
218+ helps. push ( ( STD_INSTEAD_OF_ALLOC , & sym:: std, & sym:: alloc, alloc_span) ) ;
219+ } else if all_alloc {
220+ suggestions. push ( ( STD_INSTEAD_OF_ALLOC , & sym:: std, & sym:: alloc) ) ;
221+ helps. push ( ( STD_INSTEAD_OF_CORE , & sym:: std, & sym:: core, core_span) ) ;
222+ } else {
223+ helps. push ( ( STD_INSTEAD_OF_CORE , & sym:: std, & sym:: core, core_span) ) ;
224+ helps. push ( ( STD_INSTEAD_OF_ALLOC , & sym:: std, & sym:: alloc, alloc_span) ) ;
225+ }
226+ } else if all_from_alloc {
227+ if all_core {
228+ suggestions. push ( ( ALLOC_INSTEAD_OF_CORE , & sym:: alloc, & sym:: core) ) ;
229+ } else {
230+ helps. push ( ( ALLOC_INSTEAD_OF_CORE , & sym:: alloc, & sym:: core, core_span) ) ;
231+ }
232+ }
233+
234+ for ( lint, used_mod, replace_with) in suggestions {
195235 span_lint_and_sugg (
196236 cx,
197237 lint,
@@ -201,24 +241,91 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
201241 ( * replace_with) . to_string ( ) ,
202242 Applicability :: MachineApplicable ,
203243 ) ;
204- return ;
205244 }
206245
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- ) ;
246+ for ( lint, used_mod, replace_with, span) in helps {
247+ for & span in span. primary_spans ( ) {
248+ span_lint_and_help (
249+ cx,
250+ lint,
251+ span,
252+ format ! ( "used import from `{used_mod}` instead of `{replace_with}`" ) ,
253+ None ,
254+ format ! ( "consider importing the item from `{replace_with}`" ) ,
255+ ) ;
256+ }
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