new lint legacy_numeric_constants

This commit is contained in:
Catherine 2023-06-20 16:34:24 -05:00 committed by Peter Jaszkowiak
parent 9d6f41691e
commit 0c392d918a
22 changed files with 921 additions and 37 deletions

View file

@ -41,7 +41,7 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
})
},
BinOpKind::Rem | BinOpKind::BitAnd => get_constant_bits(cx, right)
.unwrap_or(u64::max_value())
.unwrap_or(u64::MAX)
.min(apply_reductions(cx, nbits, left, signed)),
BinOpKind::Shr => apply_reductions(cx, nbits, left, signed)
.saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).unwrap_or_default())),
@ -56,7 +56,7 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
} else {
None
};
apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::max_value()))
apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::MAX))
},
ExprKind::MethodCall(method, _, [lo, hi], _) => {
if method.ident.as_str() == "clamp" {

View file

@ -254,6 +254,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::large_include_file::LARGE_INCLUDE_FILE_INFO,
crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
crate::large_stack_frames::LARGE_STACK_FRAMES_INFO,
crate::legacy_numeric_constants::LEGACY_NUMERIC_CONSTANTS_INFO,
crate::len_zero::COMPARISON_TO_EMPTY_INFO,
crate::len_zero::LEN_WITHOUT_IS_EMPTY_INFO,
crate::len_zero::LEN_ZERO_INFO,

View file

@ -0,0 +1,290 @@
use clippy_config::msrvs::{Msrv, NUMERIC_ASSOCIATED_CONSTANTS};
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::{get_parent_expr, is_from_proc_macro};
use hir::def_id::DefId;
use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir as hir;
use rustc_hir::{ExprKind, Item, ItemKind, QPath, UseKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::impl_lint_pass;
use rustc_span::symbol::kw;
use rustc_span::{sym, Symbol};
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `<integer>::max_value()`, `std::<integer>::MAX`,
/// `std::<float>::EPSILON`, etc.
///
/// ### Why is this bad?
/// All of these have been superceded by the associated constants on their respective types,
/// such as `i128::MAX`. These legacy items may be deprecated in a future version of rust.
///
/// ### Example
/// ```rust
/// let eps = std::f32::EPSILON;
/// ```
/// Use instead:
/// ```rust
/// let eps = f32::EPSILON;
/// ```
#[clippy::version = "1.72.0"]
pub LEGACY_NUMERIC_CONSTANTS,
style,
"checks for usage of legacy std numeric constants and methods"
}
pub struct LegacyNumericConstants {
msrv: Msrv,
}
impl LegacyNumericConstants {
#[must_use]
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
impl_lint_pass!(LegacyNumericConstants => [LEGACY_NUMERIC_CONSTANTS]);
impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
let Self { msrv } = self;
if !msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS) || in_external_macro(cx.sess(), item.span) {
return;
}
// Integer modules are "TBD" deprecated, and the contents are too,
// so lint on the `use` statement directly.
if let ItemKind::Use(path, kind @ (UseKind::Single | UseKind::Glob)) = item.kind
&& let Some(def_id) = path.res[0].opt_def_id()
{
let module = if is_integer_module(cx, def_id) {
true
} else if is_numeric_const(cx, def_id) {
false
} else {
return;
};
span_lint_and_then(
cx,
LEGACY_NUMERIC_CONSTANTS,
path.span,
if module {
"importing legacy numeric constants"
} else {
"importing a legacy numeric constant"
},
|diag| {
if item.ident.name == kw::Underscore {
diag.help("remove this import");
return;
}
let def_path = cx.get_def_path(def_id);
if module && let [.., module_name] = &*def_path {
if kind == UseKind::Glob {
diag.help(format!("remove this import and use associated constants `{module_name}::<CONST>` from the primitive type instead"));
} else {
diag.help("remove this import").note(format!(
"then `{module_name}::<CONST>` will resolve to the respective associated constant"
));
}
} else if let [.., module_name, name] = &*def_path {
diag.help(
format!("remove this import and use the associated constant `{module_name}::{name}` from the primitive type instead")
);
}
},
);
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
let Self { msrv } = self;
if !msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS) || in_external_macro(cx.sess(), expr.span) {
return;
}
let ExprKind::Path(qpath) = expr.kind else {
return;
};
// `std::<integer>::<CONST>` check
let (span, sugg, msg) = if let QPath::Resolved(None, path) = qpath
&& let Some(def_id) = path.res.opt_def_id()
&& is_numeric_const(cx, def_id)
&& let def_path = cx.get_def_path(def_id)
&& let [.., mod_name, name] = &*def_path
// Skip linting if this usage looks identical to the associated constant,
// since this would only require removing a `use` import (which is already linted).
&& !is_numeric_const_path_canonical(path, [*mod_name, *name])
{
(
expr.span,
format!("{mod_name}::{name}"),
"usage of a legacy numeric constant",
)
// `<integer>::xxx_value` check
} else if let QPath::TypeRelative(_, last_segment) = qpath
&& let Some(def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id()
&& is_integer_method(cx, def_id)
&& let Some(par_expr) = get_parent_expr(cx, expr)
&& let ExprKind::Call(_, _) = par_expr.kind
{
let name = last_segment.ident.name.as_str();
(
last_segment.ident.span.with_hi(par_expr.span.hi()),
name[..=2].to_ascii_uppercase(),
"usage of a legacy numeric method",
)
} else {
return;
};
if is_from_proc_macro(cx, expr) {
return;
}
span_lint_hir_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.hir_id, span, msg, |diag| {
diag.span_suggestion_with_style(
span,
"use the associated constant instead",
sugg,
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);
});
}
extract_msrv_attr!(LateContext);
}
fn is_integer_module(cx: &LateContext<'_>, did: DefId) -> bool {
[
sym::isize_legacy_mod,
sym::i128_legacy_mod,
sym::i64_legacy_mod,
sym::i32_legacy_mod,
sym::i16_legacy_mod,
sym::i8_legacy_mod,
sym::usize_legacy_mod,
sym::u128_legacy_mod,
sym::u64_legacy_mod,
sym::u32_legacy_mod,
sym::u16_legacy_mod,
sym::u8_legacy_mod,
]
.iter()
.any(|&name| cx.tcx.is_diagnostic_item(name, did))
}
fn is_numeric_const(cx: &LateContext<'_>, did: DefId) -> bool {
[
sym::isize_legacy_const_max,
sym::isize_legacy_const_min,
sym::i128_legacy_const_max,
sym::i128_legacy_const_min,
sym::i16_legacy_const_max,
sym::i16_legacy_const_min,
sym::i32_legacy_const_max,
sym::i32_legacy_const_min,
sym::i64_legacy_const_max,
sym::i64_legacy_const_min,
sym::i8_legacy_const_max,
sym::i8_legacy_const_min,
sym::usize_legacy_const_max,
sym::usize_legacy_const_min,
sym::u128_legacy_const_max,
sym::u128_legacy_const_min,
sym::u16_legacy_const_max,
sym::u16_legacy_const_min,
sym::u32_legacy_const_max,
sym::u32_legacy_const_min,
sym::u64_legacy_const_max,
sym::u64_legacy_const_min,
sym::u8_legacy_const_max,
sym::u8_legacy_const_min,
sym::f32_legacy_const_digits,
sym::f32_legacy_const_epsilon,
sym::f32_legacy_const_infinity,
sym::f32_legacy_const_mantissa_dig,
sym::f32_legacy_const_max,
sym::f32_legacy_const_max_10_exp,
sym::f32_legacy_const_max_exp,
sym::f32_legacy_const_min,
sym::f32_legacy_const_min_10_exp,
sym::f32_legacy_const_min_exp,
sym::f32_legacy_const_min_positive,
sym::f32_legacy_const_nan,
sym::f32_legacy_const_neg_infinity,
sym::f32_legacy_const_radix,
sym::f64_legacy_const_digits,
sym::f64_legacy_const_epsilon,
sym::f64_legacy_const_infinity,
sym::f64_legacy_const_mantissa_dig,
sym::f64_legacy_const_max,
sym::f64_legacy_const_max_10_exp,
sym::f64_legacy_const_max_exp,
sym::f64_legacy_const_min,
sym::f64_legacy_const_min_10_exp,
sym::f64_legacy_const_min_exp,
sym::f64_legacy_const_min_positive,
sym::f64_legacy_const_nan,
sym::f64_legacy_const_neg_infinity,
sym::f64_legacy_const_radix,
]
.iter()
.any(|&name| cx.tcx.is_diagnostic_item(name, did))
}
// Whether path expression looks like `i32::MAX`
fn is_numeric_const_path_canonical(expr_path: &hir::Path<'_>, [mod_name, name]: [Symbol; 2]) -> bool {
let [
hir::PathSegment {
ident: one, args: None, ..
},
hir::PathSegment {
ident: two, args: None, ..
},
] = expr_path.segments
else {
return false;
};
one.name == mod_name && two.name == name
}
fn is_integer_method(cx: &LateContext<'_>, did: DefId) -> bool {
[
sym::isize_legacy_fn_max_value,
sym::isize_legacy_fn_min_value,
sym::i128_legacy_fn_max_value,
sym::i128_legacy_fn_min_value,
sym::i16_legacy_fn_max_value,
sym::i16_legacy_fn_min_value,
sym::i32_legacy_fn_max_value,
sym::i32_legacy_fn_min_value,
sym::i64_legacy_fn_max_value,
sym::i64_legacy_fn_min_value,
sym::i8_legacy_fn_max_value,
sym::i8_legacy_fn_min_value,
sym::usize_legacy_fn_max_value,
sym::usize_legacy_fn_min_value,
sym::u128_legacy_fn_max_value,
sym::u128_legacy_fn_min_value,
sym::u16_legacy_fn_max_value,
sym::u16_legacy_fn_min_value,
sym::u32_legacy_fn_max_value,
sym::u32_legacy_fn_min_value,
sym::u64_legacy_fn_max_value,
sym::u64_legacy_fn_min_value,
sym::u8_legacy_fn_max_value,
sym::u8_legacy_fn_min_value,
]
.iter()
.any(|&name| cx.tcx.is_diagnostic_item(name, did))
}

View file

@ -186,6 +186,7 @@ mod large_futures;
mod large_include_file;
mod large_stack_arrays;
mod large_stack_frames;
mod legacy_numeric_constants;
mod len_zero;
mod let_if_seq;
mod let_underscore;
@ -1080,6 +1081,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
allow_one_hash_in_raw_strings,
})
});
store.register_late_pass(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(msrv())));
store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
store.register_early_pass(|| Box::new(visibility::Visibility));
store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions { msrv: msrv() }));