Skip to content

Commit f2a486b

Browse files
Add new lint to detect lossy whole-number float literals
1 parent f3edbaf commit f2a486b

File tree

7 files changed

+204
-2
lines changed

7 files changed

+204
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,7 @@ Released 2018-09-13
12071207
[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value
12081208
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
12091209
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
1210+
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
12101211
[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
12111212
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
12121213
[`manual_mul_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_mul_add

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
77

8-
[There are 355 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
8+
[There are 356 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
99

1010
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1111

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ pub mod let_underscore;
234234
pub mod lifetimes;
235235
pub mod literal_representation;
236236
pub mod loops;
237+
pub mod lossy_float_literal;
237238
pub mod main_recursion;
238239
pub mod map_clone;
239240
pub mod map_unit_fn;
@@ -597,6 +598,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
597598
&loops::WHILE_IMMUTABLE_CONDITION,
598599
&loops::WHILE_LET_LOOP,
599600
&loops::WHILE_LET_ON_ITERATOR,
601+
&lossy_float_literal::LOSSY_FLOAT_LITERAL,
600602
&main_recursion::MAIN_RECURSION,
601603
&map_clone::MAP_CLONE,
602604
&map_unit_fn::OPTION_MAP_UNIT_FN,
@@ -1003,6 +1005,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10031005
store.register_early_pass(|| box utils::internal_lints::ProduceIce);
10041006
store.register_late_pass(|| box let_underscore::LetUnderscore);
10051007
store.register_late_pass(|| box atomic_ordering::AtomicOrdering);
1008+
store.register_late_pass(|| box lossy_float_literal::LossyFloatLiteral);
10061009
store.register_early_pass(|| box single_component_path_imports::SingleComponentPathImports);
10071010
let max_fn_params_bools = conf.max_fn_params_bools;
10081011
let max_struct_bools = conf.max_struct_bools;
@@ -1022,6 +1025,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10221025
LintId::of(&integer_division::INTEGER_DIVISION),
10231026
LintId::of(&let_underscore::LET_UNDERSCORE_MUST_USE),
10241027
LintId::of(&literal_representation::DECIMAL_LITERAL_REPRESENTATION),
1028+
LintId::of(&lossy_float_literal::LOSSY_FLOAT_LITERAL),
10251029
LintId::of(&matches::WILDCARD_ENUM_MATCH_ARM),
10261030
LintId::of(&mem_forget::MEM_FORGET),
10271031
LintId::of(&methods::CLONE_ON_REF_PTR),
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use crate::utils::span_lint_and_sugg;
2+
use crate::utils::sugg::format_numeric_literal;
3+
use if_chain::if_chain;
4+
use rustc::ty;
5+
use rustc_errors::Applicability;
6+
use rustc_hir as hir;
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_session::{declare_lint_pass, declare_tool_lint};
9+
use std::{f32, f64};
10+
use syntax::ast::*;
11+
12+
declare_clippy_lint! {
13+
/// **What it does:** Checks for whole number float literals that
14+
/// cannot be represented as the underlying type without loss.
15+
///
16+
/// **Why is this bad?** Rust will silently lose precision during
17+
/// conversion to a float.
18+
///
19+
/// **Known problems:** None.
20+
///
21+
/// **Example:**
22+
///
23+
/// ```rust
24+
/// // Bad
25+
/// let _: f32 = 16_777_217.0; // 16_777_216.0
26+
///
27+
/// // Good
28+
/// let _: f32 = 16_777_216.0;
29+
/// let _: f64 = 16_777_217.0;
30+
/// ```
31+
pub LOSSY_FLOAT_LITERAL,
32+
restriction,
33+
"lossy whole number float literals"
34+
}
35+
36+
declare_lint_pass!(LossyFloatLiteral => [LOSSY_FLOAT_LITERAL]);
37+
38+
impl LateLintPass<'_, '_> for LossyFloatLiteral {
39+
fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &'_ hir::Expr<'_>) {
40+
if_chain! {
41+
let ty = cx.tables.expr_ty(expr);
42+
if let ty::Float(fty) = ty.kind;
43+
if let hir::ExprKind::Lit(ref lit) = expr.kind;
44+
if let LitKind::Float(sym, lit_float_ty) = lit.node;
45+
let sym_str = sym.as_str();
46+
// Ignore literals with exponential notation
47+
if !sym_str.contains(|c| c == 'e' || c == 'E');
48+
then {
49+
let (is_whole, float_str) = match fty {
50+
FloatTy::F32 => {
51+
let value = sym_str.parse::<f32>().unwrap();
52+
53+
(value.fract() == 0.0, value.to_string())
54+
},
55+
FloatTy::F64 => {
56+
let value = sym_str.parse::<f64>().unwrap();
57+
58+
(value.fract() == 0.0, value.to_string())
59+
}
60+
};
61+
let type_suffix = match lit_float_ty {
62+
LitFloatType::Suffixed(FloatTy::F32) => Some("f32"),
63+
LitFloatType::Suffixed(FloatTy::F64) => Some("f64"),
64+
_ => None
65+
};
66+
67+
if is_whole && sym_str.split('.').next().unwrap() != float_str {
68+
span_lint_and_sugg(
69+
cx,
70+
LOSSY_FLOAT_LITERAL,
71+
expr.span,
72+
"literal cannot be represented as the underlying type without loss of precision",
73+
"consider changing the type or replacing it with",
74+
format_numeric_literal(
75+
format!("{}.0", float_str).as_str(),
76+
type_suffix,
77+
true
78+
),
79+
Applicability::MachineApplicable,
80+
);
81+
}
82+
}
83+
}
84+
}
85+
}

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 355] = [
9+
pub const ALL_LINTS: [Lint; 356] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1001,6 +1001,13 @@ pub const ALL_LINTS: [Lint; 355] = [
10011001
deprecation: None,
10021002
module: "booleans",
10031003
},
1004+
Lint {
1005+
name: "lossy_float_literal",
1006+
group: "restriction",
1007+
desc: "lossy whole number float literals",
1008+
deprecation: None,
1009+
module: "lossy_float_literal",
1010+
},
10041011
Lint {
10051012
name: "main_recursion",
10061013
group: "style",

tests/ui/lossy_float_literal.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#![warn(clippy::lossy_float_literal)]
2+
#![allow(clippy::excessive_precision)]
3+
4+
fn main() {
5+
// Lossy whole-number float literals
6+
let _: f32 = 16_777_217.0;
7+
let _: f32 = 16_777_219.0;
8+
let _: f32 = 16_777_219.;
9+
let _: f32 = 16_777_219.000;
10+
let _ = 16_777_219f32;
11+
let _: f32 = -16_777_219.0;
12+
let _: f64 = 9_007_199_254_740_993.0;
13+
let _: f64 = 9_007_199_254_740_993.;
14+
let _: f64 = 9_007_199_254_740_993.00;
15+
let _ = 9_007_199_254_740_993f64;
16+
let _: f64 = -9_007_199_254_740_993.0;
17+
18+
// Lossless whole number float literals
19+
let _: f32 = 16_777_216.0;
20+
let _: f32 = 16_777_218.0;
21+
let _: f32 = 16_777_220.0;
22+
let _: f32 = -16_777_216.0;
23+
let _: f32 = -16_777_220.0;
24+
let _: f64 = 16_777_217.0;
25+
let _: f64 = -16_777_217.0;
26+
let _: f64 = 9_007_199_254_740_992.0;
27+
let _: f64 = -9_007_199_254_740_992.0;
28+
29+
// Ignored whole number float literals
30+
let _: f32 = 1e25;
31+
let _: f32 = 1E25;
32+
let _: f64 = 1e99;
33+
let _: f64 = 1E99;
34+
let _: f32 = 0.1;
35+
}

tests/ui/lossy_float_literal.stderr

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
error: literal cannot be represented as the underlying type without loss of precision
2+
--> $DIR/lossy_float_literal.rs:6:18
3+
|
4+
LL | let _: f32 = 16_777_217.0;
5+
| ^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_216.0`
6+
|
7+
= note: `-D clippy::lossy-float-literal` implied by `-D warnings`
8+
9+
error: literal cannot be represented as the underlying type without loss of precision
10+
--> $DIR/lossy_float_literal.rs:7:18
11+
|
12+
LL | let _: f32 = 16_777_219.0;
13+
| ^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0`
14+
15+
error: literal cannot be represented as the underlying type without loss of precision
16+
--> $DIR/lossy_float_literal.rs:8:18
17+
|
18+
LL | let _: f32 = 16_777_219.;
19+
| ^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0`
20+
21+
error: literal cannot be represented as the underlying type without loss of precision
22+
--> $DIR/lossy_float_literal.rs:9:18
23+
|
24+
LL | let _: f32 = 16_777_219.000;
25+
| ^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0`
26+
27+
error: literal cannot be represented as the underlying type without loss of precision
28+
--> $DIR/lossy_float_literal.rs:10:13
29+
|
30+
LL | let _ = 16_777_219f32;
31+
| ^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0_f32`
32+
33+
error: literal cannot be represented as the underlying type without loss of precision
34+
--> $DIR/lossy_float_literal.rs:11:19
35+
|
36+
LL | let _: f32 = -16_777_219.0;
37+
| ^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0`
38+
39+
error: literal cannot be represented as the underlying type without loss of precision
40+
--> $DIR/lossy_float_literal.rs:12:18
41+
|
42+
LL | let _: f64 = 9_007_199_254_740_993.0;
43+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0`
44+
45+
error: literal cannot be represented as the underlying type without loss of precision
46+
--> $DIR/lossy_float_literal.rs:13:18
47+
|
48+
LL | let _: f64 = 9_007_199_254_740_993.;
49+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0`
50+
51+
error: literal cannot be represented as the underlying type without loss of precision
52+
--> $DIR/lossy_float_literal.rs:14:18
53+
|
54+
LL | let _: f64 = 9_007_199_254_740_993.00;
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0`
56+
57+
error: literal cannot be represented as the underlying type without loss of precision
58+
--> $DIR/lossy_float_literal.rs:15:13
59+
|
60+
LL | let _ = 9_007_199_254_740_993f64;
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0_f64`
62+
63+
error: literal cannot be represented as the underlying type without loss of precision
64+
--> $DIR/lossy_float_literal.rs:16:19
65+
|
66+
LL | let _: f64 = -9_007_199_254_740_993.0;
67+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0`
68+
69+
error: aborting due to 11 previous errors
70+

0 commit comments

Comments
 (0)