diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index f029dd65b395..bd54c0684862 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -7,16 +7,15 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. - //! Lints concerned with the grouping of digits with underscores in integral or //! floating-point literal expressions. -use crate::rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass, in_external_macro, LintContext}; +use crate::rustc::lint::{in_external_macro, EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass}; use crate::rustc::{declare_tool_lint, lint_array}; -use if_chain::if_chain; use crate::syntax::ast::*; use crate::syntax_pos; use crate::utils::{snippet_opt, span_lint_and_sugg}; +use if_chain::if_chain; /// **What it does:** Warns if a long integral or floating-point constant does /// not contain underscores. @@ -41,9 +40,9 @@ declare_clippy_lint! { /// **Why is this bad?** This is most probably a typo /// /// **Known problems:** -/// - Recommends a signed suffix, even though the number might be too big and an unsigned +/// - Recommends a signed suffix, even though the number might be too big and an unsigned /// suffix is required -/// - Does not match on `_128` since that is a valid grouping for decimal and octal numbers +/// - Does not match on `_128` since that is a valid grouping for decimal and octal numbers /// /// **Example:** /// @@ -168,21 +167,21 @@ impl<'a> DigitInfo<'a> { let len = sans_prefix.len(); let mut last_d = '\0'; for (d_idx, d) in sans_prefix.char_indices() { - let suffix_start = if last_d == '_' { - d_idx - 1 - } else { - d_idx - }; - if float && (d == 'f' || d == 'e' || d == 'E') || - !float && (d == 'i' || d == 'u' || is_possible_suffix_index(&sans_prefix, suffix_start, len)) { - let (digits, suffix) = sans_prefix.split_at(suffix_start); - return Self { - digits, - radix, - prefix, - suffix: Some(suffix), - float, - }; + let suffix_start = if last_d == '_' { d_idx - 1 } else { d_idx }; + if float + && (d == 'f' + || is_possible_float_suffix_index(&sans_prefix, suffix_start, len) + || ((d == 'E' || d == 'e') && !has_possible_float_suffix(&sans_prefix))) + || !float && (d == 'i' || d == 'u' || is_possible_suffix_index(&sans_prefix, suffix_start, len)) + { + let (digits, suffix) = sans_prefix.split_at(suffix_start); + return Self { + digits, + radix, + prefix, + suffix: Some(suffix), + float, + }; } last_d = d } @@ -224,18 +223,44 @@ impl<'a> DigitInfo<'a> { .map(|chunk| chunk.iter().collect()) .collect::>() .join("_"); + let suffix_hint = match self.suffix { + Some(suffix) if is_mistyped_float_suffix(suffix) => format!("_f{}", &suffix[1..]), + Some(suffix) => suffix.to_string(), + None => String::new(), + }; + format!("{}.{}{}", int_part_hint, frac_part_hint, suffix_hint) + } else if self.float && (self.digits.contains('E') || self.digits.contains('e')) { + let which_e = if self.digits.contains('E') { 'E' } else { 'e' }; + let parts: Vec<&str> = self.digits.split(which_e).collect(); + let filtered_digits_vec_0 = parts[0].chars().filter(|&c| c != '_').rev().collect::>(); + let filtered_digits_vec_1 = parts[1].chars().filter(|&c| c != '_').rev().collect::>(); + let before_e_hint = filtered_digits_vec_0 + .chunks(group_size) + .map(|chunk| chunk.iter().rev().collect()) + .rev() + .collect::>() + .join("_"); + let after_e_hint = filtered_digits_vec_1 + .chunks(group_size) + .map(|chunk| chunk.iter().rev().collect()) + .rev() + .collect::>() + .join("_"); + let suffix_hint = match self.suffix { + Some(suffix) if is_mistyped_float_suffix(suffix) => format!("_f{}", &suffix[1..]), + Some(suffix) => suffix.to_string(), + None => String::new(), + }; format!( - "{}.{}{}", - int_part_hint, - frac_part_hint, - self.suffix.unwrap_or("") + "{}{}{}{}{}", + self.prefix.unwrap_or(""), + before_e_hint, + which_e, + after_e_hint, + suffix_hint ) } else { - let filtered_digits_vec = self.digits - .chars() - .filter(|&c| c != '_') - .rev() - .collect::>(); + let filtered_digits_vec = self.digits.chars().filter(|&c| c != '_').rev().collect::>(); let mut hint = filtered_digits_vec .chunks(group_size) .map(|chunk| chunk.iter().rev().collect()) @@ -248,18 +273,11 @@ impl<'a> DigitInfo<'a> { hint = format!("{:0>4}{}", &hint[..nb_digits_to_fill], &hint[nb_digits_to_fill..]); } let suffix_hint = match self.suffix { - Some(suffix) if is_mistyped_suffix(suffix) => { - format!("_i{}", &suffix[1..]) - }, + Some(suffix) if is_mistyped_suffix(suffix) => format!("_i{}", &suffix[1..]), Some(suffix) => suffix.to_string(), - None => String::new() + None => String::new(), }; - format!( - "{}{}{}", - self.prefix.unwrap_or(""), - hint, - suffix_hint - ) + format!("{}{}{}", self.prefix.unwrap_or(""), hint, suffix_hint) } } } @@ -269,22 +287,20 @@ enum WarningType { InconsistentDigitGrouping, LargeDigitGroups, DecimalRepresentation, - MistypedLiteralSuffix + MistypedLiteralSuffix, } impl WarningType { crate fn display(&self, grouping_hint: &str, cx: &EarlyContext<'_>, span: syntax_pos::Span) { match self { - WarningType::MistypedLiteralSuffix => { - span_lint_and_sugg( - cx, - MISTYPED_LITERAL_SUFFIXES, - span, - "mistyped literal suffix", - "did you mean to write", - grouping_hint.to_string() - ) - }, + WarningType::MistypedLiteralSuffix => span_lint_and_sugg( + cx, + MISTYPED_LITERAL_SUFFIXES, + span, + "mistyped literal suffix", + "did you mean to write", + grouping_hint.to_string(), + ), WarningType::UnreadableLiteral => span_lint_and_sugg( cx, UNREADABLE_LITERAL, @@ -380,7 +396,7 @@ impl LiteralDigitGrouping { // Lint integral and fractional parts separately, and then check consistency of digit // groups if both pass. - let _ = Self::do_lint(parts[0], None) + let _ = Self::do_lint(parts[0], digit_info.suffix) .map(|integral_group_size| { if parts.len() > 1 { // Lint the fractional part of literal just like integral part, but reversed. @@ -391,11 +407,11 @@ impl LiteralDigitGrouping { fractional_group_size, parts[0].len(), parts[1].len()); - if !consistent { - WarningType::InconsistentDigitGrouping.display(&digit_info.grouping_hint(), - cx, - lit.span); - } + if !consistent { + WarningType::InconsistentDigitGrouping.display(&digit_info.grouping_hint(), + cx, + lit.span); + } }) .map_err(|warning_type| warning_type.display(&digit_info.grouping_hint(), cx, @@ -494,9 +510,7 @@ impl EarlyLintPass for LiteralRepresentation { impl LiteralRepresentation { pub fn new(threshold: u64) -> Self { - Self { - threshold, - } + Self { threshold } } fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) { // Lint integral literals. @@ -529,7 +543,12 @@ impl LiteralRepresentation { fn do_lint(digits: &str) -> Result<(), WarningType> { if digits.len() == 1 { // Lint for 1 digit literals, if someone really sets the threshold that low - if digits == "1" || digits == "2" || digits == "4" || digits == "8" || digits == "3" || digits == "7" + if digits == "1" + || digits == "2" + || digits == "4" + || digits == "8" + || digits == "3" + || digits == "7" || digits == "F" { return Err(WarningType::DecimalRepresentation); @@ -538,6 +557,7 @@ impl LiteralRepresentation { // Lint for Literals with a hex-representation of 2 or 3 digits let f = &digits[0..1]; // first digit let s = &digits[1..]; // suffix + // Powers of 2 if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0')) // Powers of 2 minus 1 @@ -550,6 +570,7 @@ impl LiteralRepresentation { let f = &digits[0..1]; // first digit let m = &digits[1..digits.len() - 1]; // middle digits, except last let s = &digits[1..]; // suffix + // Powers of 2 with a margin of +15/-16 if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0')) || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F')) @@ -570,6 +591,17 @@ fn is_mistyped_suffix(suffix: &str) -> bool { } fn is_possible_suffix_index(lit: &str, idx: usize, len: usize) -> bool { - ((len > 3 && idx == len - 3) || (len > 2 && idx == len - 2)) && - is_mistyped_suffix(lit.split_at(idx).1) + ((len > 3 && idx == len - 3) || (len > 2 && idx == len - 2)) && is_mistyped_suffix(lit.split_at(idx).1) +} + +fn is_mistyped_float_suffix(suffix: &str) -> bool { + ["_32", "_64"].contains(&suffix) +} + +fn is_possible_float_suffix_index(lit: &str, idx: usize, len: usize) -> bool { + (len > 3 && idx == len - 3) && is_mistyped_float_suffix(lit.split_at(idx).1) +} + +fn has_possible_float_suffix(lit: &str) -> bool { + lit.ends_with("_32") || lit.ends_with("_64") } diff --git a/tests/ui/literals.rs b/tests/ui/literals.rs index 4db7ce957120..c08c4b693b80 100644 --- a/tests/ui/literals.rs +++ b/tests/ui/literals.rs @@ -7,9 +7,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. - - - #![warn(clippy::mixed_case_hex_literals)] #![warn(clippy::unseparated_literal_suffix)] #![warn(clippy::zero_prefixed_literal)] @@ -64,4 +61,11 @@ fn main() { let fail21 = 4___16; let fail22 = 3__4___23; let fail23 = 3__16___23; + + let fail24 = 12.34_64; + let fail25 = 1E2_32; + let fail26 = 43E7_64; + let fail27 = 243E17_32; + let fail28 = 241251235E723_64; + let fail29 = 42279.911_32; } diff --git a/tests/ui/literals.stderr b/tests/ui/literals.stderr index 4e26b9dd3216..d2a50e2ded52 100644 --- a/tests/ui/literals.stderr +++ b/tests/ui/literals.stderr @@ -1,182 +1,218 @@ error: inconsistent casing in hexadecimal literal - --> $DIR/literals.rs:24:17 + --> $DIR/literals.rs:21:17 | -24 | let fail1 = 0xabCD; +21 | let fail1 = 0xabCD; | ^^^^^^ | = note: `-D clippy::mixed-case-hex-literals` implied by `-D warnings` error: inconsistent casing in hexadecimal literal - --> $DIR/literals.rs:25:17 + --> $DIR/literals.rs:22:17 | -25 | let fail2 = 0xabCD_u32; +22 | let fail2 = 0xabCD_u32; | ^^^^^^^^^^ error: inconsistent casing in hexadecimal literal - --> $DIR/literals.rs:26:17 + --> $DIR/literals.rs:23:17 | -26 | let fail2 = 0xabCD_isize; +23 | let fail2 = 0xabCD_isize; | ^^^^^^^^^^^^ error: integer type suffix should be separated by an underscore - --> $DIR/literals.rs:27:27 + --> $DIR/literals.rs:24:27 | -27 | let fail_multi_zero = 000_123usize; +24 | let fail_multi_zero = 000_123usize; | ^^^^^^^^^^^^ | = note: `-D clippy::unseparated-literal-suffix` implied by `-D warnings` error: this is a decimal constant - --> $DIR/literals.rs:27:27 + --> $DIR/literals.rs:24:27 | -27 | let fail_multi_zero = 000_123usize; +24 | let fail_multi_zero = 000_123usize; | ^^^^^^^^^^^^ | = note: `-D clippy::zero-prefixed-literal` implied by `-D warnings` help: if you mean to use a decimal constant, remove the `0` to remove confusion | -27 | let fail_multi_zero = 123usize; +24 | let fail_multi_zero = 123usize; | ^^^^^^^^ help: if you mean to use an octal constant, use `0o` | -27 | let fail_multi_zero = 0o123usize; +24 | let fail_multi_zero = 0o123usize; | ^^^^^^^^^^ error: integer type suffix should be separated by an underscore - --> $DIR/literals.rs:32:17 + --> $DIR/literals.rs:29:17 | -32 | let fail3 = 1234i32; +29 | let fail3 = 1234i32; | ^^^^^^^ error: integer type suffix should be separated by an underscore - --> $DIR/literals.rs:33:17 + --> $DIR/literals.rs:30:17 | -33 | let fail4 = 1234u32; +30 | let fail4 = 1234u32; | ^^^^^^^ error: integer type suffix should be separated by an underscore - --> $DIR/literals.rs:34:17 + --> $DIR/literals.rs:31:17 | -34 | let fail5 = 1234isize; +31 | let fail5 = 1234isize; | ^^^^^^^^^ error: integer type suffix should be separated by an underscore - --> $DIR/literals.rs:35:17 + --> $DIR/literals.rs:32:17 | -35 | let fail6 = 1234usize; +32 | let fail6 = 1234usize; | ^^^^^^^^^ error: float type suffix should be separated by an underscore - --> $DIR/literals.rs:36:17 + --> $DIR/literals.rs:33:17 | -36 | let fail7 = 1.5f32; +33 | let fail7 = 1.5f32; | ^^^^^^ error: this is a decimal constant - --> $DIR/literals.rs:40:17 + --> $DIR/literals.rs:37:17 | -40 | let fail8 = 0123; +37 | let fail8 = 0123; | ^^^^ help: if you mean to use a decimal constant, remove the `0` to remove confusion | -40 | let fail8 = 123; +37 | let fail8 = 123; | ^^^ help: if you mean to use an octal constant, use `0o` | -40 | let fail8 = 0o123; +37 | let fail8 = 0o123; | ^^^^^ error: long literal lacking separators - --> $DIR/literals.rs:51:17 + --> $DIR/literals.rs:48:17 | -51 | let fail9 = 0xabcdef; +48 | let fail9 = 0xabcdef; | ^^^^^^^^ help: consider: `0x00ab_cdef` | = note: `-D clippy::unreadable-literal` implied by `-D warnings` error: long literal lacking separators - --> $DIR/literals.rs:52:18 + --> $DIR/literals.rs:49:18 | -52 | let fail10 = 0xBAFEBAFE; +49 | let fail10 = 0xBAFEBAFE; | ^^^^^^^^^^ help: consider: `0xBAFE_BAFE` error: long literal lacking separators - --> $DIR/literals.rs:53:18 + --> $DIR/literals.rs:50:18 | -53 | let fail11 = 0xabcdeff; +50 | let fail11 = 0xabcdeff; | ^^^^^^^^^ help: consider: `0x0abc_deff` error: long literal lacking separators - --> $DIR/literals.rs:54:18 + --> $DIR/literals.rs:51:18 | -54 | let fail12 = 0xabcabcabcabcabcabc; +51 | let fail12 = 0xabcabcabcabcabcabc; | ^^^^^^^^^^^^^^^^^^^^ help: consider: `0x00ab_cabc_abca_bcab_cabc` error: digit groups should be smaller - --> $DIR/literals.rs:55:18 + --> $DIR/literals.rs:52:18 | -55 | let fail13 = 0x1_23456_78901_usize; +52 | let fail13 = 0x1_23456_78901_usize; | ^^^^^^^^^^^^^^^^^^^^^ help: consider: `0x0123_4567_8901_usize` | = note: `-D clippy::large-digit-groups` implied by `-D warnings` error: mistyped literal suffix - --> $DIR/literals.rs:57:18 + --> $DIR/literals.rs:54:18 | -57 | let fail14 = 2_32; +54 | let fail14 = 2_32; | ^^^^ help: did you mean to write: `2_i32` | = note: #[deny(clippy::mistyped_literal_suffixes)] on by default error: mistyped literal suffix - --> $DIR/literals.rs:58:18 + --> $DIR/literals.rs:55:18 | -58 | let fail15 = 4_64; +55 | let fail15 = 4_64; | ^^^^ help: did you mean to write: `4_i64` error: mistyped literal suffix - --> $DIR/literals.rs:59:18 + --> $DIR/literals.rs:56:18 | -59 | let fail16 = 7_8; +56 | let fail16 = 7_8; | ^^^ help: did you mean to write: `7_i8` error: mistyped literal suffix - --> $DIR/literals.rs:60:18 + --> $DIR/literals.rs:57:18 | -60 | let fail17 = 23_16; +57 | let fail17 = 23_16; | ^^^^^ help: did you mean to write: `23_i16` error: digits grouped inconsistently by underscores - --> $DIR/literals.rs:62:18 + --> $DIR/literals.rs:59:18 | -62 | let fail19 = 12_3456_21; +59 | let fail19 = 12_3456_21; | ^^^^^^^^^^ help: consider: `12_345_621` | = note: `-D clippy::inconsistent-digit-grouping` implied by `-D warnings` error: mistyped literal suffix - --> $DIR/literals.rs:63:18 + --> $DIR/literals.rs:60:18 | -63 | let fail20 = 2__8; +60 | let fail20 = 2__8; | ^^^^ help: did you mean to write: `2_i8` error: mistyped literal suffix - --> $DIR/literals.rs:64:18 + --> $DIR/literals.rs:61:18 | -64 | let fail21 = 4___16; +61 | let fail21 = 4___16; | ^^^^^^ help: did you mean to write: `4_i16` error: digits grouped inconsistently by underscores - --> $DIR/literals.rs:65:18 + --> $DIR/literals.rs:62:18 | -65 | let fail22 = 3__4___23; +62 | let fail22 = 3__4___23; | ^^^^^^^^^ help: consider: `3_423` error: digits grouped inconsistently by underscores - --> $DIR/literals.rs:66:18 + --> $DIR/literals.rs:63:18 | -66 | let fail23 = 3__16___23; +63 | let fail23 = 3__16___23; | ^^^^^^^^^^ help: consider: `31_623` -error: aborting due to 25 previous errors +error: mistyped literal suffix + --> $DIR/literals.rs:65:18 + | +65 | let fail24 = 12.34_64; + | ^^^^^^^^ help: did you mean to write: `12.34_f64` + +error: mistyped literal suffix + --> $DIR/literals.rs:66:18 + | +66 | let fail25 = 1E2_32; + | ^^^^^^ help: did you mean to write: `1E2_f32` + +error: mistyped literal suffix + --> $DIR/literals.rs:67:18 + | +67 | let fail26 = 43E7_64; + | ^^^^^^^ help: did you mean to write: `43E7_f64` + +error: mistyped literal suffix + --> $DIR/literals.rs:68:18 + | +68 | let fail27 = 243E17_32; + | ^^^^^^^^^ help: did you mean to write: `243E17_f32` + +error: mistyped literal suffix + --> $DIR/literals.rs:69:18 + | +69 | let fail28 = 241251235E723_64; + | ^^^^^^^^^^^^^^^^ help: did you mean to write: `241_251_235E723_f64` + +error: mistyped literal suffix + --> $DIR/literals.rs:70:18 + | +70 | let fail29 = 42279.911_32; + | ^^^^^^^^^^^^ help: did you mean to write: `42_279.911_f32` + +error: aborting due to 31 previous errors