New excessive precision lint for floats

This commit is contained in:
Evan Simmons 2018-04-12 17:42:57 -07:00
parent 79e818267b
commit 9b14ad493b
6 changed files with 300 additions and 3 deletions

View file

@ -0,0 +1,141 @@
use rustc::hir;
use rustc::lint::*;
use rustc::ty::TypeVariants;
use std::f32;
use std::f64;
use std::fmt;
use syntax::ast::*;
use syntax_pos::symbol::Symbol;
use utils::span_lint_and_sugg;
/// **What it does:** Checks for float literals with a precision greater
/// than that supported by the underlying type
///
/// **Why is this bad?** Rust will truncate the literal silently.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// // Bad
/// Insert a short example of code that triggers the lint
/// let v: f32 = 0.123_456_789_9;
/// println!("{}", v); // 0.123_456_789
///
/// // Good
/// Insert a short example of improved code that doesn't trigger the lint
/// let v: f64 = 0.123_456_789_9;
/// println!("{}", v); // 0.123_456_789_9
/// ```
declare_clippy_lint! {
pub EXCESSIVE_PRECISION,
style,
"excessive precision for float literal"
}
pub struct ExcessivePrecision;
impl LintPass for ExcessivePrecision {
fn get_lints(&self) -> LintArray {
lint_array!(EXCESSIVE_PRECISION)
}
}
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExcessivePrecision {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) {
if_chain! {
let ty = cx.tables.expr_ty(expr);
if let TypeVariants::TyFloat(ref fty) = ty.sty;
if let hir::ExprLit(ref lit) = expr.node;
if let LitKind::Float(ref sym, _) | LitKind::FloatUnsuffixed(ref sym) = lit.node;
if let Some(sugg) = self.check(sym, fty);
then {
span_lint_and_sugg(
cx,
EXCESSIVE_PRECISION,
expr.span,
"float has excessive precision",
"consider changing the type or truncating it to",
sugg,
);
}
}
}
}
impl ExcessivePrecision {
// None if nothing to lint, Some(suggestion) if lint neccessary
fn check(&self, sym: &Symbol, fty: &FloatTy) -> Option<String> {
let max = max_digits(fty);
let sym_str = sym.as_str();
let formatter = FloatFormat::new(&sym_str);
let digits = count_digits(&sym_str);
// Try to bail out if the float is for sure fine.
// If its within the 2 decimal digits of being out of precision we
// check if the parsed representation is the same as the string
// since we'll need the truncated string anyway.
if digits > max as usize {
let sr = match *fty {
FloatTy::F32 => sym_str.parse::<f32>().map(|f| formatter.format(f)),
FloatTy::F64 => sym_str.parse::<f64>().map(|f| formatter.format(f)),
};
// We know this will parse since we are in LatePass
let s = sr.unwrap();
if sym_str == s {
None
} else {
Some(s)
}
} else {
None
}
}
}
fn max_digits(fty: &FloatTy) -> u32 {
match fty {
FloatTy::F32 => f32::DIGITS,
FloatTy::F64 => f64::DIGITS,
}
}
fn count_digits(s: &str) -> usize {
s.chars()
.filter(|c| *c != '-' || *c != '.')
.take_while(|c| *c != 'e' || *c != 'E')
.fold(0, |count, c| {
// leading zeros
if c == '0' && count == 0 {
count
} else {
count + 1
}
})
}
enum FloatFormat {
LowerExp,
UpperExp,
Normal,
}
impl FloatFormat {
fn new(s: &str) -> Self {
s.chars()
.find_map(|x| match x {
'e' => Some(FloatFormat::LowerExp),
'E' => Some(FloatFormat::UpperExp),
_ => None,
})
.unwrap_or(FloatFormat::Normal)
}
fn format<T>(&self, f: T) -> String
where T: fmt::UpperExp + fmt::LowerExp + fmt::Display {
match self {
FloatFormat::LowerExp => format!("{:e}", f),
FloatFormat::UpperExp => format!("{:E}", f),
FloatFormat::Normal => format!("{}", f),
}
}
}

View file

@ -13,6 +13,8 @@
// FIXME(mark-i-m) remove after i128 stablization merges
#![allow(stable_features)]
#![feature(i128, i128_type)]
#![feature(iterator_find_map)]
#[macro_use]
extern crate rustc;
@ -124,6 +126,7 @@ pub mod erasing_op;
pub mod escape;
pub mod eta_reduction;
pub mod eval_order_dependence;
pub mod excessive_precision;
pub mod explicit_write;
pub mod fallible_impl_from;
pub mod format;
@ -294,6 +297,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
reg.register_early_lint_pass(box enum_variants::EnumVariantNames::new(conf.enum_variant_name_threshold));
reg.register_late_lint_pass(box enum_glob_use::EnumGlobUse);
reg.register_late_lint_pass(box enum_clike::UnportableVariant);
reg.register_late_lint_pass(box excessive_precision::ExcessivePrecision);
reg.register_late_lint_pass(box bit_mask::BitMask::new(conf.verbose_bit_mask_threshold));
reg.register_late_lint_pass(box ptr::PointerPass);
reg.register_late_lint_pass(box needless_bool::NeedlessBool);

View file

