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