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 `::max_value()`, `std::::MAX`, /// `std::::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}::` from the primitive type instead")); } else { diag.help("remove this import").note(format!( "then `{module_name}::` 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::::` 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", ) // `::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 { matches!( cx.tcx.get_diagnostic_name(did), Some( 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 ) ) } fn is_numeric_const(cx: &LateContext<'_>, did: DefId) -> bool { matches!( cx.tcx.get_diagnostic_name(did), Some( 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 ) ) } // 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 { matches!( cx.tcx.get_diagnostic_name(did), Some( 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 ) ) }