diff --git a/clippy_lints/src/excessive_precision.rs b/clippy_lints/src/excessive_precision.rs new file mode 100644 index 000000000..b96ae1882 --- /dev/null +++ b/clippy_lints/src/excessive_precision.rs @@ -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 { + 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::().map(|f| formatter.format(f)), + FloatTy::F64 => sym_str.parse::().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(&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), + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 9b005016e..c2b089594 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -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; @@ -295,6 +298,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); diff --git a/tests/ui/approx_const.rs b/tests/ui/approx_const.rs index 394aa9d1e..d353d9075 100644 --- a/tests/ui/approx_const.rs +++ b/tests/ui/approx_const.rs @@ -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; diff --git a/tests/ui/approx_const.stderr b/tests/ui/approx_const.stderr index dda28433d..e5d2ba296 100644 --- a/tests/ui/approx_const.stderr +++ b/tests/ui/approx_const.stderr @@ -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 diff --git a/tests/ui/excessive_precision.rs b/tests/ui/excessive_precision.rs new file mode 100644 index 000000000..d209367cc --- /dev/null +++ b/tests/ui/excessive_precision.rs @@ -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 = vec![0.123_456_789]; + let vec64: Vec = 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; +} diff --git a/tests/ui/excessive_precision.stderr b/tests/ui/excessive_precision.stderr new file mode 100644 index 000000000..de022afb5 --- /dev/null +++ b/tests/ui/excessive_precision.stderr @@ -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 = 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 = 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 +