@@ -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,45 @@ 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() {} }
123+ /// bar();
124+ /// let _ = Rc::new(5);
125+ /// ```
126+ ///
127+ /// Use instead:
128+ /// ```no_run
129+ /// use foo::bar;
130+ /// use std::rc::Rc;
131+ ///
132+ /// # mod foo { pub fn bar() {} }
133+ /// bar();
134+ /// let _ = Rc::new(5);
135+ /// ```
136+ #[ clippy:: version = "1.89.0" ]
137+ pub STD_WILDCARD_IMPORTS ,
138+ pedantic,
139+ "lint `use _::*` from the standard library crates"
140+ }
141+
103142pub struct WildcardImports {
104143 warn_on_all : bool ,
105144 allowed_segments : FxHashSet < String > ,
@@ -114,7 +153,7 @@ impl WildcardImports {
114153 }
115154}
116155
117- impl_lint_pass ! ( WildcardImports => [ ENUM_GLOB_USE , WILDCARD_IMPORTS ] ) ;
156+ impl_lint_pass ! ( WildcardImports => [ ENUM_GLOB_USE , WILDCARD_IMPORTS , STD_WILDCARD_IMPORTS ] ) ;
118157
119158impl LateLintPass < ' _ > for WildcardImports {
120159 fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
@@ -129,51 +168,33 @@ impl LateLintPass<'_> for WildcardImports {
129168 return ;
130169 }
131170 if let ItemKind :: Use ( use_path, UseKind :: Glob ) = & item. kind
132- && ( self . warn_on_all || !self . check_exceptions ( cx, item, use_path. segments ) )
171+ && ( self . warn_on_all
172+ || !self . check_exceptions ( cx, item, use_path. segments )
173+ || ( !is_prelude_import ( use_path. segments ) && is_std_import ( use_path. segments ) ) )
133174 && let used_imports = cx. tcx . names_imported_by_glob_use ( item. owner_id . def_id )
134175 && !used_imports. is_empty ( ) // Already handled by `unused_imports`
135176 && !used_imports. contains ( & kw:: Underscore )
136177 {
137178 let mut applicability = Applicability :: MachineApplicable ;
138179 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- } ;
165180
166- let sugg = if braced_glob {
167- imports_string
168- } else {
169- format ! ( "{import_source_snippet}::{imports_string}" )
170- } ;
181+ let span = whole_glob_import_span ( cx, item, import_source_snippet. is_empty ( ) )
182+ . expect ( "Not a glob import statement" ) ;
183+ let sugg = sugg_glob_import ( & import_source_snippet, used_imports) ;
171184
172185 // Glob imports always have a single resolution. Enums are in the value namespace.
173186 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" )
187+ (
188+ ENUM_GLOB_USE ,
189+ String :: from ( "usage of wildcard import for enum variants" ) ,
190+ )
191+ } else if is_std_import ( use_path. segments ) {
192+ (
193+ STD_WILDCARD_IMPORTS ,
194+ format ! ( "usage of wildcard import from `{}`" , use_path. segments[ 0 ] . ident) ,
195+ )
175196 } else {
176- ( WILDCARD_IMPORTS , "usage of wildcard import" )
197+ ( WILDCARD_IMPORTS , String :: from ( "usage of wildcard import" ) )
177198 } ;
178199
179200 span_lint_and_sugg ( cx, lint, span, message, "try" , sugg, applicability) ;
@@ -201,10 +222,62 @@ fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
201222 segments. len ( ) == 1 && segments[ 0 ] . ident . name == kw:: Super
202223}
203224
225+ // Checks for the standard libraries, including `test` crate.
226+ fn is_std_import ( segments : & [ PathSegment < ' _ > ] ) -> bool {
227+ let Some ( first_segment_name) = segments. first ( ) . map ( |ps| ps. ident . name ) else {
228+ return false ;
229+ } ;
230+
231+ STDLIB_STABLE_CRATES . contains ( & first_segment_name) || first_segment_name == sym:: test
232+ }
233+
204234// Allow skipping imports containing user configured segments,
205235// i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml`
206236fn is_allowed_via_config ( segments : & [ PathSegment < ' _ > ] , allowed_segments : & FxHashSet < String > ) -> bool {
207237 // segment matching need to be exact instead of using 'contains', in case user unintentionally put
208238 // a single character in the config thus skipping most of the warnings.
209239 segments. iter ( ) . any ( |seg| allowed_segments. contains ( seg. ident . as_str ( ) ) )
210240}
241+
242+ // Returns the entire span for a given glob import statement, including the `*` symbol.
243+ fn whole_glob_import_span ( cx : & LateContext < ' _ > , item : & Item < ' _ > , braced_glob : bool ) -> Option < Span > {
244+ let ItemKind :: Use ( use_path, UseKind :: Glob ) = item. kind else {
245+ return None ;
246+ } ;
247+
248+ if braced_glob {
249+ // This is a `_::{_, *}` import
250+ // In this case `use_path.span` is empty and ends directly in front of the `*`,
251+ // so we need to extend it by one byte.
252+ Some ( use_path. span . with_hi ( use_path. span . hi ( ) + BytePos ( 1 ) ) )
253+ } else {
254+ // In this case, the `use_path.span` ends right before the `::*`, so we need to
255+ // extend it up to the `*`. Since it is hard to find the `*` in weird
256+ // formatting like `use _ :: *;`, we extend it up to, but not including the
257+ // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
258+ // can just use the end of the item span
259+ let mut span = use_path. span . with_hi ( item. span . hi ( ) ) ;
260+ if snippet ( cx, span, "" ) . ends_with ( ';' ) {
261+ span = use_path. span . with_hi ( item. span . hi ( ) - BytePos ( 1 ) ) ;
262+ }
263+ Some ( span)
264+ }
265+ }
266+
267+ // Generates a suggestion for a glob import using only the actually used items.
268+ fn sugg_glob_import ( import_source_snippet : & str , used_imports : & FxIndexSet < Symbol > ) -> String {
269+ let mut imports: Vec < _ > = used_imports. iter ( ) . map ( ToString :: to_string) . collect ( ) ;
270+ let imports_string = if imports. len ( ) == 1 {
271+ imports. pop ( ) . unwrap ( )
272+ } else if import_source_snippet. is_empty ( ) {
273+ imports. join ( ", " )
274+ } else {
275+ format ! ( "{{{}}}" , imports. join( ", " ) )
276+ } ;
277+
278+ if import_source_snippet. is_empty ( ) {
279+ imports_string
280+ } else {
281+ format ! ( "{import_source_snippet}::{imports_string}" )
282+ }
283+ }
0 commit comments