]>
Commit | Line | Data |
---|---|---|
c295e0f8 XL |
1 | use clippy_utils::diagnostics::span_lint_and_help; |
2 | use clippy_utils::{meets_msrv, msrvs}; | |
f20569fa XL |
3 | use rustc_ast::ast::{FloatTy, LitFloatType, LitKind}; |
4 | use rustc_hir::{Expr, ExprKind}; | |
5099ac24 | 5 | use rustc_lint::{LateContext, LateLintPass}; |
c295e0f8 XL |
6 | use rustc_semver::RustcVersion; |
7 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
f20569fa XL |
8 | use rustc_span::symbol; |
9 | use std::f64::consts as f64; | |
10 | ||
11 | declare_clippy_lint! { | |
94222f64 XL |
12 | /// ### What it does |
13 | /// Checks for floating point literals that approximate | |
f20569fa XL |
14 | /// constants which are defined in |
15 | /// [`std::f32::consts`](https://doc.rust-lang.org/stable/std/f32/consts/#constants) | |
16 | /// or | |
17 | /// [`std::f64::consts`](https://doc.rust-lang.org/stable/std/f64/consts/#constants), | |
18 | /// respectively, suggesting to use the predefined constant. | |
19 | /// | |
94222f64 XL |
20 | /// ### Why is this bad? |
21 | /// Usually, the definition in the standard library is more | |
f20569fa XL |
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). | |
25 | /// | |
94222f64 | 26 | /// ### Example |
f20569fa XL |
27 | /// ```rust |
28 | /// let x = 3.14; | |
29 | /// let y = 1_f64 / x; | |
30 | /// ``` | |
923072b8 | 31 | /// Use instead: |
f20569fa XL |
32 | /// ```rust |
33 | /// let x = std::f32::consts::PI; | |
34 | /// let y = std::f64::consts::FRAC_1_PI; | |
35 | /// ``` | |
a2a8927a | 36 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
37 | pub APPROX_CONSTANT, |
38 | correctness, | |
39 | "the approximate of a known float constant (in `std::fXX::consts`)" | |
40 | } | |
41 | ||
c295e0f8 XL |
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)), | |
f20569fa XL |
63 | ]; |
64 | ||
c295e0f8 XL |
65 | pub struct ApproxConstant { |
66 | msrv: Option<RustcVersion>, | |
67 | } | |
f20569fa | 68 | |
c295e0f8 XL |
69 | impl ApproxConstant { |
70 | #[must_use] | |
71 | pub fn new(msrv: Option<RustcVersion>) -> Self { | |
72 | Self { msrv } | |
73 | } | |
74 | ||
75 | fn check_lit(&self, cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) { | |
76 | match *lit { | |
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"), | |
80 | }, | |
81 | LitKind::Float(s, LitFloatType::Unsuffixed) => self.check_known_consts(cx, e, s, "f{32, 64}"), | |
82 | _ => (), | |
f20569fa XL |
83 | } |
84 | } | |
f20569fa | 85 | |
c295e0f8 XL |
86 | fn check_known_consts(&self, cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) { |
87 | let s = s.as_str(); | |
88 | if s.parse::<f64>().is_ok() { | |
89 | for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS { | |
923072b8 | 90 | if is_approx_const(constant, s, min_digits) && msrv.map_or(true, |msrv| meets_msrv(self.msrv, msrv)) { |
c295e0f8 XL |
91 | span_lint_and_help( |
92 | cx, | |
93 | APPROX_CONSTANT, | |
94 | e.span, | |
95 | &format!("approximate value of `{}::consts::{}` found", module, &name), | |
96 | None, | |
97 | "consider using the constant directly", | |
98 | ); | |
99 | return; | |
100 | } | |
101 | } | |
102 | } | |
f20569fa XL |
103 | } |
104 | } | |
105 | ||
c295e0f8 XL |
106 | impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); |
107 | ||
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); | |
f20569fa XL |
112 | } |
113 | } | |
c295e0f8 XL |
114 | |
115 | extract_msrv_attr!(LateContext); | |
f20569fa XL |
116 | } |
117 | ||
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`. | |
121 | #[must_use] | |
122 | fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool { | |
123 | if value.len() <= min_digits { | |
124 | false | |
125 | } else if constant.to_string().starts_with(value) { | |
126 | // The value is a truncated constant | |
127 | true | |
128 | } else { | |
129 | let round_const = format!("{:.*}", value.len() - 2, constant); | |
130 | value == round_const | |
131 | } | |
132 | } |