Skip to content

Commit 3b64402

Browse files
authored
Merge pull request rust-lang#2673 from estk/excessive_precision
Excessive precision
2 parents 8438583 + 9b14ad4 commit 3b64402

File tree

6 files changed

+300
-3
lines changed

6 files changed

+300
-3
lines changed
+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use rustc::hir;
2+
use rustc::lint::*;
3+
use rustc::ty::TypeVariants;
4+
use std::f32;
5+
use std::f64;
6+
use std::fmt;
7+
use syntax::ast::*;
8+
use syntax_pos::symbol::Symbol;
9+
use utils::span_lint_and_sugg;
10+
11+
/// **What it does:** Checks for float literals with a precision greater
12+
/// than that supported by the underlying type
13+
///
14+
/// **Why is this bad?** Rust will truncate the literal silently.
15+
///
16+
/// **Known problems:** None.
17+
///
18+
/// **Example:**
19+
///
20+
/// ```rust
21+
/// // Bad
22+
/// Insert a short example of code that triggers the lint
23+
/// let v: f32 = 0.123_456_789_9;
24+
/// println!("{}", v); // 0.123_456_789
25+
///
26+
/// // Good
27+
/// Insert a short example of improved code that doesn't trigger the lint
28+
/// let v: f64 = 0.123_456_789_9;
29+
/// println!("{}", v); // 0.123_456_789_9
30+
/// ```
31+
declare_clippy_lint! {
32+
pub EXCESSIVE_PRECISION,
33+
style,
34+
"excessive precision for float literal"
35+
}
36+
37+
pub struct ExcessivePrecision;
38+
39+
impl LintPass for ExcessivePrecision {
40+
fn get_lints(&self) -> LintArray {
41+
lint_array!(EXCESSIVE_PRECISION)
42+
}
43+
}
44+
45+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExcessivePrecision {
46+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) {
47+
if_chain! {
48+
let ty = cx.tables.expr_ty(expr);
49+
if let TypeVariants::TyFloat(ref fty) = ty.sty;
50+
if let hir::ExprLit(ref lit) = expr.node;
51+
if let LitKind::Float(ref sym, _) | LitKind::FloatUnsuffixed(ref sym) = lit.node;
52+
if let Some(sugg) = self.check(sym, fty);
53+
then {
54+
span_lint_and_sugg(
55+
cx,
56+
EXCESSIVE_PRECISION,
57+
expr.span,
58+
"float has excessive precision",
59+
"consider changing the type or truncating it to",
60+
sugg,
61+
);
62+
}
63+
}
64+
}
65+
}
66+
67+
impl ExcessivePrecision {
68+
// None if nothing to lint, Some(suggestion) if lint neccessary
69+
fn check(&self, sym: &Symbol, fty: &FloatTy) -> Option<String> {
70+
let max = max_digits(fty);
71+
let sym_str = sym.as_str();
72+
let formatter = FloatFormat::new(&sym_str);
73+
let digits = count_digits(&sym_str);
74+
// Try to bail out if the float is for sure fine.
75+
// If its within the 2 decimal digits of being out of precision we
76+
// check if the parsed representation is the same as the string
77+
// since we'll need the truncated string anyway.
78+
if digits > max as usize {
79+
let sr = match *fty {
80+
FloatTy::F32 => sym_str.parse::<f32>().map(|f| formatter.format(f)),
81+
FloatTy::F64 => sym_str.parse::<f64>().map(|f| formatter.format(f)),
82+
};
83+
// We know this will parse since we are in LatePass
84+
let s = sr.unwrap();
85+
86+
if sym_str == s {
87+
None
88+
} else {
89+
Some(s)
90+
}
91+
} else {
92+
None
93+
}
94+
}
95+
}
96+
97+
fn max_digits(fty: &FloatTy) -> u32 {
98+
match fty {
99+
FloatTy::F32 => f32::DIGITS,
100+
FloatTy::F64 => f64::DIGITS,
101+
}
102+
}
103+
104+
fn count_digits(s: &str) -> usize {
105+
s.chars()
106+
.filter(|c| *c != '-' || *c != '.')
107+
.take_while(|c| *c != 'e' || *c != 'E')
108+
.fold(0, |count, c| {
109+
// leading zeros
110+
if c == '0' && count == 0 {
111+
count
112+
} else {
113+
count + 1
114+
}
115+
})
116+
}
117+
118+
enum FloatFormat {
119+
LowerExp,
120+
UpperExp,
121+
Normal,
122+
}
123+
impl FloatFormat {
124+
fn new(s: &str) -> Self {
125+
s.chars()
126+
.find_map(|x| match x {
127+
'e' => Some(FloatFormat::LowerExp),
128+
'E' => Some(FloatFormat::UpperExp),
129+
_ => None,
130+
})
131+
.unwrap_or(FloatFormat::Normal)
132+
}
133+
fn format<T>(&self, f: T) -> String
134+
where T: fmt::UpperExp + fmt::LowerExp + fmt::Display {
135+
match self {
136+
FloatFormat::LowerExp => format!("{:e}", f),
137+
FloatFormat::UpperExp => format!("{:E}", f),
138+
FloatFormat::Normal => format!("{}", f),
139+
}
140+
}
141+
}

