@@ -6,33 +6,31 @@ use rustc_errors::Applicability;
6
6
use rustc_hir as hir;
7
7
use rustc_lint:: { LateContext , LateLintPass } ;
8
8
use 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} ;
13
10
use syntax:: ast:: * ;
14
11
15
12
declare_clippy_lint ! {
16
13
/// **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.
18
15
///
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.
20
18
///
21
19
/// **Known problems:** None.
22
20
///
23
21
/// **Example:**
24
22
///
25
23
/// ```rust
26
24
/// // 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
29
27
///
30
28
/// // 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;
33
31
/// ```
34
32
pub EXCESSIVE_PRECISION ,
35
- style ,
33
+ correctness ,
36
34
"excessive precision for float literal"
37
35
}
38
36
@@ -44,71 +42,62 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExcessivePrecision {
44
42
let ty = cx. tables. expr_ty( expr) ;
45
43
if let ty:: Float ( fty) = ty. kind;
46
44
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;
49
46
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
+ } ;
86
64
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
+ }
91
89
}
92
- } else {
93
- None
94
90
}
95
91
}
96
92
}
97
93
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
101
95
#[ 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
+ }
112
101
}
113
102
114
103
#[ must_use]
0 commit comments