1 //! Lints concerned with the grouping of digits with underscores in integral or
2 //! floating-point literal expressions.
4 use clippy_utils::diagnostics::span_lint_and_sugg;
5 use clippy_utils::source::snippet_opt;
8 numeric_literal::{NumericLiteral, Radix},
10 use if_chain::if_chain;
11 use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
12 use rustc_errors::Applicability;
13 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
14 use rustc_middle::lint::in_external_macro;
15 use rustc_session::{declare_tool_lint, impl_lint_pass};
18 declare_clippy_lint! {
20 /// Warns if a long integral or floating-point constant does
21 /// not contain underscores.
23 /// ### Why is this bad?
24 /// Reading long numbers is difficult without separators.
29 /// let x: u64 = 61864918973511;
32 /// let x: u64 = 61_864_918_973_511;
34 pub UNREADABLE_LITERAL,
36 "long literal without underscores"
39 declare_clippy_lint! {
41 /// Warns for mistyped suffix in literals
43 /// ### Why is this bad?
44 /// This is most probably a typo
46 /// ### Known problems
47 /// - Recommends a signed suffix, even though the number might be too big and an unsigned
48 /// suffix is required
49 /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers
53 /// // Probably mistyped
59 pub MISTYPED_LITERAL_SUFFIXES,
61 "mistyped literal suffix"
64 declare_clippy_lint! {
66 /// Warns if an integral or floating-point constant is
67 /// grouped inconsistently with underscores.
69 /// ### Why is this bad?
70 /// Readers may incorrectly interpret inconsistently
76 /// let x: u64 = 618_64_9189_73_511;
79 /// let x: u64 = 61_864_918_973_511;
81 pub INCONSISTENT_DIGIT_GROUPING,
83 "integer literals with digits grouped inconsistently"
86 declare_clippy_lint! {
88 /// Warns if hexadecimal or binary literals are not grouped
89 /// by nibble or byte.
91 /// ### Why is this bad?
92 /// Negatively impacts readability.
96 /// let x: u32 = 0xFFF_FFF;
97 /// let y: u8 = 0b01_011_101;
99 pub UNUSUAL_BYTE_GROUPINGS,
101 "binary or hex literals that aren't grouped by four"
104 declare_clippy_lint! {
106 /// Warns if the digits of an integral or floating-point
107 /// constant are grouped into groups that
110 /// ### Why is this bad?
111 /// Negatively impacts readability.
115 /// let x: u64 = 6186491_8973511;
117 pub LARGE_DIGIT_GROUPS,
119 "grouping digits into groups that are too large"
122 declare_clippy_lint! {
124 /// Warns if there is a better representation for a numeric literal.
126 /// ### Why is this bad?
127 /// Especially for big powers of 2 a hexadecimal representation is more
128 /// readable than a decimal representation.
132 /// `65_535` => `0xFFFF`
133 /// `4_042_322_160` => `0xF0F0_F0F0`
134 pub DECIMAL_LITERAL_REPRESENTATION,
136 "using decimal representation when hexadecimal would be better"
141 InconsistentDigitGrouping,
143 DecimalRepresentation,
144 MistypedLiteralSuffix,
145 UnusualByteGroupings,
149 fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) {
151 Self::MistypedLiteralSuffix => span_lint_and_sugg(
153 MISTYPED_LITERAL_SUFFIXES,
155 "mistyped literal suffix",
156 "did you mean to write",
158 Applicability::MaybeIncorrect,
160 Self::UnreadableLiteral => span_lint_and_sugg(
164 "long literal lacking separators",
167 Applicability::MachineApplicable,
169 Self::LargeDigitGroups => span_lint_and_sugg(
173 "digit groups should be smaller",
176 Applicability::MachineApplicable,
178 Self::InconsistentDigitGrouping => span_lint_and_sugg(
180 INCONSISTENT_DIGIT_GROUPING,
182 "digits grouped inconsistently by underscores",
185 Applicability::MachineApplicable,
187 Self::DecimalRepresentation => span_lint_and_sugg(
189 DECIMAL_LITERAL_REPRESENTATION,
191 "integer literal has a better hexadecimal representation",
194 Applicability::MachineApplicable,
196 Self::UnusualByteGroupings => span_lint_and_sugg(
198 UNUSUAL_BYTE_GROUPINGS,
200 "digits of hex or binary literal not grouped by four",
203 Applicability::MachineApplicable,
209 #[allow(clippy::module_name_repetitions)]
210 #[derive(Copy, Clone)]
211 pub struct LiteralDigitGrouping {
212 lint_fraction_readability: bool,
215 impl_lint_pass!(LiteralDigitGrouping => [
217 INCONSISTENT_DIGIT_GROUPING,
219 MISTYPED_LITERAL_SUFFIXES,
220 UNUSUAL_BYTE_GROUPINGS,
223 impl EarlyLintPass for LiteralDigitGrouping {
224 fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
225 if in_external_macro(cx.sess(), expr.span) {
229 if let ExprKind::Lit(ref lit) = expr.kind {
230 self.check_lit(cx, lit);
235 // Length of each UUID hyphenated group in hex digits.
236 const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12];
238 impl LiteralDigitGrouping {
239 pub fn new(lint_fraction_readability: bool) -> Self {
241 lint_fraction_readability,
245 fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
247 if let Some(src) = snippet_opt(cx, lit.span);
248 if let Some(mut num_lit) = NumericLiteral::from_lit(&src, lit);
250 if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) {
254 if Self::is_literal_uuid_formatted(&mut num_lit) {
260 let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
261 if let Some(fraction) = num_lit.fraction {
262 let fractional_group_size = Self::get_group_size(
263 fraction.rsplit('_'),
265 self.lint_fraction_readability)?;
267 let consistent = Self::parts_consistent(integral_group_size,
268 fractional_group_size,
269 num_lit.integer.len(),
272 return Err(WarningType::InconsistentDigitGrouping);
280 if let Err(warning_type) = result {
281 let should_warn = match warning_type {
282 | WarningType::UnreadableLiteral
283 | WarningType::InconsistentDigitGrouping
284 | WarningType::UnusualByteGroupings
285 | WarningType::LargeDigitGroups => {
288 WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
293 warning_type.display(num_lit.format(), cx, lit.span);
300 // Returns `false` if the check fails
301 fn check_for_mistyped_suffix(
302 cx: &EarlyContext<'_>,
303 span: rustc_span::Span,
304 num_lit: &mut NumericLiteral<'_>,
306 if num_lit.suffix.is_some() {
310 let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent {
311 (exponent, &["32", "64"][..], 'f')
312 } else if num_lit.fraction.is_some() {
313 (&mut num_lit.integer, &["32", "64"][..], 'f')
315 (&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i')
318 let mut split = part.rsplit('_');
319 let last_group = split.next().expect("At least one group");
320 if split.next().is_some() && mistyped_suffixes.contains(&last_group) {
321 *part = &part[..part.len() - last_group.len()];
322 let mut sugg = num_lit.format();
324 sugg.push(missing_char);
325 sugg.push_str(last_group);
326 WarningType::MistypedLiteralSuffix.display(sugg, cx, span);
333 /// Checks whether the numeric literal matches the formatting of a UUID.
335 /// Returns `true` if the radix is hexadecimal, and the groups match the
336 /// UUID format of 8-4-4-4-12.
337 fn is_literal_uuid_formatted(num_lit: &mut NumericLiteral<'_>) -> bool {
338 if num_lit.radix != Radix::Hexadecimal {
342 // UUIDs should not have a fraction
343 if num_lit.fraction.is_some() {
347 let group_sizes: Vec<usize> = num_lit.integer.split('_').map(str::len).collect();
348 if UUID_GROUP_LENS.len() == group_sizes.len() {
349 iter::zip(&UUID_GROUP_LENS, &group_sizes).all(|(&a, &b)| a == b)
355 /// Given the sizes of the digit groups of both integral and fractional
356 /// parts, and the length
357 /// of both parts, determine if the digits have been grouped consistently.
360 int_group_size: Option<usize>,
361 frac_group_size: Option<usize>,
365 match (int_group_size, frac_group_size) {
366 // No groups on either side of decimal point - trivially consistent.
367 (None, None) => true,
368 // Integral part has grouped digits, fractional part does not.
369 (Some(int_group_size), None) => frac_size <= int_group_size,
370 // Fractional part has grouped digits, integral part does not.
371 (None, Some(frac_group_size)) => int_size <= frac_group_size,
372 // Both parts have grouped digits. Groups should be the same size.
373 (Some(int_group_size), Some(frac_group_size)) => int_group_size == frac_group_size,
377 /// Returns the size of the digit groups (or None if ungrouped) if successful,
378 /// otherwise returns a `WarningType` for linting.
379 fn get_group_size<'a>(
380 groups: impl Iterator<Item = &'a str>,
382 lint_unreadable: bool,
383 ) -> Result<Option<usize>, WarningType> {
384 let mut groups = groups.map(str::len);
386 let first = groups.next().expect("At least one group");
388 if (radix == Radix::Binary || radix == Radix::Hexadecimal) && groups.any(|i| i != 4 && i != 2) {
389 return Err(WarningType::UnusualByteGroupings);
392 if let Some(second) = groups.next() {
393 if !groups.all(|x| x == second) || first > second {
394 Err(WarningType::InconsistentDigitGrouping)
395 } else if second > 4 {
396 Err(WarningType::LargeDigitGroups)
400 } else if first > 5 && lint_unreadable {
401 Err(WarningType::UnreadableLiteral)
408 #[allow(clippy::module_name_repetitions)]
409 #[derive(Copy, Clone)]
410 pub struct DecimalLiteralRepresentation {
414 impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]);
416 impl EarlyLintPass for DecimalLiteralRepresentation {
417 fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
418 if in_external_macro(cx.sess(), expr.span) {
422 if let ExprKind::Lit(ref lit) = expr.kind {
423 self.check_lit(cx, lit);
428 impl DecimalLiteralRepresentation {
430 pub fn new(threshold: u64) -> Self {
433 fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
434 // Lint integral literals.
436 if let LitKind::Int(val, _) = lit.kind;
437 if let Some(src) = snippet_opt(cx, lit.span);
438 if let Some(num_lit) = NumericLiteral::from_lit(&src, lit);
439 if num_lit.radix == Radix::Decimal;
440 if val >= u128::from(self.threshold);
442 let hex = format!("{:#X}", val);
443 let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
444 let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| {
445 warning_type.display(num_lit.format(), cx, lit.span);
451 fn do_lint(digits: &str) -> Result<(), WarningType> {
452 if digits.len() == 1 {
453 // Lint for 1 digit literals, if someone really sets the threshold that low
462 return Err(WarningType::DecimalRepresentation);
464 } else if digits.len() < 4 {
465 // Lint for Literals with a hex-representation of 2 or 3 digits
466 let f = &digits[0..1]; // first digit
467 let s = &digits[1..]; // suffix
470 if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0'))
471 // Powers of 2 minus 1
472 || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
474 return Err(WarningType::DecimalRepresentation);
477 // Lint for Literals with a hex-representation of 4 digits or more
478 let f = &digits[0..1]; // first digit
479 let m = &digits[1..digits.len() - 1]; // middle digits, except last
480 let s = &digits[1..]; // suffix
482 // Powers of 2 with a margin of +15/-16
483 if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0'))
484 || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F'))
485 // Lint for representations with only 0s and Fs, while allowing 7 as the first
487 || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
489 return Err(WarningType::DecimalRepresentation);