clippy_lints/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
// FIXME(mark-i-m) remove after i128 stablization merges
1414
#![allow(stable_features)]
1515
#![feature(i128, i128_type)]
16+
#![feature(iterator_find_map)]
17+
1618

1719
#[macro_use]
1820
extern crate rustc;
@@ -124,6 +126,7 @@ pub mod erasing_op;
124126
pub mod escape;
125127
pub mod eta_reduction;
126128
pub mod eval_order_dependence;
129+
pub mod excessive_precision;
127130
pub mod explicit_write;
128131
pub mod fallible_impl_from;
129132
pub mod format;
@@ -295,6 +298,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
295298
reg.register_early_lint_pass(box enum_variants::EnumVariantNames::new(conf.enum_variant_name_threshold));
296299
reg.register_late_lint_pass(box enum_glob_use::EnumGlobUse);
297300
reg.register_late_lint_pass(box enum_clike::UnportableVariant);
301+
reg.register_late_lint_pass(box excessive_precision::ExcessivePrecision);
298302
reg.register_late_lint_pass(box bit_mask::BitMask::new(conf.verbose_bit_mask_threshold));
299303
reg.register_late_lint_pass(box ptr::PointerPass);
300304
reg.register_late_lint_pass(box needless_bool::NeedlessBool);

