]> git.lizzy.rs Git - rust.git/commitdiff
Lint for numeric literals that have a better representation in another format
authorflip1995 <uwdkn@student.kit.edu>
Tue, 16 Jan 2018 13:01:07 +0000 (14:01 +0100)
committerflip1995 <uwdkn@student.kit.edu>
Tue, 16 Jan 2018 13:01:07 +0000 (14:01 +0100)
clippy_lints/src/lib.rs
clippy_lints/src/literal_digit_grouping.rs [deleted file]
clippy_lints/src/literal_representation.rs [new file with mode: 0644]
tests/ui/bad_literal_representation.rs [new file with mode: 0644]
tests/ui/bad_literal_representation.stderr [new file with mode: 0644]
tests/ui/drop_forget_copy.rs
tests/ui/identity_op.rs
tests/ui/identity_op.stderr

index 13760e8c0fb31834d32e70a11fa91cdd5d8cdcef..f893a3d45e3fe938410815a8b0a4bb5a0096413b 100644 (file)
@@ -116,7 +116,7 @@ macro_rules! declare_restriction_lint {
 pub mod len_zero;
 pub mod let_if_seq;
 pub mod lifetimes;
-pub mod literal_digit_grouping;
+pub mod literal_representation;
 pub mod loops;
 pub mod map_clone;
 pub mod matches;
@@ -353,7 +353,8 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
     reg.register_late_lint_pass(box large_enum_variant::LargeEnumVariant::new(conf.enum_variant_size_threshold));
     reg.register_late_lint_pass(box explicit_write::Pass);
     reg.register_late_lint_pass(box needless_pass_by_value::NeedlessPassByValue);
-    reg.register_early_lint_pass(box literal_digit_grouping::LiteralDigitGrouping);
+    reg.register_early_lint_pass(box literal_representation::LiteralDigitGrouping);
+    reg.register_early_lint_pass(box literal_representation::LiteralRepresentation);
     reg.register_late_lint_pass(box use_self::UseSelf);
     reg.register_late_lint_pass(box bytecount::ByteCount);
     reg.register_late_lint_pass(box infinite_iter::Pass);
@@ -482,9 +483,10 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
         let_if_seq::USELESS_LET_IF_SEQ,
         lifetimes::NEEDLESS_LIFETIMES,
         lifetimes::UNUSED_LIFETIMES,
-        literal_digit_grouping::INCONSISTENT_DIGIT_GROUPING,
-        literal_digit_grouping::LARGE_DIGIT_GROUPS,
-        literal_digit_grouping::UNREADABLE_LITERAL,
+        literal_representation::INCONSISTENT_DIGIT_GROUPING,
+        literal_representation::LARGE_DIGIT_GROUPS,
+        literal_representation::UNREADABLE_LITERAL,
+        literal_representation::BAD_LITERAL_REPRESENTATION,
         loops::EMPTY_LOOP,
         loops::EXPLICIT_COUNTER_LOOP,
         loops::EXPLICIT_INTO_ITER_LOOP,
diff --git a/clippy_lints/src/literal_digit_grouping.rs b/clippy_lints/src/literal_digit_grouping.rs
deleted file mode 100644 (file)
index 011b5ec..0000000
+++ /dev/null
@@ -1,355 +0,0 @@
-//! Lints concerned with the grouping of digits with underscores in integral or
-//! floating-point literal expressions.
-
-use rustc::lint::*;
-use syntax::ast::*;
-use syntax_pos;
-use utils::{in_external_macro, snippet_opt, span_help_and_lint};
-
-/// **What it does:** Warns if a long integral or floating-point constant does
-/// not contain underscores.
-///
-/// **Why is this bad?** Reading long numbers is difficult without separators.
-///
-/// **Known problems:** None.
-///
-/// **Example:**
-///
-/// ```rust
-/// 61864918973511
-/// ```
-declare_lint! {
-    pub UNREADABLE_LITERAL,
-    Warn,
-    "long integer literal without underscores"
-}
-
-/// **What it does:** Warns if an integral or floating-point constant is
-/// grouped inconsistently with underscores.
-///
-/// **Why is this bad?** Readers may incorrectly interpret inconsistently
-/// grouped digits.
-///
-/// **Known problems:** None.
-///
-/// **Example:**
-///
-/// ```rust
-/// 618_64_9189_73_511
-/// ```
-declare_lint! {
-    pub INCONSISTENT_DIGIT_GROUPING,
-    Warn,
-    "integer literals with digits grouped inconsistently"
-}
-
-/// **What it does:** Warns if the digits of an integral or floating-point
-/// constant are grouped into groups that
-/// are too large.
-///
-/// **Why is this bad?** Negatively impacts readability.
-///
-/// **Known problems:** None.
-///
-/// **Example:**
-///
-/// ```rust
-/// 6186491_8973511
-/// ```
-declare_lint! {
-    pub LARGE_DIGIT_GROUPS,
-    Warn,
-    "grouping digits into groups that are too large"
-}
-
-#[derive(Debug)]
-enum Radix {
-    Binary,
-    Octal,
-    Decimal,
-    Hexadecimal,
-}
-
-impl Radix {
-    /// Return a reasonable digit group size for this radix.
-    pub fn suggest_grouping(&self) -> usize {
-        match *self {
-            Radix::Binary | Radix::Hexadecimal => 4,
-            Radix::Octal | Radix::Decimal => 3,
-        }
-    }
-}
-
-#[derive(Debug)]
-struct DigitInfo<'a> {
-    /// Characters of a literal between the radix prefix and type suffix.
-    pub digits: &'a str,
-    /// Which radix the literal was represented in.
-    pub radix: Radix,
-    /// The radix prefix, if present.
-    pub prefix: Option<&'a str>,
-    /// The type suffix, including preceding underscore if present.
-    pub suffix: Option<&'a str>,
-    /// True for floating-point literals.
-    pub float: bool,
-}
-
-impl<'a> DigitInfo<'a> {
-    pub 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
-        } else if lit.starts_with("0b") {
-            Radix::Binary
-        } else if lit.starts_with("0o") {
-            Radix::Octal
-        } else {
-            Radix::Decimal
-        };
-
-        // Grab part of the literal after prefix, if present.
-        let (prefix, sans_prefix) = if let Radix::Decimal = radix {
-            (None, lit)
-        } else {
-            let (p, s) = lit.split_at(2);
-            (Some(p), s)
-        };
-
-        let mut last_d = '\0';
-        for (d_idx, d) in sans_prefix.char_indices() {
-            if !float && (d == 'i' || d == 'u') || float && d == 'f' {
-                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,
-                    suffix: Some(suffix),
-                    float: float,
-                };
-            }
-            last_d = d
-        }
-
-        // No suffix found
-        Self {
-            digits: sans_prefix,
-            radix: radix,
-            prefix: prefix,
-            suffix: None,
-            float: float,
-        }
-    }
-
-    /// Returns digits grouped in a sensible way.
-    fn grouping_hint(&self) -> String {
-        let group_size = self.radix.suggest_grouping();
-        if self.digits.contains('.') {
-            let mut parts = self.digits.split('.');
-            let int_part_hint = parts
-                .next()
-                .expect("split always returns at least one element")
-                .chars()
-                .rev()
-                .filter(|&c| c != '_')
-                .collect::<Vec<_>>()
-                .chunks(group_size)
-                .map(|chunk| chunk.into_iter().rev().collect())
-                .rev()
-                .collect::<Vec<String>>()
-                .join("_");
-            let frac_part_hint = parts
-                .next()
-                .expect("already checked that there is a `.`")
-                .chars()
-                .filter(|&c| c != '_')
-                .collect::<Vec<_>>()
-                .chunks(group_size)
-                .map(|chunk| chunk.into_iter().collect())
-                .collect::<Vec<String>>()
-                .join("_");
-            format!("{}.{}{}", int_part_hint, frac_part_hint, self.suffix.unwrap_or(""))
-        } else {
-            let hint = self.digits
-                .chars()
-                .rev()
-                .filter(|&c| c != '_')
-                .collect::<Vec<_>>()
-                .chunks(group_size)
-                .map(|chunk| chunk.into_iter().rev().collect())
-                .rev()
-                .collect::<Vec<String>>()
-                .join("_");
-            format!("{}{}{}", self.prefix.unwrap_or(""), hint, self.suffix.unwrap_or(""))
-        }
-    }
-}
-
-enum WarningType {
-    UnreadableLiteral,
-    InconsistentDigitGrouping,
-    LargeDigitGroups,
-}
-
-
-impl WarningType {
-    pub fn display(&self, grouping_hint: &str, cx: &EarlyContext, span: &syntax_pos::Span) {
-        match *self {
-            WarningType::UnreadableLiteral => span_help_and_lint(
-                cx,
-                UNREADABLE_LITERAL,
-                *span,
-                "long literal lacking separators",
-                &format!("consider: {}", grouping_hint),
-            ),
-            WarningType::LargeDigitGroups => span_help_and_lint(
-                cx,
-                LARGE_DIGIT_GROUPS,
-                *span,
-                "digit groups should be smaller",
-                &format!("consider: {}", grouping_hint),
-            ),
-            WarningType::InconsistentDigitGrouping => span_help_and_lint(
-                cx,
-                INCONSISTENT_DIGIT_GROUPING,
-                *span,
-                "digits grouped inconsistently by underscores",
-                &format!("consider: {}", grouping_hint),
-            ),
-        };
-    }
-}
-
-#[derive(Copy, Clone)]
-pub struct LiteralDigitGrouping;
-
-impl LintPass for LiteralDigitGrouping {
-    fn get_lints(&self) -> LintArray {
-        lint_array!(UNREADABLE_LITERAL, INCONSISTENT_DIGIT_GROUPING, LARGE_DIGIT_GROUPS)
-    }
-}
-
-impl EarlyLintPass for LiteralDigitGrouping {
-    fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
-        if in_external_macro(cx, expr.span) {
-            return;
-        }
-
-        if let ExprKind::Lit(ref lit) = expr.node {
-            self.check_lit(cx, lit)
-        }
-    }
-}
-
-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));
-            }
-        }
-    }
-
-    /// Given the sizes of the digit groups of both integral and fractional
-    /// parts, and the length
-    /// of both parts, determine if the digits have been grouped consistently.
-    fn parts_consistent(int_group_size: usize, frac_group_size: usize, int_size: usize, frac_size: usize) -> bool {
-        match (int_group_size, frac_group_size) {
-            // No groups on either side of decimal point - trivially consistent.
-            (0, 0) => true,
-            // Integral part has grouped digits, fractional part does not.
-            (_, 0) => frac_size <= int_group_size,
-            // Fractional part has grouped digits, integral part does not.
-            (0, _) => int_size <= frac_group_size,
-            // Both parts have grouped digits. Groups should be the same size.
-            (_, _) => int_group_size == frac_group_size,
-        }
-    }
-
-    /// Performs lint on `digits` (no decimal point) and returns the group
-    /// size on success or `WarningType` when emitting a warning.
-    fn do_lint(digits: &str) -> Result<usize, WarningType> {
-        // Grab underscore indices with respect to the units digit.
-        let underscore_positions: Vec<usize> = digits
-            .chars()
-            .rev()
-            .enumerate()
-            .filter_map(|(idx, digit)| if digit == '_' { Some(idx) } else { None })
-            .collect();
-
-        if underscore_positions.is_empty() {
-            // Check if literal needs underscores.
-            if digits.len() > 4 {
-                Err(WarningType::UnreadableLiteral)
-            } else {
-                Ok(0)
-            }
-        } else {
-            // Check consistency and the sizes of the groups.
-            let group_size = underscore_positions[0];
-            let consistent = underscore_positions
-                .windows(2)
-                .all(|ps| ps[1] - ps[0] == group_size + 1)
-                // number of digits to the left of the last group cannot be bigger than group size.
-                && (digits.len() - underscore_positions.last()
-                                                       .expect("there's at least one element") <= group_size + 1);
-
-            if !consistent {
-                return Err(WarningType::InconsistentDigitGrouping);
-            } else if group_size > 4 {
-                return Err(WarningType::LargeDigitGroups);
-            }
-            Ok(group_size)
-        }
-    }
-}
diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs
new file mode 100644 (file)
index 0000000..91f01ba
--- /dev/null
@@ -0,0 +1,471 @@
+//! Lints concerned with the grouping of digits with underscores in integral or
+//! floating-point literal expressions.
+
+use rustc::lint::*;
+use syntax::ast::*;
+use syntax_pos;
+use utils::{in_external_macro, snippet_opt, span_help_and_lint};
+
+/// **What it does:** Warns if a long integral or floating-point constant does
+/// not contain underscores.
+///
+/// **Why is this bad?** Reading long numbers is difficult without separators.
+///
+/// **Known problems:** None.
+///
+/// **Example:**
+///
+/// ```rust
+/// 61864918973511
+/// ```
+declare_lint! {
+    pub UNREADABLE_LITERAL,
+    Warn,
+    "long integer literal without underscores"
+}
+
+/// **What it does:** Warns if an integral or floating-point constant is
+/// grouped inconsistently with underscores.
+///
+/// **Why is this bad?** Readers may incorrectly interpret inconsistently
+/// grouped digits.
+///
+/// **Known problems:** None.
+///
+/// **Example:**
+///
+/// ```rust
+/// 618_64_9189_73_511
+/// ```
+declare_lint! {
+    pub INCONSISTENT_DIGIT_GROUPING,
+    Warn,
+    "integer literals with digits grouped inconsistently"
+}
+
+/// **What it does:** Warns if the digits of an integral or floating-point
+/// constant are grouped into groups that
+/// are too large.
+///
+/// **Why is this bad?** Negatively impacts readability.
+///
+/// **Known problems:** None.
+///
+/// **Example:**
+///
+/// ```rust
+/// 6186491_8973511
+/// ```
+declare_lint! {
+    pub LARGE_DIGIT_GROUPS,
+    Warn,
+    "grouping digits into groups that are too large"
+}
+
+/// **What it does:** Warns if there is a better representation for a numeric literal.
+///
+/// **Why is this bad?** Especially for big powers of 2 a hexadecimal representation is more
+/// readable than a decimal representation.
+///
+/// **Known problems:** None.
+///
+/// **Example:**
+///
+/// `255` => `0xFF`
+/// `65_535` => `0xFFFF`
+/// `4_042_322_160` => `0xF0F0_F0F0`
+declare_lint! {
+    pub BAD_LITERAL_REPRESENTATION,
+    Warn,
+    "using decimal representation when hexadecimal would be better"
+}
+
+#[derive(Debug, PartialEq)]
+enum Radix {
+    Binary,
+    Octal,
+    Decimal,
+    Hexadecimal,
+}
+
+impl Radix {
+    /// Return a reasonable digit group size for this radix.
+    pub fn suggest_grouping(&self) -> usize {
+        match *self {
+            Radix::Binary | Radix::Hexadecimal => 4,
+            Radix::Octal | Radix::Decimal => 3,
+        }
+    }
+}
+
+#[derive(Debug)]
+struct DigitInfo<'a> {
+    /// Characters of a literal between the radix prefix and type suffix.
+    pub digits: &'a str,
+    /// Which radix the literal was represented in.
+    pub radix: Radix,
+    /// The radix prefix, if present.
+    pub prefix: Option<&'a str>,
+    /// The type suffix, including preceding underscore if present.
+    pub suffix: Option<&'a str>,
+    /// True for floating-point literals.
+    pub float: bool,
+}
+
+impl<'a> DigitInfo<'a> {
+    pub 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
+        } else if lit.starts_with("0b") {
+            Radix::Binary
+        } else if lit.starts_with("0o") {
+            Radix::Octal
+        } else {
+            Radix::Decimal
+        };
+
+        // Grab part of the literal after prefix, if present.
+        let (prefix, sans_prefix) = if let Radix::Decimal = radix {
+            (None, lit)
+        } else {
+            let (p, s) = lit.split_at(2);
+            (Some(p), s)
+        };
+
+        let mut last_d = '\0';
+        for (d_idx, d) in sans_prefix.char_indices() {
+            if !float && (d == 'i' || d == 'u') || float && d == 'f' {
+                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,
+                    suffix: Some(suffix),
+                    float: float,
+                };
+            }
+            last_d = d
+        }
+
+        // No suffix found
+        Self {
+            digits: sans_prefix,
+            radix: radix,
+            prefix: prefix,
+            suffix: None,
+            float: float,
+        }
+    }
+
+    /// Returns digits grouped in a sensible way.
+    fn grouping_hint(&self) -> String {
+        let group_size = self.radix.suggest_grouping();
+        if self.digits.contains('.') {
+            let mut parts = self.digits.split('.');
+            let int_part_hint = parts
+                .next()
+                .expect("split always returns at least one element")
+                .chars()
+                .rev()
+                .filter(|&c| c != '_')
+                .collect::<Vec<_>>()
+                .chunks(group_size)
+                .map(|chunk| chunk.into_iter().rev().collect())
+                .rev()
+                .collect::<Vec<String>>()
+                .join("_");
+            let frac_part_hint = parts
+                .next()
+                .expect("already checked that there is a `.`")
+                .chars()
+                .filter(|&c| c != '_')
+                .collect::<Vec<_>>()
+                .chunks(group_size)
+                .map(|chunk| chunk.into_iter().collect())
+                .collect::<Vec<String>>()
+                .join("_");
+            format!(
+                "{}.{}{}",
+                int_part_hint,
+                frac_part_hint,
+                self.suffix.unwrap_or("")
+            )
+        } else {
+            let hint = self.digits
+                .chars()
+                .rev()
+                .filter(|&c| c != '_')
+                .collect::<Vec<_>>()
+                .chunks(group_size)
+                .map(|chunk| chunk.into_iter().rev().collect())
+                .rev()
+                .collect::<Vec<String>>()
+                .join("_");
+            format!(
+                "{}{}{}",
+                self.prefix.unwrap_or(""),
+                hint,
+                self.suffix.unwrap_or("")
+            )
+        }
+    }
+}
+
+enum WarningType {
+    UnreadableLiteral,
+    InconsistentDigitGrouping,
+    LargeDigitGroups,
+    BadRepresentation,
+}
+
+impl WarningType {
+    pub fn display(&self, grouping_hint: &str, cx: &EarlyContext, span: &syntax_pos::Span) {
+        match *self {
+            WarningType::UnreadableLiteral => span_help_and_lint(
+                cx,
+                UNREADABLE_LITERAL,
+                *span,
+                "long literal lacking separators",
+                &format!("consider: {}", grouping_hint),
+            ),
+            WarningType::LargeDigitGroups => span_help_and_lint(
+                cx,
+                LARGE_DIGIT_GROUPS,
+                *span,
+                "digit groups should be smaller",
+                &format!("consider: {}", grouping_hint),
+            ),
+            WarningType::InconsistentDigitGrouping => span_help_and_lint(
+                cx,
+                INCONSISTENT_DIGIT_GROUPING,
+                *span,
+                "digits grouped inconsistently by underscores",
+                &format!("consider: {}", grouping_hint),
+            ),
+            WarningType::BadRepresentation => span_help_and_lint(
+                cx,
+                BAD_LITERAL_REPRESENTATION,
+                *span,
+                "bad representation of integer literal",
+                &format!("consider: {}", grouping_hint),
+            ),
+        };
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct LiteralDigitGrouping;
+
+impl LintPass for LiteralDigitGrouping {
+    fn get_lints(&self) -> LintArray {
+        lint_array!(
+            UNREADABLE_LITERAL,
+            INCONSISTENT_DIGIT_GROUPING,
+            LARGE_DIGIT_GROUPS
+        )
+    }
+}
+
+impl EarlyLintPass for LiteralDigitGrouping {
+    fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
+        if in_external_macro(cx, expr.span) {
+            return;
+        }
+
+        if let ExprKind::Lit(ref lit) = expr.node {
+            self.check_lit(cx, lit)
+        }
+    }
+}
+
+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));
+            }
+        }
+    }
+
+    /// Given the sizes of the digit groups of both integral and fractional
+    /// parts, and the length
+    /// of both parts, determine if the digits have been grouped consistently.
+    fn parts_consistent(int_group_size: usize, frac_group_size: usize, int_size: usize, frac_size: usize) -> bool {
+        match (int_group_size, frac_group_size) {
+            // No groups on either side of decimal point - trivially consistent.
+            (0, 0) => true,
+            // Integral part has grouped digits, fractional part does not.
+            (_, 0) => frac_size <= int_group_size,
+            // Fractional part has grouped digits, integral part does not.
+            (0, _) => int_size <= frac_group_size,
+            // Both parts have grouped digits. Groups should be the same size.
+            (_, _) => int_group_size == frac_group_size,
+        }
+    }
+
+    /// Performs lint on `digits` (no decimal point) and returns the group
+    /// size on success or `WarningType` when emitting a warning.
+    fn do_lint(digits: &str) -> Result<usize, WarningType> {
+        // Grab underscore indices with respect to the units digit.
+        let underscore_positions: Vec<usize> = digits
+            .chars()
+            .rev()
+            .enumerate()
+            .filter_map(|(idx, digit)| if digit == '_' { Some(idx) } else { None })
+            .collect();
+
+        if underscore_positions.is_empty() {
+            // Check if literal needs underscores.
+            if digits.len() > 4 {
+                Err(WarningType::UnreadableLiteral)
+            } else {
+                Ok(0)
+            }
+        } else {
+            // Check consistency and the sizes of the groups.
+            let group_size = underscore_positions[0];
+            let consistent = underscore_positions
+                .windows(2)
+                .all(|ps| ps[1] - ps[0] == group_size + 1)
+                // number of digits to the left of the last group cannot be bigger than group size.
+                && (digits.len() - underscore_positions.last()
+                                                       .expect("there's at least one element") <= group_size + 1);
+
+            if !consistent {
+                return Err(WarningType::InconsistentDigitGrouping);
+            } else if group_size > 4 {
+                return Err(WarningType::LargeDigitGroups);
+            }
+            Ok(group_size)
+        }
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct LiteralRepresentation;
+
+impl LintPass for LiteralRepresentation {
+    fn get_lints(&self) -> LintArray {
+        lint_array!(BAD_LITERAL_REPRESENTATION)
+    }
+}
+
+impl EarlyLintPass for LiteralRepresentation {
+    fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
+        if in_external_macro(cx, expr.span) {
+            return;
+        }
+
+        if let ExprKind::Lit(ref lit) = expr.node {
+            self.check_lit(cx, lit)
+        }
+    }
+}
+
+impl LiteralRepresentation {
+    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);
+                if digit_info.radix == Radix::Decimal {
+                    let hex = format!("{:#X}", digit_info.digits
+                                                            .chars()
+                                                            .filter(|&c| c != '_')
+                                                            .collect::<String>()
+                                                            .parse::<u128>().unwrap());
+                    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)
+                    });
+                }
+            }
+        }
+    }
+
+    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
+            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);
+            }
+        } else if digits.len() > 3 {
+            // 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
+            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);
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/tests/ui/bad_literal_representation.rs b/tests/ui/bad_literal_representation.rs
new file mode 100644 (file)
index 0000000..0012615
--- /dev/null
@@ -0,0 +1,23 @@
+
+
+
+#[warn(bad_literal_representation)]
+#[allow(unused_variables)]
+fn main() {
+    // Hex:      7F,  80, 100,  800,  FFA,   F0F3,     7F0F_F00D
+    let good = (127, 128, 256, 2048, 4090, 61_683, 2_131_750_925);
+    let bad = (        // Hex:
+        255,           // 0xFF
+        511,           // 0x1FF
+        1023,          // 0x3FF
+        2047,          // 0x7FF
+        4095,          // 0xFFF
+        4096,          // 0x1000
+        16_371,        // 0x3FF3
+        32_773,        // 0x8005
+        65_280,        // 0xFF00
+        2_131_750_927, // 0x7F0F_F00F
+        2_147_483_647, // 0x7FFF_FFFF
+        4_042_322_160, // 0xF0F0_F0F0
+    );
+}
diff --git a/tests/ui/bad_literal_representation.stderr b/tests/ui/bad_literal_representation.stderr
new file mode 100644 (file)
index 0000000..f57956c
--- /dev/null
@@ -0,0 +1,97 @@
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:10:9
+   |
+10 |         255,           // 0xFF
+   |         ^^^
+   |
+   = note: `-D bad-literal-representation` implied by `-D warnings`
+   = help: consider: 0xFF
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:11:9
+   |
+11 |         511,           // 0x1FF
+   |         ^^^
+   |
+   = help: consider: 0x1FF
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:12:9
+   |
+12 |         1023,          // 0x3FF
+   |         ^^^^
+   |
+   = help: consider: 0x3FF
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:13:9
+   |
+13 |         2047,          // 0x7FF
+   |         ^^^^
+   |
+   = help: consider: 0x7FF
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:14:9
+   |
+14 |         4095,          // 0xFFF
+   |         ^^^^
+   |
+   = help: consider: 0xFFF
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:15:9
+   |
+15 |         4096,          // 0x1000
+   |         ^^^^
+   |
+   = help: consider: 0x1000
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:16:9
+   |
+16 |         16_371,        // 0x3FF3
+   |         ^^^^^^
+   |
+   = help: consider: 0x3FF3
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:17:9
+   |
+17 |         32_773,        // 0x8005
+   |         ^^^^^^
+   |
+   = help: consider: 0x8005
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:18:9
+   |
+18 |         65_280,        // 0xFF00
+   |         ^^^^^^
+   |
+   = help: consider: 0xFF00
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:19:9
+   |
+19 |         2_131_750_927, // 0x7F0F_F00F
+   |         ^^^^^^^^^^^^^
+   |
+   = help: consider: 0x7F0F_F00F
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:20:9
+   |
+20 |         2_147_483_647, // 0x7FFF_FFFF
+   |         ^^^^^^^^^^^^^
+   |
+   = help: consider: 0x7FFF_FFFF
+
+error: bad representation of integer literal
+  --> $DIR/bad_literal_representation.rs:21:9
+   |
+21 |         4_042_322_160, // 0xF0F0_F0F0
+   |         ^^^^^^^^^^^^^
+   |
+   = help: consider: 0xF0F0_F0F0
+
index 9fef06b0edeffdf69215084cb4ebc99b0003260a..a4d38d99c95ed22b594b07ef1fadbb0167961671 100644 (file)
@@ -42,7 +42,7 @@ fn main() {
     forget(s4);
     forget(s5);
 
-    let a1 = AnotherStruct {x: 255, y: 0, z: vec![1, 2, 3]};
+    let a1 = AnotherStruct {x: 0xFF, y: 0, z: vec![1, 2, 3]};
     let a2 = &a1;
     let mut a3 = a1.clone();
     let ref a4 = a1;
index 1ed9f974d436c2b4c0f3e863755b137e41a80229..d4bc7df44247bcadccd29c444190288e72698bf7 100644 (file)
@@ -29,5 +29,5 @@ fn main() {
     -1 & x;
 
     let u : u8 = 0;
-    u & 255;
+    u & 0xFF;
 }
index c1ce8d2ec4cd5c616aa620408d67ea3243acb985..b3f7bb713e6959cc1675d8ababfcd2c22d516c2a 100644 (file)
@@ -45,6 +45,6 @@ error: the operation is ineffective. Consider reducing it to `x`
 error: the operation is ineffective. Consider reducing it to `u`
   --> $DIR/identity_op.rs:32:5
    |
-32 |     u & 255;
-   |     ^^^^^^^
+32 |     u & 0xFF;
+   |     ^^^^^^^^