//! 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.
/// ```rust
/// 61864918973511
/// ```
-declare_lint! {
+declare_clippy_lint! {
pub UNREADABLE_LITERAL,
- Warn,
+ style,
"long integer literal without underscores"
}
/// ```rust
/// 618_64_9189_73_511
/// ```
-declare_lint! {
+declare_clippy_lint! {
pub INCONSISTENT_DIGIT_GROUPING,
- Warn,
+ style,
"integer literals with digits grouped inconsistently"
}
/// ```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,
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,
}
#[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
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
// 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('.');
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(""),
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(),
),
};
}
}
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));
+ }
+ }
+ },
+ _ => (),
}
}
if underscore_positions.is_empty() {
// Check if literal needs underscores.
- if digits.len() > 4 {
+ if digits.len() > 5 {
Err(WarningType::UnreadableLiteral)
} else {
Ok(0)
}
#[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)
}
}
}
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;
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)
});
}
}
}
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);
}
}