]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_precision.rs
Auto merge of #3518 - sinkuu:redundant_clone_tw, r=phansch
[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 #[allow(clippy::doc_markdown)]
113 /// Should we exclude the float because it has a `.0` or `.` suffix
114 /// Ex 1_000_000_000.0
115 /// Ex 1_000_000_000.
116 fn dot_zero_exclusion(s: &str) -> bool {
117     if let Some(after_dec) = s.split('.').nth(1) {
118         let mut decpart = after_dec.chars().take_while(|c| *c != 'e' || *c != 'E');
119
120         match decpart.next() {
121             Some('0') => decpart.count() == 0,
122             Some(_) => false,
123             None => true,
124         }
125     } else {
126         false
127     }
128 }
129
130 fn max_digits(fty: FloatTy) -> u32 {
131     match fty {
132         FloatTy::F32 => f32::DIGITS,
133         FloatTy::F64 => f64::DIGITS,
134     }
135 }
136
137 /// Counts the digits excluding leading zeros
138 fn count_digits(s: &str) -> usize {
139     // Note that s does not contain the f32/64 suffix, and underscores have been stripped
140     s.chars()
141         .filter(|c| *c != '-' && *c != '.')
142         .take_while(|c| *c != 'e' && *c != 'E')
143         .fold(0, |count, c| {
144             // leading zeros
145             if c == '0' && count == 0 {
146                 count
147             } else {
148                 count + 1
149             }
150         })
151 }
152
153 enum FloatFormat {
154     LowerExp,
155     UpperExp,
156     Normal,
157 }
158 impl FloatFormat {
159     fn new(s: &str) -> Self {
160         s.chars()
161             .find_map(|x| match x {
162                 'e' => Some(FloatFormat::LowerExp),
163                 'E' => Some(FloatFormat::UpperExp),
164                 _ => None,
165             })
166             .unwrap_or(FloatFormat::Normal)
167     }
168     fn format<T>(&self, f: T) -> String
169     where
170         T: fmt::UpperExp + fmt::LowerExp + fmt::Display,
171     {
172         match self {
173             FloatFormat::LowerExp => format!("{:e}", f),
174             FloatFormat::UpperExp => format!("{:E}", f),
175             FloatFormat::Normal => format!("{}", f),
176         }
177     }
178 }