@@ -6,33 +6,31 @@ use rustc_errors::Applicability;
66use rustc_hir as hir;
77use rustc_lint:: { LateContext , LateLintPass } ;
88use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
9- use rustc_span:: symbol:: Symbol ;
10- use std:: f32;
11- use std:: f64;
12- use std:: fmt;
9+ use std:: { f32, f64, fmt} ;
1310use syntax:: ast:: * ;
1411
1512declare_clippy_lint ! {
1613 /// **What it does:** Checks for float literals with a precision greater
17- /// than that supported by the underlying type
14+ /// than that supported by the underlying type.
1815 ///
19- /// **Why is this bad?** Rust will truncate the literal silently.
16+ /// **Why is this bad?** Rust will silently lose precision during conversion
17+ /// to a float.
2018 ///
2119 /// **Known problems:** None.
2220 ///
2321 /// **Example:**
2422 ///
2523 /// ```rust
2624 /// // Bad
27- /// let v : f32 = 0.123_456_789_9;
28- /// println!("{}", v) ; // 0.123_456_789
25+ /// let a : f32 = 0.123_456_789_9; // 0.123_456_789
26+ /// let b: f32 = 16_777_217.0 ; // 16_777_216.0
2927 ///
3028 /// // Good
31- /// let v : f64 = 0.123_456_789_9;
32- /// println!("{}", v); // 0.123_456_789_9
29+ /// let a : f64 = 0.123_456_789_9;
30+ /// let b: f64 = 16_777_216.0;
3331 /// ```
3432 pub EXCESSIVE_PRECISION ,
35- style ,
33+ correctness ,
3634 "excessive precision for float literal"
3735}
3836
@@ -44,71 +42,62 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExcessivePrecision {
4442 let ty = cx. tables. expr_ty( expr) ;
4543 if let ty:: Float ( fty) = ty. kind;
4644 if let hir:: ExprKind :: Lit ( ref lit) = expr. kind;
47- if let LitKind :: Float ( sym, _) = lit. node;
48- if let Some ( sugg) = Self :: check( sym, fty) ;
45+ if let LitKind :: Float ( sym, lit_float_ty) = lit. node;
4946 then {
50- span_lint_and_sugg(
51- cx,
52- EXCESSIVE_PRECISION ,
53- expr. span,
54- "float has excessive precision" ,
55- "consider changing the type or truncating it to" ,
56- sugg,
57- Applicability :: MachineApplicable ,
58- ) ;
59- }
60- }
61- }
62- }
63-
64- impl ExcessivePrecision {
65- // None if nothing to lint, Some(suggestion) if lint necessary
66- #[ must_use]
67- fn check ( sym : Symbol , fty : FloatTy ) -> Option < String > {
68- let max = max_digits ( fty) ;
69- let sym_str = sym. as_str ( ) ;
70- if dot_zero_exclusion ( & sym_str) {
71- return None ;
72- }
73- // Try to bail out if the float is for sure fine.
74- // If its within the 2 decimal digits of being out of precision we
75- // check if the parsed representation is the same as the string
76- // since we'll need the truncated string anyway.
77- let digits = count_digits ( & sym_str) ;
78- if digits > max as usize {
79- let formatter = FloatFormat :: new ( & sym_str) ;
80- let sr = match fty {
81- FloatTy :: F32 => sym_str. parse :: < f32 > ( ) . map ( |f| formatter. format ( f) ) ,
82- FloatTy :: F64 => sym_str. parse :: < f64 > ( ) . map ( |f| formatter. format ( f) ) ,
83- } ;
84- // We know this will parse since we are in LatePass
85- let s = sr. unwrap ( ) ;
47+ let sym_str = sym. as_str( ) ;
48+ let formatter = FloatFormat :: new( & sym_str) ;
49+ // Try to bail out if the float is for sure fine.
50+ // If its within the 2 decimal digits of being out of precision we
51+ // check if the parsed representation is the same as the string
52+ // since we'll need the truncated string anyway.
53+ let digits = count_digits( & sym_str) ;
54+ let max = max_digits( fty) ;
55+ let float_str = match fty {
56+ FloatTy :: F32 => sym_str. parse:: <f32 >( ) . map( |f| formatter. format( f) ) ,
57+ FloatTy :: F64 => sym_str. parse:: <f64 >( ) . map( |f| formatter. format( f) ) ,
58+ } . unwrap( ) ;
59+ let type_suffix = match lit_float_ty {
60+ LitFloatType :: Suffixed ( FloatTy :: F32 ) => Some ( "f32" ) ,
61+ LitFloatType :: Suffixed ( FloatTy :: F64 ) => Some ( "f64" ) ,
62+ _ => None
63+ } ;
8664
87- if sym_str == s {
88- None
89- } else {
90- Some ( format_numeric_literal ( & s, None , true ) )
65+ if is_whole_number( & sym_str, fty) {
66+ // Normalize the literal by stripping the fractional portion
67+ if sym_str. split( '.' ) . next( ) . unwrap( ) != float_str {
68+ span_lint_and_sugg(
69+ cx,
70+ EXCESSIVE_PRECISION ,
71+ expr. span,
72+ "literal cannot be represented as the underlying type without loss of precision" ,
73+ "consider changing the type or replacing it with" ,
74+ format_numeric_literal( format!( "{}.0" , float_str) . as_str( ) , type_suffix, true ) ,
75+ Applicability :: MachineApplicable ,
76+ ) ;
77+ }
78+ } else if digits > max as usize && sym_str != float_str {
79+ span_lint_and_sugg(
80+ cx,
81+ EXCESSIVE_PRECISION ,
82+ expr. span,
83+ "float has excessive precision" ,
84+ "consider changing the type or truncating it to" ,
85+ format_numeric_literal( & float_str, type_suffix, true ) ,
86+ Applicability :: MachineApplicable ,
87+ ) ;
88+ }
9189 }
92- } else {
93- None
9490 }
9591 }
9692}
9793
98- /// Should we exclude the float because it has a `.0` or `.` suffix
99- /// Ex `1_000_000_000.0`
100- /// Ex `1_000_000_000.`
94+ // Checks whether a float literal is a whole number
10195#[ must_use]
102- fn dot_zero_exclusion ( s : & str ) -> bool {
103- s. split ( '.' ) . nth ( 1 ) . map_or ( false , |after_dec| {
104- let mut decpart = after_dec. chars ( ) . take_while ( |c| * c != 'e' || * c != 'E' ) ;
105-
106- match decpart. next ( ) {
107- Some ( '0' ) => decpart. count ( ) == 0 ,
108- Some ( _) => false ,
109- None => true ,
110- }
111- } )
96+ fn is_whole_number ( sym_str : & str , fty : FloatTy ) -> bool {
97+ match fty {
98+ FloatTy :: F32 => sym_str. parse :: < f32 > ( ) . unwrap ( ) . fract ( ) == 0.0 ,
99+ FloatTy :: F64 => sym_str. parse :: < f64 > ( ) . unwrap ( ) . fract ( ) == 0.0 ,
100+ }
112101}
113102
114103#[ must_use]
0 commit comments