wowlab_tidy/languages/rust/rules/style/
excessive_float_precision.rs1#[cfg(test)]
2use googletest::prelude::*;
3use ra_ap_syntax::ast::{self, LiteralKind};
4use winnow::token::take_while;
5
6use crate::{AstCtx, Example, Violation, infra::parse};
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[
10 Example {
11 label: "f32 with too many digits",
12 code: "fn f() { let _x: f32 = 1.23456789012345_f32; }",
13 pass: false,
14 },
15 Example {
16 label: "f32 with ok precision",
17 code: "fn f() { let _x: f32 = 1.234567_f32; }",
18 pass: true,
19 },
20 Example {
21 label: "f64 with too many digits",
22 code: "fn f() { let _x = 3.14159265358979323846_f64; }",
23 pass: false,
24 },
25 Example {
26 label: "f64 with ok precision",
27 code: "fn f() { let _x = 3.141592653589793_f64; }",
28 pass: true,
29 },
30 Example {
31 label: "unsuffixed float is fine",
32 code: "fn f() { let _x = 3.14159265358979323846; }",
33 pass: true,
34 },
35 Example {
36 label: "excessive precision in test",
37 code: "#[cfg(test)]\nmod tests {\n fn t() { let _x = 1.23456789012345_f32; }\n}",
38 pass: true,
39 },
40];
41
42crate::ast_rule!(
43 excessive_float_precision,
44 "Flag float literals with more significant digits than the type can represent.",
45 "Extra digits beyond what f32/f64 can represent are misleading. They suggest precision that does not exist.",
46);
47
48const F32_MAX_SIGNIFICANT: usize = 8;
50const F64_MAX_SIGNIFICANT: usize = 17;
51
52fn check_excessive_float_precision(ctx: &AstCtx<'_>) -> Vec<Violation> {
53 ctx.nodes::<ast::Literal>()
54 .filter(|literal| !ctx.is_in_test(literal))
55 .filter_map(|literal| {
56 let LiteralKind::FloatNumber(number) = literal.kind() else {
57 return None;
58 };
59 let repr = number.to_string();
60 let (max_digits, type_name) = match number.suffix()? {
61 "f32" => (F32_MAX_SIGNIFICANT, "f32"),
62 "f64" => (F64_MAX_SIGNIFICANT, "f64"),
63 _ => return None,
64 };
65 let sig_digits = count_significant_digits(&repr);
66
67 (sig_digits > max_digits).then(|| {
68 ctx.violation(
69 &literal,
70 format!(
71 "float literal has {sig_digits} significant digits but {type_name} only supports ~{} — excess digits are silently lost",
72 max_digits - 1
73 ),
74 )
75 })
76 })
77 .collect()
78}
79
80fn count_significant_digits(s: &str) -> usize {
81 let mut input = s;
82 let body = parse::try_parse(&mut input, take_while(0.., |c: char| c != 'f')).unwrap_or(s);
83 let cleaned: String = body.chars().filter(|&c| c != '_').collect();
84
85 let mut inp = cleaned.as_str();
86 let _ = parse::try_parse(&mut inp, take_while(0.., |c: char| c.is_ascii_digit()));
87
88 if parse::try_parse(&mut inp, '.').is_none() {
89 return 0;
90 }
91
92 let all_digits: String = cleaned.chars().filter(char::is_ascii_digit).collect();
93 let mut digit_input = all_digits.as_str();
94 let _ = parse::try_parse(&mut digit_input, take_while(0.., '0'));
95
96 digit_input.len()
97}
98
99crate::tidy_ast_test!(check_excessive_float_precision, {
100 crate::example_tests!(EXAMPLES, check_excessive_float_precision);
101
102 #[gtest]
103 fn count_digits() -> Result<()> {
104 verify_eq!(count_significant_digits("3.14_f32"), 3)?;
105 verify_eq!(count_significant_digits("0.001_f32"), 1)?;
106 verify_eq!(count_significant_digits("1.234567890_f64"), 10)?;
107 verify_eq!(count_significant_digits("100_f32"), 0)?;
108
109 Ok(())
110 }
111});