-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathapprox_const.rs
129 lines (121 loc) · 4.72 KB
/
approx_const.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::msrvs::{self, Msrv};
use rustc_ast::ast::{FloatTy, LitFloatType, LitKind};
use rustc_attr_parsing::RustcVersion;
use rustc_hir::{HirId, Lit};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, symbol};
use std::f64::consts as f64;
declare_clippy_lint! {
/// ### What it does
/// Checks for floating point literals that approximate
/// constants which are defined in
/// [`std::f32::consts`](https://doc.rust-lang.org/stable/std/f32/consts/#constants)
/// or
/// [`std::f64::consts`](https://doc.rust-lang.org/stable/std/f64/consts/#constants),
/// respectively, suggesting to use the predefined constant.
///
/// ### Why is this bad?
/// Usually, the definition in the standard library is more
/// precise than what people come up with. If you find that your definition is
/// actually more precise, please [file a Rust
/// issue](https://github.com/rust-lang/rust/issues).
///
/// ### Example
/// ```no_run
/// let x = 3.14;
/// let y = 1_f64 / x;
/// ```
/// Use instead:
/// ```no_run
/// let x = std::f32::consts::PI;
/// let y = std::f64::consts::FRAC_1_PI;
/// ```
#[clippy::version = "pre 1.29.0"]
pub APPROX_CONSTANT,
correctness,
"the approximate of a known float constant (in `std::fXX::consts`)"
}
// Tuples are of the form (constant, name, min_digits, msrv)
const KNOWN_CONSTS: [(f64, &str, usize, Option<RustcVersion>); 19] = [
(f64::E, "E", 4, None),
(f64::FRAC_1_PI, "FRAC_1_PI", 4, None),
(f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5, None),
(f64::FRAC_2_PI, "FRAC_2_PI", 5, None),
(f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5, None),
(f64::FRAC_PI_2, "FRAC_PI_2", 5, None),
(f64::FRAC_PI_3, "FRAC_PI_3", 5, None),
(f64::FRAC_PI_4, "FRAC_PI_4", 5, None),
(f64::FRAC_PI_6, "FRAC_PI_6", 5, None),
(f64::FRAC_PI_8, "FRAC_PI_8", 5, None),
(f64::LN_2, "LN_2", 5, None),
(f64::LN_10, "LN_10", 5, None),
(f64::LOG2_10, "LOG2_10", 5, Some(msrvs::LOG2_10)),
(f64::LOG2_E, "LOG2_E", 5, None),
(f64::LOG10_2, "LOG10_2", 5, Some(msrvs::LOG10_2)),
(f64::LOG10_E, "LOG10_E", 5, None),
(f64::PI, "PI", 3, None),
(f64::SQRT_2, "SQRT_2", 5, None),
(f64::TAU, "TAU", 3, Some(msrvs::TAU)),
];
pub struct ApproxConstant {
msrv: Msrv,
}
impl ApproxConstant {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl LateLintPass<'_> for ApproxConstant {
fn check_lit(&mut self, cx: &LateContext<'_>, _hir_id: HirId, lit: &Lit, _negated: bool) {
match lit.node {
LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty {
FloatTy::F16 => self.check_known_consts(cx, lit.span, s, "f16"),
FloatTy::F32 => self.check_known_consts(cx, lit.span, s, "f32"),
FloatTy::F64 => self.check_known_consts(cx, lit.span, s, "f64"),
FloatTy::F128 => self.check_known_consts(cx, lit.span, s, "f128"),
},
// FIXME(f16_f128): add `f16` and `f128` when these types become stable.
LitKind::Float(s, LitFloatType::Unsuffixed) => self.check_known_consts(cx, lit.span, s, "f{32, 64}"),
_ => (),
}
}
}
impl ApproxConstant {
fn check_known_consts(&self, cx: &LateContext<'_>, span: Span, s: symbol::Symbol, module: &str) {
let s = s.as_str();
if s.parse::<f64>().is_ok() {
for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS {
if is_approx_const(constant, s, min_digits) && msrv.is_none_or(|msrv| self.msrv.meets(cx, msrv)) {
span_lint_and_help(
cx,
APPROX_CONSTANT,
span,
format!("approximate value of `{module}::consts::{name}` found"),
None,
"consider using the constant directly",
);
return;
}
}
}
}
}
impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]);
/// Returns `false` if the number of significant figures in `value` are
/// less than `min_digits`; otherwise, returns true if `value` is equal
/// to `constant`, rounded to the number of digits present in `value`.
#[must_use]
fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool {
if value.len() <= min_digits {
false
} else if constant.to_string().starts_with(value) {
// The value is a truncated constant
true
} else {
let round_const = format!("{constant:.*}", value.len() - 2);
value == round_const
}
}