]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_precision.rs
Fix ICE for issues 2767, 2499, 1782
[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         if dot_zero_exclusion(&sym_str) {
73             return None
74         }
75         // Try to bail out if the float is for sure fine.
76         // If its within the 2 decimal digits of being out of precision we
77         // check if the parsed representation is the same as the string
78         // since we'll need the truncated string anyway.
79         let digits = count_digits(&sym_str);
80         if digits > max as usize {
81             let formatter = FloatFormat::new(&sym_str);
82             let sr = match *fty {
83                 FloatTy::F32 => sym_str.parse::<f32>().map(|f| formatter.format(f)),
84                 FloatTy::F64 => sym_str.parse::<f64>().map(|f| formatter.format(f)),
85             };
86             // We know this will parse since we are in LatePass
87             let s = sr.unwrap();
88
89             if sym_str == s {
90                 None
91             } else {
92                 let di = super::literal_representation::DigitInfo::new(&s, true);
93                 Some(di.grouping_hint())
94             }
95         } else {
96             None
97         }
98     }
99 }
100
101 /// Should we exclude the float because it has a .0 suffix
102 /// Ex 1_000_000_000.0
103 fn dot_zero_exclusion(s: &str) -> bool {
104     if let Some(after_dec) = s.split('.').nth(1) {
105         let mut decpart = after_dec
106             .chars()
107             .take_while(|c| *c != 'e' || *c != 'E');
108
109         match decpart.next() {
110             Some('0') => decpart.count() == 0,
111             _ => false,
112         }
113     } else {
114         false
115     }
116 }
117
118 fn max_digits(fty: &FloatTy) -> u32 {
119     match fty {
120         FloatTy::F32 => f32::DIGITS,
121         FloatTy::F64 => f64::DIGITS,
122     }
123 }
124
125 /// Counts the digits excluding leading zeros
126 fn count_digits(s: &str) -> usize {
127     // Note that s does not contain the f32/64 suffix
128     s.chars()
129         .filter(|c| *c != '-' || *c != '.')
130         .take_while(|c| *c != 'e' || *c != 'E')
131         .fold(0, |count, c| {
132             // leading zeros
133             if c == '0' && count == 0 {
134                 count
135             } else {
136                 count + 1
137             }
138         })
139 }
140
141 enum FloatFormat {
142     LowerExp,
143     UpperExp,
144     Normal,
145 }
146 impl FloatFormat {
147     fn new(s: &str) -> Self {
148         s.chars()
149             .find_map(|x| match x {
150                 'e' => Some(FloatFormat::LowerExp),
151                 'E' => Some(FloatFormat::UpperExp),
152                 _ => None,
153             })
154             .unwrap_or(FloatFormat::Normal)
155     }
156     fn format<T>(&self, f: T) -> String
157     where T: fmt::UpperExp + fmt::LowerExp + fmt::Display {
158         match self {
159             FloatFormat::LowerExp => format!("{:e}", f),
160             FloatFormat::UpperExp => format!("{:E}", f),
161             FloatFormat::Normal => format!("{}", f),
162         }
163     }
164 }