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