]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_precision.rs
Auto merge of #3556 - lucasloisp:bool-ord-comparison, r=oli-obk
[rust.git] / clippy_lints / src / excessive_precision.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10 use crate::rustc::hir;
11 use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
12 use crate::rustc::ty;
13 use crate::rustc::{declare_tool_lint, lint_array};
14 use crate::rustc_errors::Applicability;
15 use crate::syntax::ast::*;
16 use crate::syntax_pos::symbol::Symbol;
17 use crate::utils::span_lint_and_sugg;
18 use if_chain::if_chain;
19 use std::f32;
20 use std::f64;
21 use std::fmt;
22
23 /// **What it does:** Checks for float literals with a precision greater
24 /// than that supported by the underlying type
25 ///
26 /// **Why is this bad?** Rust will truncate the literal silently.
27 ///
28 /// **Known problems:** None.
29 ///
30 /// **Example:**
31 ///
32 /// ```rust
33 /// // Bad
34 /// let v: f32 = 0.123_456_789_9;
35 /// println!("{}", v); //  0.123_456_789
36 ///
37 /// // Good
38 /// let v: f64 = 0.123_456_789_9;
39 /// println!("{}", v); //  0.123_456_789_9
40 /// ```
41 declare_clippy_lint! {
42     pub EXCESSIVE_PRECISION,
43     style,
44     "excessive precision for float literal"
45 }
46
47 pub struct ExcessivePrecision;
48
49 impl LintPass for ExcessivePrecision {
50     fn get_lints(&self) -> LintArray {
51         lint_array!(EXCESSIVE_PRECISION)
52     }
53 }
54
55 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExcessivePrecision {
56     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) {
57         if_chain! {
58             let ty = cx.tables.expr_ty(expr);
59             if let ty::Float(fty) = ty.sty;
60             if let hir::ExprKind::Lit(ref lit) = expr.node;
61             if let LitKind::Float(sym, _) | LitKind::FloatUnsuffixed(sym) = lit.node;
62             if let Some(sugg) = self.check(sym, fty);
63             then {
64                 span_lint_and_sugg(
65                     cx,
66                     EXCESSIVE_PRECISION,
67                     expr.span,
68                     "float has excessive precision",
69                     "consider changing the type or truncating it to",
70                     sugg,
71                     Applicability::MachineApplicable,
72                 );
73             }
74         }
75     }
76 }
77
78 impl ExcessivePrecision {
79     // None if nothing to lint, Some(suggestion) if lint necessary
80     fn check(&self, sym: Symbol, fty: FloatTy) -> Option<String> {
81         let max = max_digits(fty);
82         let sym_str = sym.as_str();
83         if dot_zero_exclusion(&sym_str) {
84             return None;
85         }
86         // Try to bail out if the float is for sure fine.
87         // If its within the 2 decimal digits of being out of precision we
88         // check if the parsed representation is the same as the string
89         // since we'll need the truncated string anyway.
90         let digits = count_digits(&sym_str);
91         if digits > max as usize {
92             let formatter = FloatFormat::new(&sym_str);
93             let sr = match fty {
94                 FloatTy::F32 => sym_str.parse::<f32>().map(|f| formatter.format(f)),
95                 FloatTy::F64 => sym_str.parse::<f64>().map(|f| formatter.format(f)),
96             };
97             // We know this will parse since we are in LatePass
98             let s = sr.unwrap();
99
100             if sym_str == s {
101                 None
102             } else {
103                 let di = super::literal_representation::DigitInfo::new(&s, true);
104                 Some(di.grouping_hint())
105             }
106         } else {
107             None
108         }
109     }
110 }
111
112 /// Should we exclude the float because it has a `.0` or `.` suffix
113 /// Ex `1_000_000_000.0`
114 /// Ex `1_000_000_000.`
115 fn dot_zero_exclusion(s: &str) -> bool {
116     if let Some(after_dec) = s.split('.').nth(1) {
117         let mut decpart = after_dec.chars().take_while(|c| *c != 'e' || *c != 'E');
118
119         match decpart.next() {
120             Some('0') => decpart.count() == 0,
121             Some(_) => false,
122             None => true,
123         }
124     } else {
125         false
126     }
127 }
128
129 fn max_digits(fty: FloatTy) -> u32 {
130     match fty {
131         FloatTy::F32 => f32::DIGITS,
132         FloatTy::F64 => f64::DIGITS,
133     }
134 }
135
136 /// Counts the digits excluding leading zeros
137 fn count_digits(s: &str) -> usize {
138     // Note that s does not contain the f32/64 suffix, and underscores have been stripped
139     s.chars()
140         .filter(|c| *c != '-' && *c != '.')
141         .take_while(|c| *c != 'e' && *c != 'E')
142         .fold(0, |count, c| {
143             // leading zeros
144             if c == '0' && count == 0 {
145                 count
146             } else {
147                 count + 1
148             }
149         })
150 }
151
152 enum FloatFormat {
153     LowerExp,
154     UpperExp,
155     Normal,
156 }
157 impl FloatFormat {
158     fn new(s: &str) -> Self {
159         s.chars()
160             .find_map(|x| match x {
161                 'e' => Some(FloatFormat::LowerExp),
162                 'E' => Some(FloatFormat::UpperExp),
163                 _ => None,
164             })
165             .unwrap_or(FloatFormat::Normal)
166     }
167     fn format<T>(&self, f: T) -> String
168     where
169         T: fmt::UpperExp + fmt::LowerExp + fmt::Display,
170     {
171         match self {
172             FloatFormat::LowerExp => format!("{:e}", f),
173             FloatFormat::UpperExp => format!("{:E}", f),
174             FloatFormat::Normal => format!("{}", f),
175         }
176     }
177 }