1 use clippy_config
::msrvs
::{self, Msrv}
;
2 use clippy_utils
::diagnostics
::span_lint_and_help
;
3 use rustc_ast
::ast
::{FloatTy, LitFloatType, LitKind}
;
4 use rustc_hir
::{Expr, ExprKind}
;
5 use rustc_lint
::{LateContext, LateLintPass}
;
6 use rustc_semver
::RustcVersion
;
7 use rustc_session
::impl_lint_pass
;
8 use rustc_span
::symbol
;
9 use std
::f64::consts
as f64;
11 declare_clippy_lint
! {
13 /// Checks for floating point literals that approximate
14 /// constants which are defined in
15 /// [`std::f32::consts`](https://doc.rust-lang.org/stable/std/f32/consts/#constants)
17 /// [`std::f64::consts`](https://doc.rust-lang.org/stable/std/f64/consts/#constants),
18 /// respectively, suggesting to use the predefined constant.
20 /// ### Why is this bad?
21 /// Usually, the definition in the standard library is more
22 /// precise than what people come up with. If you find that your definition is
23 /// actually more precise, please [file a Rust
24 /// issue](https://github.com/rust-lang/rust/issues).
29 /// let y = 1_f64 / x;
33 /// let x = std::f32::consts::PI;
34 /// let y = std::f64::consts::FRAC_1_PI;
36 #[clippy::version = "pre 1.29.0"]
39 "the approximate of a known float constant (in `std::fXX::consts`)"
42 // Tuples are of the form (constant, name, min_digits, msrv)
43 const KNOWN_CONSTS
: [(f64, &str, usize, Option
<RustcVersion
>); 19] = [
44 (f64::E
, "E", 4, None
),
45 (f64::FRAC_1_PI
, "FRAC_1_PI", 4, None
),
46 (f64::FRAC_1_SQRT_2
, "FRAC_1_SQRT_2", 5, None
),
47 (f64::FRAC_2_PI
, "FRAC_2_PI", 5, None
),
48 (f64::FRAC_2_SQRT_PI
, "FRAC_2_SQRT_PI", 5, None
),
49 (f64::FRAC_PI_2
, "FRAC_PI_2", 5, None
),
50 (f64::FRAC_PI_3
, "FRAC_PI_3", 5, None
),
51 (f64::FRAC_PI_4
, "FRAC_PI_4", 5, None
),
52 (f64::FRAC_PI_6
, "FRAC_PI_6", 5, None
),
53 (f64::FRAC_PI_8
, "FRAC_PI_8", 5, None
),
54 (f64::LN_2
, "LN_2", 5, None
),
55 (f64::LN_10
, "LN_10", 5, None
),
56 (f64::LOG2_10
, "LOG2_10", 5, Some(msrvs
::LOG2_10
)),
57 (f64::LOG2_E
, "LOG2_E", 5, None
),
58 (f64::LOG10_2
, "LOG10_2", 5, Some(msrvs
::LOG10_2
)),
59 (f64::LOG10_E
, "LOG10_E", 5, None
),
60 (f64::PI
, "PI", 3, None
),
61 (f64::SQRT_2
, "SQRT_2", 5, None
),
62 (f64::TAU
, "TAU", 3, Some(msrvs
::TAU
)),
65 pub struct ApproxConstant
{
71 pub fn new(msrv
: Msrv
) -> Self {
75 fn check_lit(&self, cx
: &LateContext
<'_
>, lit
: &LitKind
, e
: &Expr
<'_
>) {
77 LitKind
::Float(s
, LitFloatType
::Suffixed(fty
)) => match fty
{
78 FloatTy
::F32
=> self.check_known_consts(cx
, e
, s
, "f32"),
79 FloatTy
::F64
=> self.check_known_consts(cx
, e
, s
, "f64"),
81 LitKind
::Float(s
, LitFloatType
::Unsuffixed
) => self.check_known_consts(cx
, e
, s
, "f{32, 64}"),
86 fn check_known_consts(&self, cx
: &LateContext
<'_
>, e
: &Expr
<'_
>, s
: symbol
::Symbol
, module
: &str) {
88 if s
.parse
::<f64>().is_ok() {
89 for &(constant
, name
, min_digits
, msrv
) in &KNOWN_CONSTS
{
90 if is_approx_const(constant
, s
, min_digits
) && msrv
.map_or(true, |msrv
| self.msrv
.meets(msrv
)) {
95 &format
!("approximate value of `{module}::consts::{}` found", &name
),
97 "consider using the constant directly",
106 impl_lint_pass
!(ApproxConstant
=> [APPROX_CONSTANT
]);
108 impl<'tcx
> LateLintPass
<'tcx
> for ApproxConstant
{
109 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, e
: &'tcx Expr
<'_
>) {
110 if let ExprKind
::Lit(lit
) = &e
.kind
{
111 self.check_lit(cx
, &lit
.node
, e
);
115 extract_msrv_attr
!(LateContext
);
118 /// Returns `false` if the number of significant figures in `value` are
119 /// less than `min_digits`; otherwise, returns true if `value` is equal
120 /// to `constant`, rounded to the number of digits present in `value`.
122 fn is_approx_const(constant
: f64, value
: &str, min_digits
: usize) -> bool
{
123 if value
.len() <= min_digits
{
125 } else if constant
.to_string().starts_with(value
) {
126 // The value is a truncated constant
129 let round_const
= format
!("{constant:.*}", value
.len() - 2);