@ -42,7 +42,7 @@ fn main() {
let my_ln_2 = 0.6931471805599453;
let no_ln_2 = 0.693;
let my_log10_e = 0.43429448190325182;
let my_log10_e = 0.4342944819032518;
let no_log10_e = 0.434;
let my_log2_e = 1.4426950408889634;

View file

@ -87,8 +87,8 @@ error: approximate value of `f{32, 64}::consts::LN_2` found. Consider using it d
error: approximate value of `f{32, 64}::consts::LOG10_E` found. Consider using it directly
--> $DIR/approx_const.rs:45:22
|
45 | let my_log10_e = 0.43429448190325182;
| ^^^^^^^^^^^^^^^^^^^
45 | let my_log10_e = 0.4342944819032518;
| ^^^^^^^^^^^^^^^^^^
error: approximate value of `f{32, 64}::consts::LOG2_E` found. Consider using it directly
--> $DIR/approx_const.rs:48:21

View file

@ -0,0 +1,52 @@
#![feature(plugin, custom_attribute)]
#![warn(excessive_precision)]
#![allow(print_literal)]
fn main() {
// TODO add prefix tests
// Consts
const GOOD32_SUF: f32 = 0.123_456_f32;
const GOOD32: f32 = 0.123_456;
const GOOD32_SM: f32 = 0.000_000_000_1;
const GOOD64: f64 = 0.123_456_789_012;
const GOOD64_SM: f32 = 0.000_000_000_000_000_1;
const BAD32_1: f32 = 0.123_456_789_f32;
const BAD32_2: f32 = 0.123_456_789;
const BAD32_3: f32 = 0.100_000_000_000_1;
const BAD64_1: f64 = 0.123_456_789_012_345_67f64;
const BAD64_2: f64 = 0.123_456_789_012_345_67;
const BAD64_3: f64 = 0.100_000_000_000_000_000_1;
// Literal
println!("{}", 8.888_888_888_888_888_888_888);
// TODO add inferred type tests for f32
// TODO add tests cases exactly on the edge
// Locals
let good32: f32 = 0.123_456_f32;
let good32_2: f32 = 0.123_456;
let good64: f64 = 0.123_456_789_012f64;
let good64: f64 = 0.123_456_789_012;
let good64_2 = 0.123_456_789_012;
let bad32_1: f32 = 1.123_456_789_f32;
let bad32_2: f32 = 1.123_456_789;
let bad64_1: f64 = 0.123_456_789_012_345_67f64;
let bad64_2: f64 = 0.123_456_789_012_345_67;
let bad64_3 = 0.123_456_789_012_345_67;
// TODO Vectors / nested vectors
let vec32: Vec<f32> = vec![0.123_456_789];
let vec64: Vec<f64> = vec![0.123_456_789_123_456_789];
// Exponential float notation
let good_e32: f32 = 1e-10;
let bad_e32: f32 = 1.123_456_788_888e-10;
let good_bige32: f32 = 1E-10;
let bad_bige32: f32 = 1.123_456_788_888E-10;
}

View file

@ -0,0 +1,100 @@
error: float has excessive precision
--> $DIR/excessive_precision.rs:14:26
|
14 | const BAD32_1: f32 = 0.123_456_789_f32;
| ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345679`
|
= note: `-D excessive-precision` implied by `-D warnings`
error: float has excessive precision
--> $DIR/excessive_precision.rs:15:26
|
15 | const BAD32_2: f32 = 0.123_456_789;
| ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345679`
error: float has excessive precision
--> $DIR/excessive_precision.rs:16:26
|
16 | const BAD32_3: f32 = 0.100_000_000_000_1;
| ^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.1`
error: float has excessive precision
--> $DIR/excessive_precision.rs:18:26
|
18 | const BAD64_1: f64 = 0.123_456_789_012_345_67f64;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
error: float has excessive precision
--> $DIR/excessive_precision.rs:19:26
|
19 | const BAD64_2: f64 = 0.123_456_789_012_345_67;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
error: float has excessive precision
--> $DIR/excessive_precision.rs:20:26
|
20 | const BAD64_3: f64 = 0.100_000_000_000_000_000_1;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.1`
error: float has excessive precision
--> $DIR/excessive_precision.rs:23:20
|
23 | println!("{}", 8.888_888_888_888_888_888_888);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `8.88888888888889`
error: float has excessive precision
--> $DIR/excessive_precision.rs:35:24
|
35 | let bad32_1: f32 = 1.123_456_789_f32;
| ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.1234568`
error: float has excessive precision
--> $DIR/excessive_precision.rs:36:24
|
36 | let bad32_2: f32 = 1.123_456_789;
| ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.1234568`
error: float has excessive precision
--> $DIR/excessive_precision.rs:38:24
|
38 | let bad64_1: f64 = 0.123_456_789_012_345_67f64;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
error: float has excessive precision
--> $DIR/excessive_precision.rs:39:24
|
39 | let bad64_2: f64 = 0.123_456_789_012_345_67;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
error: float has excessive precision
--> $DIR/excessive_precision.rs:40:19
|
40 | let bad64_3 = 0.123_456_789_012_345_67;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678901234566`
error: float has excessive precision
--> $DIR/excessive_precision.rs:43:32
|
43 | let vec32: Vec<f32> = vec![0.123_456_789];
| ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345679`
error: float has excessive precision
--> $DIR/excessive_precision.rs:44:32
|
44 | let vec64: Vec<f64> = vec![0.123_456_789_123_456_789];
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.12345678912345678`
error: float has excessive precision
--> $DIR/excessive_precision.rs:48:24
|
48 | let bad_e32: f32 = 1.123_456_788_888e-10;
| ^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.1234568e-10`
error: float has excessive precision
--> $DIR/excessive_precision.rs:51:27
|
51 | let bad_bige32: f32 = 1.123_456_788_888E-10;
| ^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.1234568E-10`
error: aborting due to 16 previous errors