Skip to content

Commit 1ad0d75

Browse files
committed
Refine stderr
1 parent d516d56 commit 1ad0d75

11 files changed

+276
-180
lines changed

book/src/lint_configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ The minimum rust version that the project supports
151151
* [`type_repetition_in_bounds`](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds)
152152
* [`tuple_array_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions)
153153
* [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold)
154+
* [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants)
154155

155156

156157
## `cognitive-complexity-threshold`
Lines changed: 100 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
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};
85
use 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};
169
use 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;
1812
use 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

2116
declare_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
}
4338
pub struct LegacyNumericConstants {
4439
msrv: Msrv,
45-
use_stmts: FxHashMap<Symbol, Vec<Span>>,
46-
glob_use_stmts: Vec<Span>,
4740
}
4841

4942
impl 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

6049
impl_lint_pass!(LegacyNumericConstants => [LEGACY_NUMERIC_CONSTANTS]);
6150

6251
impl<'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 {
108103
struct 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

115108
impl<'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
}

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10801080
needless_raw_string_hashes_allow_one,
10811081
})
10821082
});
1083-
store.register_late_pass(move |_| Box::new(legacy_integral_constants::LegacyIntegralConstants::new(msrv())));
1083+
store.register_late_pass(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(msrv())));
10841084
store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
10851085
store.register_early_pass(|| Box::new(visibility::Visibility));
10861086
store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions { msrv: msrv() }));

clippy_lints/src/utils/conf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ define_Conf! {
294294
///
295295
/// Suppress lints whenever the suggested change would cause breakage for other crates.
296296
(avoid_breaking_exported_api: bool = true),
297-
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD.
297+
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, LEGACY_NUMERIC_CONSTANTS.
298298
///
299299
/// The minimum rust version that the project supports
300300
(msrv: Option<String> = None),

clippy_utils/src/check_proc_macro.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use rustc_hir::{
2424
use rustc_lint::{LateContext, LintContext};
2525
use rustc_middle::ty::TyCtxt;
2626
use rustc_session::Session;
27-
use rustc_span::symbol::Ident;
27+
use rustc_span::symbol::{kw, Ident};
2828
use rustc_span::{Span, Symbol};
2929
use rustc_target::spec::abi::Abi;
3030

@@ -103,9 +103,13 @@ fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) {
103103
let start = if ty.is_some() {
104104
Pat::Str("<")
105105
} else {
106-
path.segments
107-
.first()
108-
.map_or(Pat::Str(""), |seg| Pat::Sym(seg.ident.name))
106+
path.segments.first().map_or(Pat::Str(""), |seg| {
107+
if seg.ident.name == kw::PathRoot {
108+
Pat::Str("::")
109+
} else {
110+
Pat::Sym(seg.ident.name)
111+
}
112+
})
109113
};
110114
let end = path.segments.last().map_or(Pat::Str(""), |seg| {
111115
if seg.args.is_some() {

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ msrv_aliases! {
3333
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN }
3434
1,46,0 { CONST_IF_MATCH }
3535
1,45,0 { STR_STRIP_PREFIX }
36-
1,43,0 { LOG2_10, LOG10_2, STD_INTEGRAL_CONSTANTS }
36+
1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS }
3737
1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
3838
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
3939
1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }

tests/ui/legacy_numeric_constants.fixed

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
//@aux-build:proc_macros.rs:proc-macro
33
#![allow(clippy::no_effect, deprecated, unused)]
44
#![warn(clippy::legacy_numeric_constants)]
5+
#![feature(lint_reasons)]
56

67
#[macro_use]
78
extern crate proc_macros;
89

910
use std::u128 as _;
1011
pub mod a {
12+
#![expect(clippy::legacy_numeric_constants)]
1113
pub use std::u128;
1214
}
1315

@@ -17,6 +19,7 @@ fn main() {
1719
usize::MIN;
1820
u32::MAX;
1921
u32::MAX;
22+
#![expect(clippy::legacy_numeric_constants)]
2023
use std::u32::MAX;
2124
u32::MAX;
2225
u32::MAX;

0 commit comments

Comments
 (0)