1- use clippy_utils:: {
2- diagnostics:: span_lint_and_then,
3- get_parent_expr, is_from_proc_macro, last_path_segment,
4- msrvs:: { self , Msrv } ,
5- } ;
6- use itertools:: Itertools ;
7- use rustc_data_structures:: fx:: FxHashMap ;
1+ use clippy_utils:: diagnostics:: { span_lint_and_then, span_lint_hir_and_then} ;
2+ use clippy_utils:: msrvs:: { Msrv , NUMERIC_ASSOCIATED_CONSTANTS } ;
3+ use clippy_utils:: source:: snippet_opt;
4+ use clippy_utils:: { get_parent_expr, is_from_proc_macro, last_path_segment, std_or_core} ;
85use rustc_errors:: Applicability ;
9- use rustc_hir:: {
10- def:: Res ,
11- def_id:: DefId ,
12- intravisit:: { walk_expr, Visitor } ,
13- Item , UseKind ,
14- } ;
15- use rustc_hir:: { Expr , ExprKind , ItemKind , PrimTy , QPath , TyKind } ;
6+ use rustc_hir:: def:: { DefKind , Res } ;
7+ use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
8+ use rustc_hir:: { Expr , ExprKind , Item , ItemKind , PrimTy , QPath , TyKind , UseKind } ;
169use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
17- use rustc_middle:: { hir:: nested_filter:: OnlyBodies , lint:: in_external_macro} ;
10+ use rustc_middle:: hir:: nested_filter:: OnlyBodies ;
11+ use rustc_middle:: lint:: in_external_macro;
1812use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
19- use rustc_span:: { sym, Span , Symbol } ;
13+ use rustc_span:: symbol:: kw;
14+ use rustc_span:: { sym, Symbol } ;
2015
2116declare_clippy_lint ! {
2217 /// ### What it does
@@ -25,7 +20,7 @@ declare_clippy_lint! {
2520 ///
2621 /// ### Why is this bad?
2722 /// All of these have been superceded by the associated constants on their respective types,
28- /// such as `i128::MAX`. These legacy constants may be deprecated in a future version of rust.
23+ /// such as `i128::MAX`. These legacy items may be deprecated in a future version of rust.
2924 ///
3025 /// ### Example
3126 /// ```rust
@@ -42,64 +37,64 @@ declare_clippy_lint! {
4237}
4338pub struct LegacyNumericConstants {
4439 msrv : Msrv ,
45- use_stmts : FxHashMap < Symbol , Vec < Span > > ,
46- glob_use_stmts : Vec < Span > ,
4740}
4841
4942impl LegacyNumericConstants {
5043 #[ must_use]
5144 pub fn new ( msrv : Msrv ) -> Self {
52- Self {
53- msrv,
54- use_stmts : FxHashMap :: default ( ) ,
55- glob_use_stmts : vec ! [ ] ,
56- }
45+ Self { msrv }
5746 }
5847}
5948
6049impl_lint_pass ! ( LegacyNumericConstants => [ LEGACY_NUMERIC_CONSTANTS ] ) ;
6150
6251impl < ' tcx > LateLintPass < ' tcx > for LegacyNumericConstants {
6352 fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' tcx > ) {
64- let Self {
65- msrv : _,
66- use_stmts,
67- glob_use_stmts,
68- } = self ;
69-
70- if !item. span . is_dummy ( ) && let ItemKind :: Use ( path, kind) = item. kind {
71- match kind {
72- UseKind :: Single => {
73- for res in & path. res {
74- if let Some ( def_id) = res. opt_def_id ( )
75- && let Some ( module_name) = is_path_in_integral_module ( cx, def_id)
76- && let _ = use_stmts. insert ( module_name, vec ! [ ] )
77- && let Some ( use_stmts) = use_stmts. get_mut ( & module_name)
78- {
79- use_stmts. push ( item. span ) ;
80- }
53+ let Self { msrv } = self ;
54+
55+ if msrv. meets ( NUMERIC_ASSOCIATED_CONSTANTS )
56+ && !in_external_macro ( cx. sess ( ) , item. span )
57+ && let ItemKind :: Use ( path, kind @ ( UseKind :: Single | UseKind :: Glob ) ) = item. kind
58+ // These modules are "TBD" deprecated, and the contents are too, so lint on the `use`
59+ // statement directly
60+ && let def_path = cx. get_def_path ( path. res [ 0 ] . def_id ( ) )
61+ && is_path_in_numeric_module ( & def_path, true )
62+ {
63+ let plurality = matches ! (
64+ kind,
65+ UseKind :: Glob | UseKind :: Single if matches!( path. res[ 0 ] , Res :: Def ( DefKind :: Mod , _) ) ,
66+ ) ;
67+
68+ span_lint_and_then (
69+ cx,
70+ LEGACY_NUMERIC_CONSTANTS ,
71+ path. span ,
72+ if plurality {
73+ "importing legacy numeric constants"
74+ } else {
75+ "importing a legacy numeric constant"
76+ } ,
77+ |diag| {
78+ if item. ident . name != kw:: Underscore {
79+ let msg = if plurality && let [ .., module_name] = & * def_path {
80+ format ! ( "use the associated constants on `{module_name}` instead at their usage" )
81+ } else if let [ .., module_name, name] = & * def_path {
82+ format ! ( "use the associated constant `{module_name}::{name}` instead at its usage" )
83+ } else {
84+ return ;
85+ } ;
86+
87+ diag. help ( msg) ;
8188 }
8289 } ,
83- UseKind :: Glob => glob_use_stmts. push ( item. span ) ,
84- UseKind :: ListStem => { } ,
85- }
90+ ) ;
8691 }
8792 }
8893
8994 fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
90- let Self {
91- msrv,
92- use_stmts,
93- glob_use_stmts,
94- } = self ;
95-
96- let mut v = V {
97- cx,
98- msrv,
99- use_stmts,
100- glob_use_stmts,
101- } ;
102- cx. tcx . hir ( ) . visit_all_item_likes_in_crate ( & mut v) ;
95+ let Self { msrv } = self ;
96+
97+ cx. tcx . hir ( ) . visit_all_item_likes_in_crate ( & mut V { cx, msrv } ) ;
10398 }
10499
105100 extract_msrv_attr ! ( LateContext ) ;
@@ -108,8 +103,6 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
108103struct V < ' a , ' tcx > {
109104 cx : & ' a LateContext < ' tcx > ,
110105 msrv : & ' a Msrv ,
111- use_stmts : & ' a FxHashMap < Symbol , Vec < Span > > ,
112- glob_use_stmts : & ' a Vec < Span > ,
113106}
114107
115108impl < ' tcx > Visitor < ' tcx > for V < ' _ , ' tcx > {
@@ -121,35 +114,36 @@ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
121114
122115 fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) {
123116 walk_expr ( self , expr) ;
124- let Self {
125- cx,
126- msrv,
127- use_stmts,
128- glob_use_stmts,
129- } = * self ;
130-
131- if !msrv. meets ( msrvs:: STD_INTEGRAL_CONSTANTS ) || in_external_macro ( cx. sess ( ) , expr. span ) {
117+ let Self { cx, msrv } = * self ;
118+
119+ if !msrv. meets ( NUMERIC_ASSOCIATED_CONSTANTS ) || in_external_macro ( cx. sess ( ) , expr. span ) {
132120 return ;
133121 }
134122 let ExprKind :: Path ( qpath) = expr. kind else {
135123 return ;
136124 } ;
137125
138126 // `std::<integer>::<CONST>` check
139- let ( span, sugg, is_method , use_stmts ) = if let QPath :: Resolved ( _ , path) = qpath
127+ let ( span, sugg, msg ) = if let QPath :: Resolved ( None , path) = qpath
140128 && let Some ( def_id) = path. res . opt_def_id ( )
141- && let Some ( name) = path. segments . iter ( ) . last ( ) . map ( |segment| segment. ident . name )
142- && let Some ( module_name) = is_path_in_integral_module ( cx, def_id)
129+ && let path = cx. get_def_path ( def_id)
130+ && is_path_in_numeric_module ( & path, false )
131+ && let [ .., module_name, name] = & * path
132+ && let Some ( snippet) = snippet_opt ( cx, expr. span )
133+ && let is_float_module = ( * module_name == sym:: f32 || * module_name == sym:: f64)
134+ // Skip linting if this usage looks identical to the associated constant, since this
135+ // would only require removing a `use` import. We don't ignore ones from `f32` or `f64`, however.
136+ && let identical = snippet == format ! ( "{module_name}::{name}" )
137+ && ( !identical || is_float_module)
143138 {
144139 (
145140 expr. span ,
146- format ! ( "{module_name}::{name}" ) ,
147- false ,
148- if path. segments . get ( 0 ) . is_some_and ( |segment| segment. ident . name == module_name) {
149- use_stmts. get ( & module_name)
150- } else {
141+ if identical {
151142 None
152- }
143+ } else {
144+ Some ( format ! ( "{module_name}::{name}" ) )
145+ } ,
146+ "usage of a legacy numeric constant" ,
153147 )
154148 // `<integer>::xxx_value` check
155149 } else if let QPath :: TypeRelative ( ty, _) = qpath
@@ -164,43 +158,40 @@ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
164158 {
165159 (
166160 qpath. last_segment_span ( ) . with_hi ( par_expr. span . hi ( ) ) ,
167- name[ ..=2 ] . to_ascii_uppercase ( ) ,
168- true ,
169- None ,
161+ Some ( name[ ..=2 ] . to_ascii_uppercase ( ) ) ,
162+ "usage of a legacy numeric method" ,
170163 )
171164 } else {
172165 return ;
173166 } ;
174167
175168 if !is_from_proc_macro ( cx, expr) {
176- let msg = if is_method {
177- "usage of a legacy numeric method"
178- } else {
179- "usage of a legacy numeric constant"
180- } ;
181-
182- span_lint_and_then ( cx, LEGACY_NUMERIC_CONSTANTS , span, msg, |diag| {
183- let app = if use_stmts. is_none ( ) {
184- Applicability :: MachineApplicable
185- } else {
186- Applicability :: MaybeIncorrect
187- } ;
188- diag. span_suggestion ( span, "use the associated constant instead" , sugg, app) ;
189- if let Some ( use_stmts) = use_stmts {
190- diag. span_note (
191- use_stmts. iter ( ) . chain ( glob_use_stmts) . copied ( ) . collect_vec ( ) ,
192- "you may need to remove one of the following `use` statements" ,
169+ span_lint_hir_and_then ( cx, LEGACY_NUMERIC_CONSTANTS , expr. hir_id , span, msg, |diag| {
170+ if let Some ( sugg) = sugg {
171+ diag. span_suggestion (
172+ span,
173+ "use the associated constant instead" ,
174+ sugg,
175+ Applicability :: MaybeIncorrect ,
193176 ) ;
177+ } else if let Some ( std_or_core) = std_or_core ( cx)
178+ && let QPath :: Resolved ( None , path) = qpath
179+ {
180+ diag. help ( format ! (
181+ "remove the import that brings `{std_or_core}::{}` into scope" ,
182+ // Must be `<module>::<CONST>` if `needs_import_removed` is true yet is
183+ // being linted
184+ path. segments[ 0 ] . ident. name,
185+ ) ) ;
194186 }
195187 } ) ;
196188 }
197189 }
198190}
199191
200- fn is_path_in_integral_module ( cx : & LateContext < ' _ > , def_id : DefId ) -> Option < Symbol > {
201- let path = cx. get_def_path ( def_id) ;
192+ fn is_path_in_numeric_module ( path : & [ Symbol ] , ignore_float_modules : bool ) -> bool {
202193 if let [
203- sym:: core | sym :: std ,
194+ sym:: core,
204195 module @ ( sym:: u8
205196 | sym:: i8
206197 | sym:: u16
@@ -216,12 +207,17 @@ fn is_path_in_integral_module(cx: &LateContext<'_>, def_id: DefId) -> Option<Sym
216207 | sym:: f32
217208 | sym:: f64) ,
218209 ..,
219- ] = & * cx. get_def_path ( def_id)
220- // So `use` statements like `std::f32` also work
221- && path. len ( ) <= 3
210+ ] = path
211+ && !path. get ( 2 ) . is_some_and ( |& s| s == sym ! ( consts) )
222212 {
223- return Some ( * module) ;
213+ // If `ignore_float_modules` is `true`, return `None` for `_::f32` or `_::f64`, but not
214+ // _::f64::MAX` or similar.
215+ if ignore_float_modules && ( * module == sym:: f32 || * module == sym:: f64) && path. get ( 2 ) . is_none ( ) {
216+ return false ;
217+ }
218+
219+ return true ;
224220 }
225221
226- None
222+ false
227223}
0 commit comments