tests/ui/approx_const.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ fn main() {
4242
let my_ln_2 = 0.6931471805599453;
4343
let no_ln_2 = 0.693;
4444

45-
let my_log10_e = 0.43429448190325182;
45+
let my_log10_e = 0.4342944819032518;
4646
let no_log10_e = 0.434;
4747

4848
let my_log2_e = 1.4426950408889634;

tests/ui/approx_const.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ error: approximate value of `f{32, 64}::consts::LN_2` found. Consider using it d
8787
error: approximate value of `f{32, 64}::consts::LOG10_E` found. Consider using it directly
8888
--> $DIR/approx_const.rs:45:22
8989
|
90-
45 | let my_log10_e = 0.43429448190325182;
91-
| ^^^^^^^^^^^^^^^^^^^
90+
45 | let my_log10_e = 0.4342944819032518;
91+
| ^^^^^^^^^^^^^^^^^^
9292

9393
error: approximate value of `f{32, 64}::consts::LOG2_E` found. Consider using it directly
9494
--> $DIR/approx_const.rs:48:21

tests/ui/excessive_precision.rs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#![feature(plugin, custom_attribute)]
2+
#![warn(excessive_precision)]
3+
#![allow(print_literal)]
4+
5+
fn main() {
6+
// TODO add prefix tests
7+
// Consts
8+
const GOOD32_SUF: f32 = 0.123_456_f32;
9+
const GOOD32: f32 = 0.123_456;
10+
const GOOD32_SM: f32 = 0.000_000_000_1;
11+
const GOOD64: f64 = 0.123_456_789_012;
12+
const GOOD64_SM: f32 = 0.000_000_000_000_000_1;
13+
14+
const BAD32_1: f32 = 0.123_456_789_f32;
15+
const BAD32_2: f32 = 0.123_456_789;
16+
const BAD32_3: f32 = 0.100_000_000_000_1;
17+
18+
const BAD64_1: f64 = 0.123_456_789_012_345_67f64;
19+
const BAD64_2: f64 = 0.123_456_789_012_345_67;
20+
const BAD64_3: f64 = 0.100_000_000_000_000_000_1;
21+
22+
// Literal
23+
println!("{}", 8.888_888_888_888_888_888_888);
24+
25+
// TODO add inferred type tests for f32
26+
// TODO add tests cases exactly on the edge
27+
// Locals
28+
let good32: f32 = 0.123_456_f32;
29+
let good32_2: f32 = 0.123_456;
30+
31+
let good64: f64 = 0.123_456_789_012f64;
32+
let good64: f64 = 0.123_456_789_012;
33+
let good64_2 = 0.123_456_789_012;
34+
35+
let bad32_1: f32 = 1.123_456_789_f32;
36+
let bad32_2: f32 = 1.123_456_789;
37+
38+
let bad64_1: f64 = 0.123_456_789_012_345_67f64;
39+
let bad64_2: f64 = 0.123_456_789_012_345_67;
40+
let bad64_3 = 0.123_456_789_012_345_67;
41+
42+
// TODO Vectors / nested vectors
43+
let vec32: Vec<f32> = vec![0.123_456_789];
44+
let vec64: Vec<f64> = vec![0.123_456_789_123_456_789];
45+
46+
// Exponential float notation
47+
let good_e32: f32 = 1e-10;
48+
let bad_e32: f32 = 1.123_456_788_888e-10;
49+
50+
let good_bige32: f32 = 1E-10;
51+
let bad_bige32: f32 = 1.123_456_788_888E-10;
52+
}

tests/ui/excessive_precision.stderr

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
error: float has excessive precision
2+
--> $DIR/excessive_precision.rs:14:26
3+
|
4+
14 | const BAD32_1: f32 = 0.123_456_789_f32;
5+
| ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345679`
6+
|
7+
= note: `-D excessive-precision` implied by `-D warnings`
8+
9+
error: float has excessive precision
10+
--> $DIR/excessive_precision.rs:15:26
11+
|
12+
15 | const BAD32_2: f32 = 0.123_456_789;
13+
| ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345679`
14+
15+
error: float has excessive precision
16+
--> $DIR/excessive_precision.rs:16:26
17+
|
18+
16 | const BAD32_3: f32 = 0.100_000_000_000_1;
19+
| ^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.1`
20+
21+
error: float has excessive precision
22+
--> $DIR/excessive_precision.rs:18:26
23+
|
24+
18 | const BAD64_1: f64 = 0.123_456_789_012_345_67f64;
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
26+
27+
error: float has excessive precision
28+
--> $DIR/excessive_precision.rs:19:26
29+
|
30+
19 | const BAD64_2: f64 = 0.123_456_789_012_345_67;
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
32+
33+
error: float has excessive precision
34+
--> $DIR/excessive_precision.rs:20:26
35+
|
36+
20 | const BAD64_3: f64 = 0.100_000_000_000_000_000_1;
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.1`
38+
39+
error: float has excessive precision
40+
--> $DIR/excessive_precision.rs:23:20
41+
|
42+
23 | println!("{}", 8.888_888_888_888_888_888_888);
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `8.88888888888889`
44+
45+
error: float has excessive precision
46+
--> $DIR/excessive_precision.rs:35:24
47+
|
48+
35 | let bad32_1: f32 = 1.123_456_789_f32;
49+
| ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.1234568`
50+
51+
error: float has excessive precision
52+
--> $DIR/excessive_precision.rs:36:24
53+
|
54+
36 | let bad32_2: f32 = 1.123_456_789;
55+
| ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.1234568`
56+
57+
error: float has excessive precision
58+
--> $DIR/excessive_precision.rs:38:24
59+
|
60+
38 | let bad64_1: f64 = 0.123_456_789_012_345_67f64;
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
62+
63+
error: float has excessive precision
64+
--> $DIR/excessive_precision.rs:39:24
65+
|
66+
39 | let bad64_2: f64 = 0.123_456_789_012_345_67;
67+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
68+
69+
error: float has excessive precision
70+
--> $DIR/excessive_precision.rs:40:19
71+
|
72+
40 | let bad64_3 = 0.123_456_789_012_345_67;
73+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
74+
75+
error: float has excessive precision
76+
--> $DIR/excessive_precision.rs:43:32
77+
|
78+
43 | let vec32: Vec<f32> = vec![0.123_456_789];
79+
| ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345679`
80+
81+
error: float has excessive precision
82+
--> $DIR/excessive_precision.rs:44:32
83+
|
84+
44 | let vec64: Vec<f64> = vec![0.123_456_789_123_456_789];
85+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678912345678`
86+
87+
error: float has excessive precision
88+
--> $DIR/excessive_precision.rs:48:24
89+
|
90+
48 | let bad_e32: f32 = 1.123_456_788_888e-10;
91+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.1234568e-10`
92+
93+
error: float has excessive precision
94+
--> $DIR/excessive_precision.rs:51:27
95+
|
96+
51 | let bad_bige32: f32 = 1.123_456_788_888E-10;
97+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.1234568E-10`
98+
99+
error: aborting due to 16 previous errors
100+

0 commit comments

Comments
 (0)