1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
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.
10 use crate::rustc::hir;
11 use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
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;
23 /// **What it does:** Checks for float literals with a precision greater
24 /// than that supported by the underlying type
26 /// **Why is this bad?** Rust will truncate the literal silently.
28 /// **Known problems:** None.
34 /// let v: f32 = 0.123_456_789_9;
35 /// println!("{}", v); // 0.123_456_789
38 /// let v: f64 = 0.123_456_789_9;
39 /// println!("{}", v); // 0.123_456_789_9
41 declare_clippy_lint! {
42 pub EXCESSIVE_PRECISION,
44 "excessive precision for float literal"
47 pub struct ExcessivePrecision;
49 impl LintPass for ExcessivePrecision {
50 fn get_lints(&self) -> LintArray {
51 lint_array!(EXCESSIVE_PRECISION)
55 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExcessivePrecision {
56 fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) {
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);
68 "float has excessive precision",
69 "consider changing the type or truncating it to",
71 Applicability::MachineApplicable,
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) {
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);
94 FloatTy::F32 => sym_str.parse::<f32>().map(|f| formatter.format(f)),
95 FloatTy::F64 => sym_str.parse::<f64>().map(|f| formatter.format(f)),
97 // We know this will parse since we are in LatePass
103 let di = super::literal_representation::DigitInfo::new(&s, true);
104 Some(di.grouping_hint())
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');
119 match decpart.next() {
120 Some('0') => decpart.count() == 0,
129 fn max_digits(fty: FloatTy) -> u32 {
131 FloatTy::F32 => f32::DIGITS,
132 FloatTy::F64 => f64::DIGITS,
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
140 .filter(|c| *c != '-' && *c != '.')
141 .take_while(|c| *c != 'e' && *c != 'E')
142 .fold(0, |count, c| {
144 if c == '0' && count == 0 {
158 fn new(s: &str) -> Self {
160 .find_map(|x| match x {
161 'e' => Some(FloatFormat::LowerExp),
162 'E' => Some(FloatFormat::UpperExp),
165 .unwrap_or(FloatFormat::Normal)
167 fn format<T>(&self, f: T) -> String
169 T: fmt::UpperExp + fmt::LowerExp + fmt::Display,
172 FloatFormat::LowerExp => format!("{:e}", f),
173 FloatFormat::UpperExp => format!("{:E}", f),
174 FloatFormat::Normal => format!("{}", f),