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