]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/literal_representation.rs
Merge branch 'macro-use' into HEAD
[rust.git] / clippy_lints / src / literal_representation.rs
index 91f01bae257ddf426c47f91e5efbfcdfcb3b813e..bb5a923eaf9ca9ed2dd78ee4073092538fcb9a7d 100644 (file)
@@ -2,9 +2,11 @@
 //! floating-point literal expressions.
 
 use rustc::lint::*;
+use rustc::{declare_lint, lint_array};
+use if_chain::if_chain;
 use syntax::ast::*;
 use syntax_pos;
-use utils::{in_external_macro, snippet_opt, span_help_and_lint};
+use crate::utils::{in_external_macro, snippet_opt, span_lint_and_sugg};
 
 /// **What it does:** Warns if a long integral or floating-point constant does
 /// not contain underscores.
@@ -18,9 +20,9 @@
 /// ```rust
 /// 61864918973511
 /// ```
-declare_lint! {
+declare_clippy_lint! {
     pub UNREADABLE_LITERAL,
-    Warn,
+    style,
     "long integer literal without underscores"
 }
 
@@ -37,9 +39,9 @@
 /// ```rust
 /// 618_64_9189_73_511
 /// ```
-declare_lint! {
+declare_clippy_lint! {
     pub INCONSISTENT_DIGIT_GROUPING,
-    Warn,
+    style,
     "integer literals with digits grouped inconsistently"
 }
 
@@ -56,9 +58,9 @@
 /// ```rust
 /// 6186491_8973511
 /// ```
-declare_lint! {
+declare_clippy_lint! {
     pub LARGE_DIGIT_GROUPS,
-    Warn,
+    style,
     "grouping digits into groups that are too large"
 }
 
 /// `255` => `0xFF`
 /// `65_535` => `0xFFFF`
 /// `4_042_322_160` => `0xF0F0_F0F0`
-declare_lint! {
-    pub BAD_LITERAL_REPRESENTATION,
-    Warn,
+declare_clippy_lint! {
+    pub DECIMAL_LITERAL_REPRESENTATION,
+    restriction,
     "using decimal representation when hexadecimal would be better"
 }
 
 #[derive(Debug, PartialEq)]
-enum Radix {
+pub(super) enum Radix {
     Binary,
     Octal,
     Decimal,
@@ -90,7 +92,7 @@ enum Radix {
 
 impl Radix {
     /// Return a reasonable digit group size for this radix.
-    pub fn suggest_grouping(&self) -> usize {
+    crate fn suggest_grouping(&self) -> usize {
         match *self {
             Radix::Binary | Radix::Hexadecimal => 4,
             Radix::Octal | Radix::Decimal => 3,
@@ -99,21 +101,21 @@ pub fn suggest_grouping(&self) -> usize {
 }
 
 #[derive(Debug)]
-struct DigitInfo<'a> {
+pub(super) struct DigitInfo<'a> {
     /// Characters of a literal between the radix prefix and type suffix.
-    pub digits: &'a str,
+    crate digits: &'a str,
     /// Which radix the literal was represented in.
-    pub radix: Radix,
+    crate radix: Radix,
     /// The radix prefix, if present.
-    pub prefix: Option<&'a str>,
+    crate prefix: Option<&'a str>,
     /// The type suffix, including preceding underscore if present.
-    pub suffix: Option<&'a str>,
+    crate suffix: Option<&'a str>,
     /// True for floating-point literals.
-    pub float: bool,
+    crate float: bool,
 }
 
 impl<'a> DigitInfo<'a> {
-    pub fn new(lit: &'a str, float: bool) -> Self {
+    crate fn new(lit: &'a str, float: bool) -> Self {
         // Determine delimiter for radix prefix, if present, and radix.
         let radix = if lit.starts_with("0x") {
             Radix::Hexadecimal
@@ -135,15 +137,15 @@ pub fn new(lit: &'a str, float: bool) -> Self {
 
         let mut last_d = '\0';
         for (d_idx, d) in sans_prefix.char_indices() {
-            if !float && (d == 'i' || d == 'u') || float && d == 'f' {
+            if !float && (d == 'i' || d == 'u') || float && (d == 'f' || d == 'e' || d == 'E') {
                 let suffix_start = if last_d == '_' { d_idx - 1 } else { d_idx };
                 let (digits, suffix) = sans_prefix.split_at(suffix_start);
                 return Self {
-                    digits: digits,
-                    radix: radix,
-                    prefix: prefix,
+                    digits,
+                    radix,
+                    prefix,
                     suffix: Some(suffix),
-                    float: float,
+                    float,
                 };
             }
             last_d = d
@@ -152,15 +154,15 @@ pub fn new(lit: &'a str, float: bool) -> Self {
         // No suffix found
         Self {
             digits: sans_prefix,
-            radix: radix,
-            prefix: prefix,
+            radix,
+            prefix,
             suffix: None,
-            float: float,
+            float,
         }
     }
 
     /// Returns digits grouped in a sensible way.
-    fn grouping_hint(&self) -> String {
+    crate fn grouping_hint(&self) -> String {
         let group_size = self.radix.suggest_grouping();
         if self.digits.contains('.') {
             let mut parts = self.digits.split('.');
@@ -193,16 +195,22 @@ fn grouping_hint(&self) -> String {
                 self.suffix.unwrap_or("")
             )
         } else {
-            let hint = self.digits
+            let filtered_digits_vec = self.digits
                 .chars()
-                .rev()
                 .filter(|&c| c != '_')
-                .collect::<Vec<_>>()
+                .rev()
+                .collect::<Vec<_>>();
+            let mut hint = filtered_digits_vec
                 .chunks(group_size)
                 .map(|chunk| chunk.into_iter().rev().collect())
                 .rev()
                 .collect::<Vec<String>>()
                 .join("_");
+            // Forces hexadecimal values to be grouped by 4 being filled with zeroes (e.g 0x00ab_cdef)
+            let nb_digits_to_fill = filtered_digits_vec.len() % 4;
+            if self.radix == Radix::Hexadecimal && nb_digits_to_fill != 0 {
+                hint = format!("{:0>4}{}", &hint[..nb_digits_to_fill], &hint[nb_digits_to_fill..]);
+            }
             format!(
                 "{}{}{}",
                 self.prefix.unwrap_or(""),
@@ -217,39 +225,43 @@ enum WarningType {
     UnreadableLiteral,
     InconsistentDigitGrouping,
     LargeDigitGroups,
-    BadRepresentation,
+    DecimalRepresentation,
 }
 
 impl WarningType {
-    pub fn display(&self, grouping_hint: &str, cx: &EarlyContext, span: &syntax_pos::Span) {
-        match *self {
-            WarningType::UnreadableLiteral => span_help_and_lint(
+    crate fn display(&self, grouping_hint: &str, cx: &EarlyContext, span: syntax_pos::Span) {
+        match self {
+            WarningType::UnreadableLiteral => span_lint_and_sugg(
                 cx,
                 UNREADABLE_LITERAL,
-                *span,
+                span,
                 "long literal lacking separators",
-                &format!("consider: {}", grouping_hint),
+                "consider",
+                grouping_hint.to_owned(),
             ),
-            WarningType::LargeDigitGroups => span_help_and_lint(
+            WarningType::LargeDigitGroups => span_lint_and_sugg(
                 cx,
                 LARGE_DIGIT_GROUPS,
-                *span,
+                span,
                 "digit groups should be smaller",
-                &format!("consider: {}", grouping_hint),
+                "consider",
+                grouping_hint.to_owned(),
             ),
-            WarningType::InconsistentDigitGrouping => span_help_and_lint(
+            WarningType::InconsistentDigitGrouping => span_lint_and_sugg(
                 cx,
                 INCONSISTENT_DIGIT_GROUPING,
-                *span,
+                span,
                 "digits grouped inconsistently by underscores",
-                &format!("consider: {}", grouping_hint),
+                "consider",
+                grouping_hint.to_owned(),
             ),
-            WarningType::BadRepresentation => span_help_and_lint(
+            WarningType::DecimalRepresentation => span_lint_and_sugg(
                 cx,
-                BAD_LITERAL_REPRESENTATION,
-                *span,
-                "bad representation of integer literal",
-                &format!("consider: {}", grouping_hint),
+                DECIMAL_LITERAL_REPRESENTATION,
+                span,
+                "integer literal has a better hexadecimal representation",
+                "consider",
+                grouping_hint.to_owned(),
             ),
         };
     }
@@ -281,61 +293,65 @@ fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
 }
 
 impl LiteralDigitGrouping {
-    fn check_lit(&self, cx: &EarlyContext, lit: &Lit) {
-        // Lint integral literals.
-        if_chain! {
-            if let LitKind::Int(..) = lit.node;
-            if let Some(src) = snippet_opt(cx, lit.span);
-            if let Some(firstch) = src.chars().next();
-            if char::to_digit(firstch, 10).is_some();
-            then {
-                let digit_info = DigitInfo::new(&src, false);
-                let _ = Self::do_lint(digit_info.digits).map_err(|warning_type| {
-                    warning_type.display(&digit_info.grouping_hint(), cx, &lit.span)
-                });
-            }
-        }
-
-        // Lint floating-point literals.
-        if_chain! {
-            if let LitKind::Float(..) = lit.node;
-            if let Some(src) = snippet_opt(cx, lit.span);
-            if let Some(firstch) = src.chars().next();
-            if char::to_digit(firstch, 10).is_some();
-            then {
-                let digit_info = DigitInfo::new(&src, true);
-                // Separate digits into integral and fractional parts.
-                let parts: Vec<&str> = digit_info
-                    .digits
-                    .split_terminator('.')
-                    .collect();
-
-                // Lint integral and fractional parts separately, and then check consistency of digit
-                // groups if both pass.
-                let _ = Self::do_lint(parts[0])
-                    .map(|integral_group_size| {
-                        if parts.len() > 1 {
-                            // Lint the fractional part of literal just like integral part, but reversed.
-                            let fractional_part = &parts[1].chars().rev().collect::<String>();
-                            let _ = Self::do_lint(fractional_part)
-                                .map(|fractional_group_size| {
-                                    let consistent = Self::parts_consistent(integral_group_size,
-                                                                            fractional_group_size,
-                                                                            parts[0].len(),
-                                                                            parts[1].len());
-                                    if !consistent {
-                                        WarningType::InconsistentDigitGrouping.display(&digit_info.grouping_hint(),
-                                                                                       cx,
-                                                                                       &lit.span);
-                                    }
-                                })
-                                .map_err(|warning_type| warning_type.display(&digit_info.grouping_hint(),
-                                                                             cx,
-                                                                             &lit.span));
-                        }
-                    })
-                    .map_err(|warning_type| warning_type.display(&digit_info.grouping_hint(), cx, &lit.span));
-            }
+    fn check_lit(self, cx: &EarlyContext, lit: &Lit) {
+        match lit.node {
+            LitKind::Int(..) => {
+                // Lint integral literals.
+                if_chain! {
+                    if let Some(src) = snippet_opt(cx, lit.span);
+                    if let Some(firstch) = src.chars().next();
+                    if char::to_digit(firstch, 10).is_some();
+                    then {
+                        let digit_info = DigitInfo::new(&src, false);
+                        let _ = Self::do_lint(digit_info.digits).map_err(|warning_type| {
+                            warning_type.display(&digit_info.grouping_hint(), cx, lit.span)
+                        });
+                    }
+                }
+            },
+            LitKind::Float(..) | LitKind::FloatUnsuffixed(..) => {
+                // Lint floating-point literals.
+                if_chain! {
+                    if let Some(src) = snippet_opt(cx, lit.span);
+                    if let Some(firstch) = src.chars().next();
+                    if char::to_digit(firstch, 10).is_some();
+                    then {
+                        let digit_info = DigitInfo::new(&src, true);
+                        // Separate digits into integral and fractional parts.
+                        let parts: Vec<&str> = digit_info
+                            .digits
+                            .split_terminator('.')
+                            .collect();
+
+                        // Lint integral and fractional parts separately, and then check consistency of digit
+                        // groups if both pass.
+                        let _ = Self::do_lint(parts[0])
+                            .map(|integral_group_size| {
+                                if parts.len() > 1 {
+                                    // Lint the fractional part of literal just like integral part, but reversed.
+                                    let fractional_part = &parts[1].chars().rev().collect::<String>();
+                                    let _ = Self::do_lint(fractional_part)
+                                        .map(|fractional_group_size| {
+                                            let consistent = Self::parts_consistent(integral_group_size,
+                                                                                    fractional_group_size,
+                                                                                    parts[0].len(),
+                                                                                    parts[1].len());
+                                            if !consistent {
+                                                WarningType::InconsistentDigitGrouping.display(&digit_info.grouping_hint(),
+                                                cx,
+                                                lit.span);
+                                            }
+                                        })
+                                    .map_err(|warning_type| warning_type.display(&digit_info.grouping_hint(),
+                                    cx,
+                                    lit.span));
+                                }
+                            })
+                        .map_err(|warning_type| warning_type.display(&digit_info.grouping_hint(), cx, lit.span));
+                    }
+                }
+            },
+            _ => (),
         }
     }
 
@@ -368,7 +384,7 @@ fn do_lint(digits: &str) -> Result<usize, WarningType> {
 
         if underscore_positions.is_empty() {
             // Check if literal needs underscores.
-            if digits.len() > 4 {
+            if digits.len() > 5 {
                 Err(WarningType::UnreadableLiteral)
             } else {
                 Ok(0)
@@ -394,11 +410,13 @@ fn do_lint(digits: &str) -> Result<usize, WarningType> {
 }
 
 #[derive(Copy, Clone)]
-pub struct LiteralRepresentation;
+pub struct LiteralRepresentation {
+    threshold: u64,
+}
 
 impl LintPass for LiteralRepresentation {
     fn get_lints(&self) -> LintArray {
-        lint_array!(BAD_LITERAL_REPRESENTATION)
+        lint_array!(DECIMAL_LITERAL_REPRESENTATION)
     }
 }
 
@@ -415,7 +433,12 @@ fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
 }
 
 impl LiteralRepresentation {
-    fn check_lit(&self, cx: &EarlyContext, lit: &Lit) {
+    pub fn new(threshold: u64) -> Self {
+        Self {
+            threshold,
+        }
+    }
+    fn check_lit(self, cx: &EarlyContext, lit: &Lit) {
         // Lint integral literals.
         if_chain! {
             if let LitKind::Int(..) = lit.node;
@@ -425,14 +448,18 @@ fn check_lit(&self, cx: &EarlyContext, lit: &Lit) {
             then {
                 let digit_info = DigitInfo::new(&src, false);
                 if digit_info.radix == Radix::Decimal {
-                    let hex = format!("{:#X}", digit_info.digits
-                                                            .chars()
-                                                            .filter(|&c| c != '_')
-                                                            .collect::<String>()
-                                                            .parse::<u128>().unwrap());
+                    let val = digit_info.digits
+                        .chars()
+                        .filter(|&c| c != '_')
+                        .collect::<String>()
+                        .parse::<u128>().unwrap();
+                    if val < u128::from(self.threshold) {
+                        return
+                    }
+                    let hex = format!("{:#X}", val);
                     let digit_info = DigitInfo::new(&hex[..], false);
                     let _ = Self::do_lint(digit_info.digits).map_err(|warning_type| {
-                        warning_type.display(&digit_info.grouping_hint(), cx, &lit.span)
+                        warning_type.display(&digit_info.grouping_hint(), cx, lit.span)
                     });
                 }
             }
@@ -440,29 +467,37 @@ fn check_lit(&self, cx: &EarlyContext, lit: &Lit) {
     }
 
     fn do_lint(digits: &str) -> Result<(), WarningType> {
-        if digits.len() == 2 && digits == "FF" {
-            return Err(WarningType::BadRepresentation);
-        } else if digits.len() == 3 {
-            // Lint for Literals with a hex-representation of 3 digits
+        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"
+                || digits == "F"
+            {
+                return Err(WarningType::DecimalRepresentation);
+            }
+        } else if digits.len() < 4 {
+            // 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 minus 1
-            if (f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.eq("FF") {
-                return Err(WarningType::BadRepresentation);
+            // 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
+                || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
+            {
+                return Err(WarningType::DecimalRepresentation);
             }
-        } else if digits.len() > 3 {
+        } else {
             // Lint for Literals with a hex-representation of 4 digits or more
             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
+            // 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'))
                 // Lint for representations with only 0s and Fs, while allowing 7 as the first
                 // digit
                 || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
             {
-                return Err(WarningType::BadRepresentation);
+                return Err(WarningType::DecimalRepresentation);
             }
         }