]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/literal_representation.rs
Rollup merge of #88215 - jyn514:lazy-loading, r=petrochenkov
[rust.git] / src / tools / clippy / 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 clippy_utils::diagnostics::span_lint_and_sugg;
5 use clippy_utils::source::snippet_opt;
6 use clippy_utils::{
7     in_macro,
8     numeric_literal::{NumericLiteral, Radix},
9 };
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};
14 use rustc_middle::lint::in_external_macro;
15 use rustc_session::{declare_tool_lint, impl_lint_pass};
16 use std::iter;
17
18 declare_clippy_lint! {
19     /// ### What it does
20     /// Warns if a long integral or floating-point constant does
21     /// not contain underscores.
22     ///
23     /// ### Why is this bad?
24     /// Reading long numbers is difficult without separators.
25     ///
26     /// ### Example
27     /// ```rust
28     /// // Bad
29     /// let x: u64 = 61864918973511;
30     ///
31     /// // Good
32     /// let x: u64 = 61_864_918_973_511;
33     /// ```
34     pub UNREADABLE_LITERAL,
35     pedantic,
36     "long literal without underscores"
37 }
38
39 declare_clippy_lint! {
40     /// ### What it does
41     /// Warns for mistyped suffix in literals
42     ///
43     /// ### Why is this bad?
44     /// This is most probably a typo
45     ///
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
50     ///
51     /// ### Example
52     /// ```rust
53     /// // Probably mistyped
54     /// 2_32;
55     ///
56     /// // Good
57     /// 2_i32;
58     /// ```
59     pub MISTYPED_LITERAL_SUFFIXES,
60     correctness,
61     "mistyped literal suffix"
62 }
63
64 declare_clippy_lint! {
65     /// ### What it does
66     /// Warns if an integral or floating-point constant is
67     /// grouped inconsistently with underscores.
68     ///
69     /// ### Why is this bad?
70     /// Readers may incorrectly interpret inconsistently
71     /// grouped digits.
72     ///
73     /// ### Example
74     /// ```rust
75     /// // Bad
76     /// let x: u64 = 618_64_9189_73_511;
77     ///
78     /// // Good
79     /// let x: u64 = 61_864_918_973_511;
80     /// ```
81     pub INCONSISTENT_DIGIT_GROUPING,
82     style,
83     "integer literals with digits grouped inconsistently"
84 }
85
86 declare_clippy_lint! {
87     /// ### What it does
88     /// Warns if hexadecimal or binary literals are not grouped
89     /// by nibble or byte.
90     ///
91     /// ### Why is this bad?
92     /// Negatively impacts readability.
93     ///
94     /// ### Example
95     /// ```rust
96     /// let x: u32 = 0xFFF_FFF;
97     /// let y: u8 = 0b01_011_101;
98     /// ```
99     pub UNUSUAL_BYTE_GROUPINGS,
100     style,
101     "binary or hex literals that aren't grouped by four"
102 }
103
104 declare_clippy_lint! {
105     /// ### What it does
106     /// Warns if the digits of an integral or floating-point
107     /// constant are grouped into groups that
108     /// are too large.
109     ///
110     /// ### Why is this bad?
111     /// Negatively impacts readability.
112     ///
113     /// ### Example
114     /// ```rust
115     /// let x: u64 = 6186491_8973511;
116     /// ```
117     pub LARGE_DIGIT_GROUPS,
118     pedantic,
119     "grouping digits into groups that are too large"
120 }
121
122 declare_clippy_lint! {
123     /// ### What it does
124     /// Warns if there is a better representation for a numeric literal.
125     ///
126     /// ### Why is this bad?
127     /// Especially for big powers of 2 a hexadecimal representation is more
128     /// readable than a decimal representation.
129     ///
130     /// ### Example
131     /// `255` => `0xFF`
132     /// `65_535` => `0xFFFF`
133     /// `4_042_322_160` => `0xF0F0_F0F0`
134     pub DECIMAL_LITERAL_REPRESENTATION,
135     restriction,
136     "using decimal representation when hexadecimal would be better"
137 }
138
139 enum WarningType {
140     UnreadableLiteral,
141     InconsistentDigitGrouping,
142     LargeDigitGroups,
143     DecimalRepresentation,
144     MistypedLiteralSuffix,
145     UnusualByteGroupings,
146 }
147
148 impl WarningType {
149     fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) {
150         match self {
151             Self::MistypedLiteralSuffix => span_lint_and_sugg(
152                 cx,
153                 MISTYPED_LITERAL_SUFFIXES,
154                 span,
155                 "mistyped literal suffix",
156                 "did you mean to write",
157                 suggested_format,
158                 Applicability::MaybeIncorrect,
159             ),
160             Self::UnreadableLiteral => span_lint_and_sugg(
161                 cx,
162                 UNREADABLE_LITERAL,
163                 span,
164                 "long literal lacking separators",
165                 "consider",
166                 suggested_format,
167                 Applicability::MachineApplicable,
168             ),
169             Self::LargeDigitGroups => span_lint_and_sugg(
170                 cx,
171                 LARGE_DIGIT_GROUPS,
172                 span,
173                 "digit groups should be smaller",
174                 "consider",
175                 suggested_format,
176                 Applicability::MachineApplicable,
177             ),
178             Self::InconsistentDigitGrouping => span_lint_and_sugg(
179                 cx,
180                 INCONSISTENT_DIGIT_GROUPING,
181                 span,
182                 "digits grouped inconsistently by underscores",
183                 "consider",
184                 suggested_format,
185                 Applicability::MachineApplicable,
186             ),
187             Self::DecimalRepresentation => span_lint_and_sugg(
188                 cx,
189                 DECIMAL_LITERAL_REPRESENTATION,
190                 span,
191                 "integer literal has a better hexadecimal representation",
192                 "consider",
193                 suggested_format,
194                 Applicability::MachineApplicable,
195             ),
196             Self::UnusualByteGroupings => span_lint_and_sugg(
197                 cx,
198                 UNUSUAL_BYTE_GROUPINGS,
199                 span,
200                 "digits of hex or binary literal not grouped by four",
201                 "consider",
202                 suggested_format,
203                 Applicability::MachineApplicable,
204             ),
205         };
206     }
207 }
208
209 #[allow(clippy::module_name_repetitions)]
210 #[derive(Copy, Clone)]
211 pub struct LiteralDigitGrouping {
212     lint_fraction_readability: bool,
213 }
214
215 impl_lint_pass!(LiteralDigitGrouping => [
216     UNREADABLE_LITERAL,
217     INCONSISTENT_DIGIT_GROUPING,
218     LARGE_DIGIT_GROUPS,
219     MISTYPED_LITERAL_SUFFIXES,
220     UNUSUAL_BYTE_GROUPINGS,
221 ]);
222
223 impl EarlyLintPass for LiteralDigitGrouping {
224     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
225         if in_external_macro(cx.sess, expr.span) {
226             return;
227         }
228
229         if let ExprKind::Lit(ref lit) = expr.kind {
230             self.check_lit(cx, lit);
231         }
232     }
233 }
234
235 // Length of each UUID hyphenated group in hex digits.
236 const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12];
237
238 impl LiteralDigitGrouping {
239     pub fn new(lint_fraction_readability: bool) -> Self {
240         Self {
241             lint_fraction_readability,
242         }
243     }
244
245     fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
246         if_chain! {
247             if let Some(src) = snippet_opt(cx, lit.span);
248             if let Some(mut num_lit) = NumericLiteral::from_lit(&src, lit);
249             then {
250                 if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) {
251                     return;
252                 }
253
254                 if Self::is_literal_uuid_formatted(&mut num_lit) {
255                     return;
256                 }
257
258                 let result = (|| {
259
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('_'),
264                             num_lit.radix,
265                             self.lint_fraction_readability)?;
266
267                         let consistent = Self::parts_consistent(integral_group_size,
268                                                                 fractional_group_size,
269                                                                 num_lit.integer.len(),
270                                                                 fraction.len());
271                         if !consistent {
272                             return Err(WarningType::InconsistentDigitGrouping);
273                         };
274                     }
275
276                     Ok(())
277                 })();
278
279
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 => {
286                             !in_macro(lit.span)
287                         }
288                         WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
289                             true
290                         }
291                     };
292                     if should_warn {
293                         warning_type.display(num_lit.format(), cx, lit.span);
294                     }
295                 }
296             }
297         }
298     }
299
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<'_>,
305     ) -> bool {
306         if num_lit.suffix.is_some() {
307             return true;
308         }
309
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')
314         } else {
315             (&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i')
316         };
317
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();
323             sugg.push('_');
324             sugg.push(missing_char);
325             sugg.push_str(last_group);
326             WarningType::MistypedLiteralSuffix.display(sugg, cx, span);
327             false
328         } else {
329             true
330         }
331     }
332
333     /// Checks whether the numeric literal matches the formatting of a UUID.
334     ///
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 {
339             return false;
340         }
341
342         // UUIDs should not have a fraction
343         if num_lit.fraction.is_some() {
344             return false;
345         }
346
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)
350         } else {
351             false
352         }
353     }
354
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.
358     #[must_use]
359     fn parts_consistent(
360         int_group_size: Option<usize>,
361         frac_group_size: Option<usize>,
362         int_size: usize,
363         frac_size: usize,
364     ) -> bool {
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,
374         }
375     }
376
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>,
381         radix: Radix,
382         lint_unreadable: bool,
383     ) -> Result<Option<usize>, WarningType> {
384         let mut groups = groups.map(str::len);
385
386         let first = groups.next().expect("At least one group");
387
388         if (radix == Radix::Binary || radix == Radix::Hexadecimal) && groups.any(|i| i != 4 && i != 2) {
389             return Err(WarningType::UnusualByteGroupings);
390         }
391
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)
397             } else {
398                 Ok(Some(second))
399             }
400         } else if first > 5 && lint_unreadable {
401             Err(WarningType::UnreadableLiteral)
402         } else {
403             Ok(None)
404         }
405     }
406 }
407
408 #[allow(clippy::module_name_repetitions)]
409 #[derive(Copy, Clone)]
410 pub struct DecimalLiteralRepresentation {
411     threshold: u64,
412 }
413
414 impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]);
415
416 impl EarlyLintPass for DecimalLiteralRepresentation {
417     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
418         if in_external_macro(cx.sess, expr.span) {
419             return;
420         }
421
422         if let ExprKind::Lit(ref lit) = expr.kind {
423             self.check_lit(cx, lit);
424         }
425     }
426 }
427
428 impl DecimalLiteralRepresentation {
429     #[must_use]
430     pub fn new(threshold: u64) -> Self {
431         Self { threshold }
432     }
433     fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
434         // Lint integral literals.
435         if_chain! {
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);
441             then {
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);
446                 });
447             }
448         }
449     }
450
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
454             if digits == "1"
455                 || digits == "2"
456                 || digits == "4"
457                 || digits == "8"
458                 || digits == "3"
459                 || digits == "7"
460                 || digits == "F"
461             {
462                 return Err(WarningType::DecimalRepresentation);
463             }
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
468
469             // Powers of 2
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'))
473             {
474                 return Err(WarningType::DecimalRepresentation);
475             }
476         } else {
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
481
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
486                 // digit
487                 || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
488             {
489                 return Err(WarningType::DecimalRepresentation);
490             }
491         }
492
493         Ok(())
494     }
495 }