]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_precision.rs
Merge pull request #2673 from estk/excessive_precision
[rust.git] / clippy_lints / src / excessive_precision.rs
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 }