@@ -2,15 +2,15 @@ use clippy_config::Conf;
22use clippy_utils:: diagnostics:: span_lint_and_sugg;
33use clippy_utils:: is_in_test;
44use clippy_utils:: source:: { snippet, snippet_with_applicability} ;
5- use rustc_data_structures:: fx:: FxHashSet ;
5+ use rustc_data_structures:: fx:: { FxHashSet , FxIndexSet } ;
66use rustc_errors:: Applicability ;
77use rustc_hir:: def:: { DefKind , Res } ;
88use rustc_hir:: { Item , ItemKind , PathSegment , UseKind } ;
99use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
1010use rustc_middle:: ty;
1111use rustc_session:: impl_lint_pass;
12- use rustc_span:: BytePos ;
13- use rustc_span:: symbol :: kw ;
12+ use rustc_span:: symbol :: { STDLIB_STABLE_CRATES , Symbol , kw } ;
13+ use rustc_span:: { BytePos , Span , sym } ;
1414
1515declare_clippy_lint ! {
1616 /// ### What it does
@@ -100,6 +100,43 @@ declare_clippy_lint! {
100100 "lint `use _::*` statements"
101101}
102102
103+ declare_clippy_lint ! {
104+ /// ### What it does
105+ /// Checks for wildcard imports `use _::*` from the standard library crates.
106+ ///
107+ /// ### Why is this bad?
108+ /// Wildcard imports from the standard library crates can lead to breakages due to name
109+ /// resolution ambiguities when the standard library introduces new items with the same names
110+ /// as locally defined items.
111+ ///
112+ /// ### Exceptions
113+ /// Wildcard imports are allowed from modules whose names contain `prelude`. Many crates
114+ /// (including the standard library) provide modules named "prelude" specifically designed
115+ /// for wildcard imports.
116+ ///
117+ /// ### Example
118+ /// ```no_run
119+ /// use foo::bar;
120+ /// use std::rc::*;
121+ ///
122+ /// # mod foo { pub fn bar() { let _ = Rc::new(5); } }
123+ /// bar();
124+ /// ```
125+ ///
126+ /// Use instead:
127+ /// ```no_run
128+ /// use foo::bar;
129+ /// use std::rc::Rc;
130+ ///
131+ /// # mod foo { pub fn bar() { let _ = Rc::new(5); } }
132+ /// bar();
133+ /// ```
134+ #[ clippy:: version = "1.89.0" ]
135+ pub STD_WILDCARD_IMPORTS ,
136+ pedantic,
137+ "lint `use _::*` from the standard library crates"
138+ }
139+
103140pub struct WildcardImports {
104141 warn_on_all : bool ,
105142 allowed_segments : FxHashSet < String > ,
@@ -114,7 +151,7 @@ impl WildcardImports {
114151 }
115152}
116153
117- impl_lint_pass ! ( WildcardImports => [ ENUM_GLOB_USE , WILDCARD_IMPORTS ] ) ;
154+ impl_lint_pass ! ( WildcardImports => [ ENUM_GLOB_USE , WILDCARD_IMPORTS , STD_WILDCARD_IMPORTS ] ) ;
118155
119156impl LateLintPass < ' _ > for WildcardImports {
120157 fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
@@ -129,51 +166,33 @@ impl LateLintPass<'_> for WildcardImports {
129166 return ;
130167 }
131168 if let ItemKind :: Use ( use_path, UseKind :: Glob ) = & item. kind
132- && ( self . warn_on_all || !self . check_exceptions ( cx, item, use_path. segments ) )
169+ && ( self . warn_on_all
170+ || !self . check_exceptions ( cx, item, use_path. segments )
171+ || ( !is_prelude_import ( use_path. segments ) && is_std_import ( use_path. segments ) ) )
133172 && let used_imports = cx. tcx . names_imported_by_glob_use ( item. owner_id . def_id )
134173 && !used_imports. is_empty ( ) // Already handled by `unused_imports`
135174 && !used_imports. contains ( & kw:: Underscore )
136175 {
137176 let mut applicability = Applicability :: MachineApplicable ;
138177 let import_source_snippet = snippet_with_applicability ( cx, use_path. span , ".." , & mut applicability) ;
139- let ( span, braced_glob) = if import_source_snippet. is_empty ( ) {
140- // This is a `_::{_, *}` import
141- // In this case `use_path.span` is empty and ends directly in front of the `*`,
142- // so we need to extend it by one byte.
143- ( use_path. span . with_hi ( use_path. span . hi ( ) + BytePos ( 1 ) ) , true )
144- } else {
145- // In this case, the `use_path.span` ends right before the `::*`, so we need to
146- // extend it up to the `*`. Since it is hard to find the `*` in weird
147- // formatting like `use _ :: *;`, we extend it up to, but not including the
148- // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
149- // can just use the end of the item span
150- let mut span = use_path. span . with_hi ( item. span . hi ( ) ) ;
151- if snippet ( cx, span, "" ) . ends_with ( ';' ) {
152- span = use_path. span . with_hi ( item. span . hi ( ) - BytePos ( 1 ) ) ;
153- }
154- ( span, false )
155- } ;
156-
157- let mut imports: Vec < _ > = used_imports. iter ( ) . map ( ToString :: to_string) . collect ( ) ;
158- let imports_string = if imports. len ( ) == 1 {
159- imports. pop ( ) . unwrap ( )
160- } else if braced_glob {
161- imports. join ( ", " )
162- } else {
163- format ! ( "{{{}}}" , imports. join( ", " ) )
164- } ;
165178
166- let sugg = if braced_glob {
167- imports_string
168- } else {
169- format ! ( "{import_source_snippet}::{imports_string}" )
170- } ;
179+ let span = whole_glob_import_span ( cx, item, import_source_snippet. is_empty ( ) )
180+ . expect ( "Not a glob import statement" ) ;
181+ let sugg = sugg_glob_import ( & import_source_snippet, used_imports) ;
171182
172183 // Glob imports always have a single resolution. Enums are in the value namespace.
173184 let ( lint, message) = if let Some ( Res :: Def ( DefKind :: Enum , _) ) = use_path. res . value_ns {
174- ( ENUM_GLOB_USE , "usage of wildcard import for enum variants" )
185+ (
186+ ENUM_GLOB_USE ,
187+ String :: from ( "usage of wildcard import for enum variants" ) ,
188+ )
189+ } else if is_std_import ( use_path. segments ) {
190+ (
191+ STD_WILDCARD_IMPORTS ,
192+ format ! ( "usage of wildcard import from `{}`" , use_path. segments[ 0 ] . ident) ,
193+ )
175194 } else {
176- ( WILDCARD_IMPORTS , "usage of wildcard import" )
195+ ( WILDCARD_IMPORTS , String :: from ( "usage of wildcard import" ) )
177196 } ;
178197
179198 span_lint_and_sugg ( cx, lint, span, message, "try" , sugg, applicability) ;
@@ -201,10 +220,62 @@ fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
201220 segments. len ( ) == 1 && segments[ 0 ] . ident . name == kw:: Super
202221}
203222
223+ // Checks for the standard libraries, including `test` crate.
224+ fn is_std_import ( segments : & [ PathSegment < ' _ > ] ) -> bool {
225+ let Some ( first_segment_name) = segments. first ( ) . map ( |ps| ps. ident . name ) else {
226+ return false ;
227+ } ;
228+
229+ STDLIB_STABLE_CRATES . contains ( & first_segment_name) || first_segment_name == sym:: test
230+ }
231+
204232// Allow skipping imports containing user configured segments,
205233// i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml`
206234fn is_allowed_via_config ( segments : & [ PathSegment < ' _ > ] , allowed_segments : & FxHashSet < String > ) -> bool {
207235 // segment matching need to be exact instead of using 'contains', in case user unintentionally put
208236 // a single character in the config thus skipping most of the warnings.
209237 segments. iter ( ) . any ( |seg| allowed_segments. contains ( seg. ident . as_str ( ) ) )
210238}
239+
240+ // Returns the entire span for a given glob import statement, including the `*` symbol.
241+ fn whole_glob_import_span ( cx : & LateContext < ' _ > , item : & Item < ' _ > , braced_glob : bool ) -> Option < Span > {
242+ let ItemKind :: Use ( use_path, UseKind :: Glob ) = item. kind else {
243+ return None ;
244+ } ;
245+
246+ if braced_glob {
247+ // This is a `_::{_, *}` import
248+ // In this case `use_path.span` is empty and ends directly in front of the `*`,
249+ // so we need to extend it by one byte.
250+ Some ( use_path. span . with_hi ( use_path. span . hi ( ) + BytePos ( 1 ) ) )
251+ } else {
252+ // In this case, the `use_path.span` ends right before the `::*`, so we need to
253+ // extend it up to the `*`. Since it is hard to find the `*` in weird
254+ // formatting like `use _ :: *;`, we extend it up to, but not including the
255+ // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
256+ // can just use the end of the item span
257+ let mut span = use_path. span . with_hi ( item. span . hi ( ) ) ;
258+ if snippet ( cx, span, "" ) . ends_with ( ';' ) {
259+ span = use_path. span . with_hi ( item. span . hi ( ) - BytePos ( 1 ) ) ;
260+ }
261+ Some ( span)
262+ }
263+ }
264+
265+ // Generates a suggestion for a glob import using only the actually used items.
266+ fn sugg_glob_import ( import_source_snippet : & str , used_imports : & FxIndexSet < Symbol > ) -> String {
267+ let mut imports: Vec < _ > = used_imports. iter ( ) . map ( ToString :: to_string) . collect ( ) ;
268+ let imports_string = if imports. len ( ) == 1 {
269+ imports. pop ( ) . unwrap ( )
270+ } else if import_source_snippet. is_empty ( ) {
271+ imports. join ( ", " )
272+ } else {
273+ format ! ( "{{{}}}" , imports. join( ", " ) )
274+ } ;
275+
276+ if import_source_snippet. is_empty ( ) {
277+ imports_string
278+ } else {
279+ format ! ( "{import_source_snippet}::{imports_string}" )
280+ }
281+ }
0 commit comments