@@ -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
@@ -175,23 +165,55 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
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+ let mut core_span = MultiSpan :: new ( ) ;
169+ let mut alloc_span = MultiSpan :: new ( ) ;
170+ let mut all_from_std = true ;
171+ let mut all_from_alloc = true ;
172+ let mut all_core = true ;
173+ let mut all_alloc = true ;
174+
175+ for lint_point in lint_points {
176+ all_from_std &= lint_point. used_from == sym:: std;
177+ all_from_alloc &= lint_point. used_from == sym:: alloc;
178+
179+ if lint_point. available_from_core {
180+ core_span. push_primary_span ( lint_point. span ) ;
181+ } else {
182+ all_core = false ;
183+ }
184+
185+ if !lint_point. available_from_alloc {
186+ all_alloc = false ;
187+ }
188+
189+ if !lint_point. available_from_core && lint_point. available_from_alloc {
190+ alloc_span. push_primary_span ( lint_point. span ) ;
191191 }
192192 }
193193
194- if !has_conflict && let Some ( ( lint, used_mod, replace_with) ) = lint {
194+ let mut helps = Vec :: new ( ) ;
195+ let mut suggestions = Vec :: new ( ) ;
196+
197+ if all_from_std {
198+ if all_core {
199+ suggestions. push ( ( STD_INSTEAD_OF_CORE , & sym:: std, & sym:: core) ) ;
200+ helps. push ( ( STD_INSTEAD_OF_ALLOC , & sym:: std, & sym:: alloc, alloc_span) ) ;
201+ } else if all_alloc {
202+ suggestions. push ( ( STD_INSTEAD_OF_ALLOC , & sym:: std, & sym:: alloc) ) ;
203+ helps. push ( ( STD_INSTEAD_OF_CORE , & sym:: std, & sym:: core, core_span) ) ;
204+ } else {
205+ helps. push ( ( STD_INSTEAD_OF_CORE , & sym:: std, & sym:: core, core_span) ) ;
206+ helps. push ( ( STD_INSTEAD_OF_ALLOC , & sym:: std, & sym:: alloc, alloc_span) ) ;
207+ }
208+ } else if all_from_alloc {
209+ if all_core {
210+ suggestions. push ( ( ALLOC_INSTEAD_OF_CORE , & sym:: alloc, & sym:: core) ) ;
211+ } else {
212+ helps. push ( ( ALLOC_INSTEAD_OF_CORE , & sym:: alloc, & sym:: core, core_span) ) ;
213+ }
214+ }
215+
216+ for ( lint, used_mod, replace_with) in suggestions {
195217 span_lint_and_sugg (
196218 cx,
197219 lint,
@@ -201,13 +223,12 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
201223 ( * replace_with) . to_string ( ) ,
202224 Applicability :: MachineApplicable ,
203225 ) ;
204- return ;
205226 }
206227
207- for lint_point in lint_points {
208- let LintPoint :: Available ( span, lint , used_mod , replace_with ) = lint_point else {
228+ for ( lint , used_mod , replace_with , span ) in helps {
229+ if span. primary_spans ( ) . is_empty ( ) {
209230 continue ;
210- } ;
231+ }
211232 span_lint_and_help (
212233 cx,
213234 lint,
@@ -219,6 +240,67 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
219240 }
220241}
221242
243+ /// Checks if the provided `path` could have its crate replaced with `krate` and still be valid.
244+ fn is_available_from ( cx : & LateContext < ' _ > , krate : Symbol , path : & Path < ' _ > ) -> bool {
245+ // We're attempting to find this DefId with an alternate path.
246+ // If this DefId isn't available then there's no way to do that.
247+ let Some ( target) = path. res . opt_def_id ( ) else {
248+ return false ;
249+ } ;
250+
251+ // Determine DefId for krate
252+ let Some ( krate) = cx
253+ . tcx
254+ . crates ( ( ) )
255+ . iter ( )
256+ . filter ( |& & krate| cx. tcx . is_user_visible_dep ( krate) )
257+ . find ( |& & krate_num| cx. tcx . crate_name ( krate_num) == krate)
258+ . map ( |& krate| DefId {
259+ index : rustc_span:: def_id:: CRATE_DEF_INDEX ,
260+ krate,
261+ } )
262+ else {
263+ return false ;
264+ } ;
265+
266+ path. segments
267+ . iter ( )
268+ . skip_while ( |segment| {
269+ // Skip `::crate` and `crate` segments
270+ segment. ident . name == kw:: PathRoot || segment. res . opt_def_id ( ) . is_some_and ( DefId :: is_crate_root)
271+ } )
272+ . scan ( krate, |cursor, segment| {
273+ // Some, but not all, items from core and reexported by alloc.
274+ // To find these cases, we must walk the alloc and core crates along the provided path
275+ // to see if the desired item is accessible.
276+ * cursor = cx
277+ . tcx
278+ . module_children ( * cursor)
279+ . iter ( )
280+ . filter ( |child| child. vis . is_public ( ) )
281+ . filter ( |child| {
282+ // We search by name to allow cases like `core::str` and `std::str`.
283+ // `std::str` is actually a reexport of `alloc::str`, but `alloc::str`
284+ // reexports the contents of `core::str`, so many instances of `std::str`
285+ // can be replaced with `core::str`, even if they are distinct items.
286+ child. ident . name == segment. ident . name
287+ } )
288+ . find_map ( |child| {
289+ // TyCtxt::module_children will ICE if the provided DefId isn't "module like".
290+ // The initial value will always be a crate so no need to check there.
291+ // Since what we're searching for may not be "module like", we must
292+ // exempt a non-"module like" DefId if and only if it is our target.
293+ let is_module_like = child. res . module_like_def_id ( ) . is_some ( ) ;
294+ let def_id = child. res . opt_def_id ( ) ?;
295+ ( def_id == target || is_module_like) . then_some ( def_id)
296+ } ) ?;
297+
298+ Some ( * cursor)
299+ } )
300+ . last ( )
301+ . is_some_and ( |cursor| cursor == target)
302+ }
303+
222304/// Returns the first named segment of a [`Path`].
223305///
224306/// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`]
0 commit comments