]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/literal_representation.rs
Auto merge of #5040 - JohnTitor:rustup-0111, r=flip1995
[rust.git] / clippy_lints / src / literal_representation.rs
1 //! Lints concerned with the grouping of digits with underscores in integral or
2 //! floating-point literal expressions.
3
4 use crate::utils::{in_macro, snippet_opt, span_lint_and_sugg};
5 use if_chain::if_chain;
6 use rustc::lint::{in_external_macro, EarlyContext, EarlyLintPass, LintContext};
7 use rustc_errors::Applicability;
8 use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
9 use syntax::ast::*;
10
11 declare_clippy_lint! {
12     /// **What it does:** Warns if a long integral or floating-point constant does
13     /// not contain underscores.
14     ///
15     /// **Why is this bad?** Reading long numbers is difficult without separators.
16     ///
17     /// **Known problems:** None.
18     ///
19     /// **Example:**
20     ///
21     /// ```rust
22     /// let x: u64 = 61864918973511;
23     /// ```
24     pub UNREADABLE_LITERAL,
25     style,
26     "long integer literal without underscores"
27 }
28
29 declare_clippy_lint! {
30     /// **What it does:** Warns for mistyped suffix in literals
31     ///
32     /// **Why is this bad?** This is most probably a typo
33     ///
34     /// **Known problems:**
35     /// - Recommends a signed suffix, even though the number might be too big and an unsigned
36     ///   suffix is required
37     /// - Does not match on `_128` since that is a valid grouping for decimal and octal numbers
38     ///
39     /// **Example:**
40     ///
41     /// ```rust
42     /// 2_32;
43     /// ```
44     pub MISTYPED_LITERAL_SUFFIXES,
45     correctness,
46     "mistyped literal suffix"
47 }
48
49 declare_clippy_lint! {
50     /// **What it does:** Warns if an integral or floating-point constant is
51     /// grouped inconsistently with underscores.
52     ///
53     /// **Why is this bad?** Readers may incorrectly interpret inconsistently
54     /// grouped digits.
55     ///
56     /// **Known problems:** None.
57     ///
58     /// **Example:**
59     ///
60     /// ```rust
61     /// let x: u64 = 618_64_9189_73_511;
62     /// ```
63     pub INCONSISTENT_DIGIT_GROUPING,
64     style,
65     "integer literals with digits grouped inconsistently"
66 }
67
68 declare_clippy_lint! {
69     /// **What it does:** Warns if the digits of an integral or floating-point
70     /// constant are grouped into groups that
71     /// are too large.
72     ///
73     /// **Why is this bad?** Negatively impacts readability.
74     ///
75     /// **Known problems:** None.
76     ///
77     /// **Example:**
78     ///
79     /// ```rust
80     /// let x: u64 = 6186491_8973511;
81     /// ```
82     pub LARGE_DIGIT_GROUPS,
83     pedantic,
84     "grouping digits into groups that are too large"
85 }
86
87 declare_clippy_lint! {
88     /// **What it does:** Warns if there is a better representation for a numeric literal.
89     ///
90     /// **Why is this bad?** Especially for big powers of 2 a hexadecimal representation is more
91     /// readable than a decimal representation.
92     ///
93     /// **Known problems:** None.
94     ///
95     /// **Example:**
96     ///
97     /// `255` => `0xFF`
98     /// `65_535` => `0xFFFF`
99     /// `4_042_322_160` => `0xF0F0_F0F0`
100     pub DECIMAL_LITERAL_REPRESENTATION,
101     restriction,
102     "using decimal representation when hexadecimal would be better"
103 }
104
105 #[derive(Debug, PartialEq)]
106 pub(super) enum Radix {
107     Binary,
108     Octal,
109     Decimal,
110     Hexadecimal,
111 }
112
113 impl Radix {
114     /// Returns a reasonable digit group size for this radix.
115     #[must_use]
116     fn suggest_grouping(&self) -> usize {
117         match *self {
118             Self::Binary | Self::Hexadecimal => 4,
119             Self::Octal | Self::Decimal => 3,
120         }
121     }
122 }
123
124 /// A helper method to format numeric literals with digit grouping.
125 /// `lit` must be a valid numeric literal without suffix.
126 pub fn format_numeric_literal(lit: &str, type_suffix: Option<&str>, float: bool) -> String {
127     NumericLiteral::new(lit, type_suffix, float).format()
128 }
129
130 #[derive(Debug)]
131 pub(super) struct NumericLiteral<'a> {
132     /// Which radix the literal was represented in.
133     radix: Radix,
134     /// The radix prefix, if present.
135     prefix: Option<&'a str>,
136
137     /// The integer part of the number.
138     integer: &'a str,
139     /// The fraction part of the number.
140     fraction: Option<&'a str>,
141     /// The character used as exponent seperator (b'e' or b'E') and the exponent part.
142     exponent: Option<(char, &'a str)>,
143
144     /// The type suffix, including preceding underscore if present.
145     suffix: Option<&'a str>,
146 }
147
148 impl<'a> NumericLiteral<'a> {
149     fn from_lit(src: &'a str, lit: &Lit) -> Option<NumericLiteral<'a>> {
150         if lit.kind.is_numeric() && src.chars().next().map_or(false, |c| c.is_digit(10)) {
151             let (unsuffixed, suffix) = split_suffix(&src, &lit.kind);
152             let float = if let LitKind::Float(..) = lit.kind { true } else { false };
153             Some(NumericLiteral::new(unsuffixed, suffix, float))
154         } else {
155             None
156         }
157     }
158
159     #[must_use]
160     fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self {
161         // Determine delimiter for radix prefix, if present, and radix.
162         let radix = if lit.starts_with("0x") {
163             Radix::Hexadecimal
164         } else if lit.starts_with("0b") {
165             Radix::Binary
166         } else if lit.starts_with("0o") {
167             Radix::Octal
168         } else {
169             Radix::Decimal
170         };
171
172         // Grab part of the literal after prefix, if present.
173         let (prefix, mut sans_prefix) = if let Radix::Decimal = radix {
174             (None, lit)
175         } else {
176             let (p, s) = lit.split_at(2);
177             (Some(p), s)
178         };
179
180         if suffix.is_some() && sans_prefix.ends_with('_') {
181             // The '_' before the suffix isn't part of the digits
182             sans_prefix = &sans_prefix[..sans_prefix.len() - 1];
183         }
184
185         let (integer, fraction, exponent) = Self::split_digit_parts(sans_prefix, float);
186
187         Self {
188             radix,
189             prefix,
190             integer,
191             fraction,
192             exponent,
193             suffix,
194         }
195     }
196
197     fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(char, &str)>) {
198         let mut integer = digits;
199         let mut fraction = None;
200         let mut exponent = None;
201
202         if float {
203             for (i, c) in digits.char_indices() {
204                 match c {
205                     '.' => {
206                         integer = &digits[..i];
207                         fraction = Some(&digits[i + 1..]);
208                     },
209                     'e' | 'E' => {
210                         if integer.len() > i {
211                             integer = &digits[..i];
212                         } else {
213                             fraction = Some(&digits[integer.len() + 1..i]);
214                         };
215                         exponent = Some((c, &digits[i + 1..]));
216                         break;
217                     },
218                     _ => {},
219                 }
220             }
221         }
222
223         (integer, fraction, exponent)
224     }
225
226     /// Returns literal formatted in a sensible way.
227     fn format(&self) -> String {
228         let mut output = String::new();
229
230         if let Some(prefix) = self.prefix {
231             output.push_str(prefix);
232         }
233
234         let group_size = self.radix.suggest_grouping();
235
236         Self::group_digits(
237             &mut output,
238             self.integer,
239             group_size,
240             true,
241             self.radix == Radix::Hexadecimal,
242         );
243
244         if let Some(fraction) = self.fraction {
245             output.push('.');
246             Self::group_digits(&mut output, fraction, group_size, false, false);
247         }
248
249         if let Some((separator, exponent)) = self.exponent {
250             output.push(separator);
251             Self::group_digits(&mut output, exponent, group_size, true, false);
252         }
253
254         if let Some(suffix) = self.suffix {
255             output.push('_');
256             output.push_str(suffix);
257         }
258
259         output
260     }
261
262     fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) {
263         debug_assert!(group_size > 0);
264
265         let mut digits = input.chars().filter(|&c| c != '_');
266
267         let first_group_size;
268
269         if partial_group_first {
270             first_group_size = (digits.clone().count() - 1) % group_size + 1;
271             if pad {
272                 for _ in 0..group_size - first_group_size {
273                     output.push('0');
274                 }
275             }
276         } else {
277             first_group_size = group_size;
278         }
279
280         for _ in 0..first_group_size {
281             if let Some(digit) = digits.next() {
282                 output.push(digit);
283             }
284         }
285
286         for (c, i) in digits.zip((0..group_size).cycle()) {
287             if i == 0 {
288                 output.push('_');
289             }
290             output.push(c);
291         }
292     }
293 }
294
295 fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
296     debug_assert!(lit_kind.is_numeric());
297     if let Some(suffix_length) = lit_suffix_length(lit_kind) {
298         let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
299         (unsuffixed, Some(suffix))
300     } else {
301         (src, None)
302     }
303 }
304
305 fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
306     debug_assert!(lit_kind.is_numeric());
307     let suffix = match lit_kind {
308         LitKind::Int(_, int_lit_kind) => match int_lit_kind {
309             LitIntType::Signed(int_ty) => Some(int_ty.name_str()),
310             LitIntType::Unsigned(uint_ty) => Some(uint_ty.name_str()),
311             LitIntType::Unsuffixed => None,
312         },
313         LitKind::Float(_, float_lit_kind) => match float_lit_kind {
314             LitFloatType::Suffixed(float_ty) => Some(float_ty.name_str()),
315             LitFloatType::Unsuffixed => None,
316         },
317         _ => None,
318     };
319
320     suffix.map(str::len)
321 }
322
323 enum WarningType {
324     UnreadableLiteral,
325     InconsistentDigitGrouping,
326     LargeDigitGroups,
327     DecimalRepresentation,
328     MistypedLiteralSuffix,
329 }
330
331 impl WarningType {
332     fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) {
333         match self {
334             Self::MistypedLiteralSuffix => span_lint_and_sugg(
335                 cx,
336                 MISTYPED_LITERAL_SUFFIXES,
337                 span,
338                 "mistyped literal suffix",
339                 "did you mean to write",
340                 suggested_format,
341                 Applicability::MaybeIncorrect,
342             ),
343             Self::UnreadableLiteral => span_lint_and_sugg(
344                 cx,
345                 UNREADABLE_LITERAL,
346                 span,
347                 "long literal lacking separators",
348                 "consider",
349                 suggested_format,
350                 Applicability::MachineApplicable,
351             ),
352             Self::LargeDigitGroups => span_lint_and_sugg(
353                 cx,
354                 LARGE_DIGIT_GROUPS,
355                 span,
356                 "digit groups should be smaller",
357                 "consider",
358                 suggested_format,
359                 Applicability::MachineApplicable,
360             ),
361             Self::InconsistentDigitGrouping => span_lint_and_sugg(
362                 cx,
363                 INCONSISTENT_DIGIT_GROUPING,
364                 span,
365                 "digits grouped inconsistently by underscores",
366                 "consider",
367                 suggested_format,
368                 Applicability::MachineApplicable,
369             ),
370             Self::DecimalRepresentation => span_lint_and_sugg(
371                 cx,
372                 DECIMAL_LITERAL_REPRESENTATION,
373                 span,
374                 "integer literal has a better hexadecimal representation",
375                 "consider",
376                 suggested_format,
377                 Applicability::MachineApplicable,
378             ),
379         };
380     }
381 }
382
383 declare_lint_pass!(LiteralDigitGrouping => [
384     UNREADABLE_LITERAL,
385     INCONSISTENT_DIGIT_GROUPING,
386     LARGE_DIGIT_GROUPS,
387     MISTYPED_LITERAL_SUFFIXES,
388 ]);
389
390 impl EarlyLintPass for LiteralDigitGrouping {
391     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
392         if in_external_macro(cx.sess(), expr.span) {
393             return;
394         }
395
396         if let ExprKind::Lit(ref lit) = expr.kind {
397             Self::check_lit(cx, lit)
398         }
399     }
400 }
401
402 impl LiteralDigitGrouping {
403     fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
404         if_chain! {
405             if let Some(src) = snippet_opt(cx, lit.span);
406             if let Some(mut num_lit) = NumericLiteral::from_lit(&src, &lit);
407             then {
408                 if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) {
409                     return;
410                 }
411
412                 let result = (|| {
413
414                     let integral_group_size = Self::get_group_size(num_lit.integer.split('_'))?;
415                     if let Some(fraction) = num_lit.fraction {
416                         let fractional_group_size = Self::get_group_size(fraction.rsplit('_'))?;
417
418                         let consistent = Self::parts_consistent(integral_group_size,
419                                                                 fractional_group_size,
420                                                                 num_lit.integer.len(),
421                                                                 fraction.len());
422                         if !consistent {
423                             return Err(WarningType::InconsistentDigitGrouping);
424                         };
425                     }
426                     Ok(())
427                 })();
428
429
430                 if let Err(warning_type) = result {
431                     let should_warn = match warning_type {
432                         | WarningType::UnreadableLiteral
433                         | WarningType::InconsistentDigitGrouping
434                         | WarningType::LargeDigitGroups => {
435                             !in_macro(lit.span)
436                         }
437                         WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
438                             true
439                         }
440                     };
441                     if should_warn {
442                         warning_type.display(num_lit.format(), cx, lit.span)
443                     }
444                 }
445             }
446         }
447     }
448
449     // Returns `false` if the check fails
450     fn check_for_mistyped_suffix(
451         cx: &EarlyContext<'_>,
452         span: rustc_span::Span,
453         num_lit: &mut NumericLiteral<'_>,
454     ) -> bool {
455         if num_lit.suffix.is_some() {
456             return true;
457         }
458
459         let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent {
460             (exponent, &["32", "64"][..], 'f')
461         } else if let Some(fraction) = &mut num_lit.fraction {
462             (fraction, &["32", "64"][..], 'f')
463         } else {
464             (&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i')
465         };
466
467         let mut split = part.rsplit('_');
468         let last_group = split.next().expect("At least one group");
469         if split.next().is_some() && mistyped_suffixes.contains(&last_group) {
470             *part = &part[..part.len() - last_group.len()];
471             let mut sugg = num_lit.format();
472             sugg.push('_');
473             sugg.push(missing_char);
474             sugg.push_str(last_group);
475             WarningType::MistypedLiteralSuffix.display(sugg, cx, span);
476             false
477         } else {
478             true
479         }
480     }
481
482     /// Given the sizes of the digit groups of both integral and fractional
483     /// parts, and the length
484     /// of both parts, determine if the digits have been grouped consistently.
485     #[must_use]
486     fn parts_consistent(
487         int_group_size: Option<usize>,
488         frac_group_size: Option<usize>,
489         int_size: usize,
490         frac_size: usize,
491     ) -> bool {
492         match (int_group_size, frac_group_size) {
493             // No groups on either side of decimal point - trivially consistent.
494             (None, None) => true,
495             // Integral part has grouped digits, fractional part does not.
496             (Some(int_group_size), None) => frac_size <= int_group_size,
497             // Fractional part has grouped digits, integral part does not.
498             (None, Some(frac_group_size)) => int_size <= frac_group_size,
499             // Both parts have grouped digits. Groups should be the same size.
500             (Some(int_group_size), Some(frac_group_size)) => int_group_size == frac_group_size,
501         }
502     }
503
504     /// Returns the size of the digit groups (or None if ungrouped) if successful,
505     /// otherwise returns a `WarningType` for linting.
506     fn get_group_size<'a>(groups: impl Iterator<Item = &'a str>) -> Result<Option<usize>, WarningType> {
507         let mut groups = groups.map(str::len);
508
509         let first = groups.next().expect("At least one group");
510
511         if let Some(second) = groups.next() {
512             if !groups.all(|x| x == second) || first > second {
513                 Err(WarningType::InconsistentDigitGrouping)
514             } else if second > 4 {
515                 Err(WarningType::LargeDigitGroups)
516             } else {
517                 Ok(Some(second))
518             }
519         } else if first > 5 {
520             Err(WarningType::UnreadableLiteral)
521         } else {
522             Ok(None)
523         }
524     }
525 }
526
527 #[allow(clippy::module_name_repetitions)]
528 #[derive(Copy, Clone)]
529 pub struct DecimalLiteralRepresentation {
530     threshold: u64,
531 }
532
533 impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]);
534
535 impl EarlyLintPass for DecimalLiteralRepresentation {
536     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
537         if in_external_macro(cx.sess(), expr.span) {
538             return;
539         }
540
541         if let ExprKind::Lit(ref lit) = expr.kind {
542             self.check_lit(cx, lit)
543         }
544     }
545 }
546
547 impl DecimalLiteralRepresentation {
548     #[must_use]
549     pub fn new(threshold: u64) -> Self {
550         Self { threshold }
551     }
552     fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
553         // Lint integral literals.
554         if_chain! {
555             if let LitKind::Int(val, _) = lit.kind;
556             if let Some(src) = snippet_opt(cx, lit.span);
557             if let Some(num_lit) = NumericLiteral::from_lit(&src, &lit);
558             if num_lit.radix == Radix::Decimal;
559             if val >= u128::from(self.threshold);
560             then {
561                 let hex = format!("{:#X}", val);
562                 let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
563                 let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| {
564                     warning_type.display(num_lit.format(), cx, lit.span)
565                 });
566             }
567         }
568     }
569
570     fn do_lint(digits: &str) -> Result<(), WarningType> {
571         if digits.len() == 1 {
572             // Lint for 1 digit literals, if someone really sets the threshold that low
573             if digits == "1"
574                 || digits == "2"
575                 || digits == "4"
576                 || digits == "8"
577                 || digits == "3"
578                 || digits == "7"
579                 || digits == "F"
580             {
581                 return Err(WarningType::DecimalRepresentation);
582             }
583         } else if digits.len() < 4 {
584             // Lint for Literals with a hex-representation of 2 or 3 digits
585             let f = &digits[0..1]; // first digit
586             let s = &digits[1..]; // suffix
587
588             // Powers of 2
589             if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0'))
590                 // Powers of 2 minus 1
591                 || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
592             {
593                 return Err(WarningType::DecimalRepresentation);
594             }
595         } else {
596             // Lint for Literals with a hex-representation of 4 digits or more
597             let f = &digits[0..1]; // first digit
598             let m = &digits[1..digits.len() - 1]; // middle digits, except last
599             let s = &digits[1..]; // suffix
600
601             // Powers of 2 with a margin of +15/-16
602             if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0'))
603                 || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F'))
604                 // Lint for representations with only 0s and Fs, while allowing 7 as the first
605                 // digit
606                 || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
607             {
608                 return Err(WarningType::DecimalRepresentation);
609             }
610         }
611
612         Ok(())
613     }
614 }