]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/literal_representation.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / literal_representation.rs
CommitLineData
f20569fa
XL
1//! Lints concerned with the grouping of digits with underscores in integral or
2//! floating-point literal expressions.
3
cdc7bbd5 4use clippy_utils::diagnostics::span_lint_and_sugg;
a2a8927a 5use clippy_utils::numeric_literal::{NumericLiteral, Radix};
cdc7bbd5 6use clippy_utils::source::snippet_opt;
487cf647
FG
7use rustc_ast::ast::{Expr, ExprKind, LitKind};
8use rustc_ast::token;
f20569fa 9use rustc_errors::Applicability;
5099ac24 10use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
f20569fa 11use rustc_middle::lint::in_external_macro;
4b012472 12use rustc_session::impl_lint_pass;
487cf647 13use rustc_span::Span;
cdc7bbd5 14use std::iter;
f20569fa
XL
15
16declare_clippy_lint! {
94222f64
XL
17 /// ### What it does
18 /// Warns if a long integral or floating-point constant does
f20569fa
XL
19 /// not contain underscores.
20 ///
94222f64
XL
21 /// ### Why is this bad?
22 /// Reading long numbers is difficult without separators.
f20569fa 23 ///
94222f64 24 /// ### Example
ed00b5ec 25 /// ```no_run
923072b8
FG
26 /// # let _: u64 =
27 /// 61864918973511
28 /// # ;
29 /// ```
f20569fa 30 ///
923072b8 31 /// Use instead:
ed00b5ec 32 /// ```no_run
923072b8
FG
33 /// # let _: u64 =
34 /// 61_864_918_973_511
35 /// # ;
f20569fa 36 /// ```
a2a8927a 37 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
38 pub UNREADABLE_LITERAL,
39 pedantic,
40 "long literal without underscores"
41}
42
43declare_clippy_lint! {
94222f64
XL
44 /// ### What it does
45 /// Warns for mistyped suffix in literals
f20569fa 46 ///
94222f64
XL
47 /// ### Why is this bad?
48 /// This is most probably a typo
f20569fa 49 ///
94222f64 50 /// ### Known problems
04454e1e 51 /// - Does not match on integers too large to fit in the corresponding unsigned type
f20569fa
XL
52 /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers
53 ///
94222f64 54 /// ### Example
923072b8 55 /// ```ignore
04454e1e
FG
56 /// `2_32` => `2_i32`
57 /// `250_8 => `250_u8`
f20569fa 58 /// ```
a2a8927a 59 #[clippy::version = "1.30.0"]
f20569fa
XL
60 pub MISTYPED_LITERAL_SUFFIXES,
61 correctness,
62 "mistyped literal suffix"
63}
64
65declare_clippy_lint! {
94222f64
XL
66 /// ### What it does
67 /// Warns if an integral or floating-point constant is
f20569fa
XL
68 /// grouped inconsistently with underscores.
69 ///
94222f64
XL
70 /// ### Why is this bad?
71 /// Readers may incorrectly interpret inconsistently
f20569fa
XL
72 /// grouped digits.
73 ///
94222f64 74 /// ### Example
ed00b5ec 75 /// ```no_run
923072b8
FG
76 /// # let _: u64 =
77 /// 618_64_9189_73_511
78 /// # ;
79 /// ```
f20569fa 80 ///
923072b8 81 /// Use instead:
ed00b5ec 82 /// ```no_run
923072b8
FG
83 /// # let _: u64 =
84 /// 61_864_918_973_511
85 /// # ;
f20569fa 86 /// ```
a2a8927a 87 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
88 pub INCONSISTENT_DIGIT_GROUPING,
89 style,
90 "integer literals with digits grouped inconsistently"
91}
92
93declare_clippy_lint! {
94222f64
XL
94 /// ### What it does
95 /// Warns if hexadecimal or binary literals are not grouped
f20569fa
XL
96 /// by nibble or byte.
97 ///
94222f64
XL
98 /// ### Why is this bad?
99 /// Negatively impacts readability.
f20569fa 100 ///
94222f64 101 /// ### Example
ed00b5ec 102 /// ```no_run
f20569fa
XL
103 /// let x: u32 = 0xFFF_FFF;
104 /// let y: u8 = 0b01_011_101;
105 /// ```
a2a8927a 106 #[clippy::version = "1.49.0"]
f20569fa
XL
107 pub UNUSUAL_BYTE_GROUPINGS,
108 style,
109 "binary or hex literals that aren't grouped by four"
110}
111
112declare_clippy_lint! {
94222f64
XL
113 /// ### What it does
114 /// Warns if the digits of an integral or floating-point
f20569fa
XL
115 /// constant are grouped into groups that
116 /// are too large.
117 ///
94222f64
XL
118 /// ### Why is this bad?
119 /// Negatively impacts readability.
f20569fa 120 ///
94222f64 121 /// ### Example
ed00b5ec 122 /// ```no_run
f20569fa
XL
123 /// let x: u64 = 6186491_8973511;
124 /// ```
a2a8927a 125 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
126 pub LARGE_DIGIT_GROUPS,
127 pedantic,
128 "grouping digits into groups that are too large"
129}
130
131declare_clippy_lint! {
94222f64
XL
132 /// ### What it does
133 /// Warns if there is a better representation for a numeric literal.
f20569fa 134 ///
94222f64
XL
135 /// ### Why is this bad?
136 /// Especially for big powers of 2 a hexadecimal representation is more
f20569fa
XL
137 /// readable than a decimal representation.
138 ///
94222f64 139 /// ### Example
923072b8 140 /// ```text
f20569fa
XL
141 /// `255` => `0xFF`
142 /// `65_535` => `0xFFFF`
143 /// `4_042_322_160` => `0xF0F0_F0F0`
923072b8 144 /// ```
a2a8927a 145 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
146 pub DECIMAL_LITERAL_REPRESENTATION,
147 restriction,
148 "using decimal representation when hexadecimal would be better"
149}
150
151enum WarningType {
152 UnreadableLiteral,
153 InconsistentDigitGrouping,
154 LargeDigitGroups,
155 DecimalRepresentation,
156 MistypedLiteralSuffix,
157 UnusualByteGroupings,
158}
159
160impl WarningType {
161 fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) {
162 match self {
163 Self::MistypedLiteralSuffix => span_lint_and_sugg(
164 cx,
165 MISTYPED_LITERAL_SUFFIXES,
166 span,
167 "mistyped literal suffix",
168 "did you mean to write",
169 suggested_format,
170 Applicability::MaybeIncorrect,
171 ),
172 Self::UnreadableLiteral => span_lint_and_sugg(
173 cx,
174 UNREADABLE_LITERAL,
175 span,
176 "long literal lacking separators",
177 "consider",
178 suggested_format,
179 Applicability::MachineApplicable,
180 ),
181 Self::LargeDigitGroups => span_lint_and_sugg(
182 cx,
183 LARGE_DIGIT_GROUPS,
184 span,
185 "digit groups should be smaller",
186 "consider",
187 suggested_format,
188 Applicability::MachineApplicable,
189 ),
190 Self::InconsistentDigitGrouping => span_lint_and_sugg(
191 cx,
192 INCONSISTENT_DIGIT_GROUPING,
193 span,
194 "digits grouped inconsistently by underscores",
195 "consider",
196 suggested_format,
197 Applicability::MachineApplicable,
198 ),
199 Self::DecimalRepresentation => span_lint_and_sugg(
200 cx,
201 DECIMAL_LITERAL_REPRESENTATION,
202 span,
203 "integer literal has a better hexadecimal representation",
204 "consider",
205 suggested_format,
206 Applicability::MachineApplicable,
207 ),
208 Self::UnusualByteGroupings => span_lint_and_sugg(
209 cx,
210 UNUSUAL_BYTE_GROUPINGS,
211 span,
9ffffee4 212 "digits of hex, binary or octal literal not in groups of equal size",
f20569fa
XL
213 "consider",
214 suggested_format,
215 Applicability::MachineApplicable,
216 ),
217 };
218 }
219}
220
f20569fa
XL
221#[derive(Copy, Clone)]
222pub struct LiteralDigitGrouping {
223 lint_fraction_readability: bool,
224}
225
226impl_lint_pass!(LiteralDigitGrouping => [
227 UNREADABLE_LITERAL,
228 INCONSISTENT_DIGIT_GROUPING,
229 LARGE_DIGIT_GROUPS,
230 MISTYPED_LITERAL_SUFFIXES,
231 UNUSUAL_BYTE_GROUPINGS,
232]);
233
234impl EarlyLintPass for LiteralDigitGrouping {
235 fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
5099ac24 236 if in_external_macro(cx.sess(), expr.span) {
f20569fa
XL
237 return;
238 }
239
487cf647
FG
240 if let ExprKind::Lit(lit) = expr.kind {
241 self.check_lit(cx, lit, expr.span);
f20569fa
XL
242 }
243 }
244}
245
246// Length of each UUID hyphenated group in hex digits.
247const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12];
248
249impl LiteralDigitGrouping {
250 pub fn new(lint_fraction_readability: bool) -> Self {
251 Self {
252 lint_fraction_readability,
253 }
254 }
255
487cf647 256 fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
4b012472
FG
257 if let Some(src) = snippet_opt(cx, span)
258 && let Ok(lit_kind) = LitKind::from_token_lit(lit)
259 && let Some(mut num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
260 {
261 if !Self::check_for_mistyped_suffix(cx, span, &mut num_lit) {
262 return;
263 }
264
265 if Self::is_literal_uuid_formatted(&num_lit) {
266 return;
267 }
f20569fa 268
4b012472
FG
269 let result = (|| {
270 let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
271 if let Some(fraction) = num_lit.fraction {
272 let fractional_group_size =
273 Self::get_group_size(fraction.rsplit('_'), num_lit.radix, self.lint_fraction_readability)?;
274
275 let consistent = Self::parts_consistent(
276 integral_group_size,
277 fractional_group_size,
278 num_lit.integer.len(),
279 fraction.len(),
280 );
281 if !consistent {
282 return Err(WarningType::InconsistentDigitGrouping);
283 };
f20569fa
XL
284 }
285
4b012472
FG
286 Ok(())
287 })();
f20569fa 288
4b012472
FG
289 if let Err(warning_type) = result {
290 let should_warn = match warning_type {
291 WarningType::UnreadableLiteral
292 | WarningType::InconsistentDigitGrouping
293 | WarningType::UnusualByteGroupings
294 | WarningType::LargeDigitGroups => !span.from_expansion(),
295 WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => true,
296 };
297 if should_warn {
298 warning_type.display(num_lit.format(), cx, span);
f20569fa
XL
299 }
300 }
301 }
302 }
303
304 // Returns `false` if the check fails
305 fn check_for_mistyped_suffix(
306 cx: &EarlyContext<'_>,
307 span: rustc_span::Span,
308 num_lit: &mut NumericLiteral<'_>,
309 ) -> bool {
310 if num_lit.suffix.is_some() {
311 return true;
312 }
313
04454e1e
FG
314 let (part, mistyped_suffixes, is_float) = if let Some((_, exponent)) = &mut num_lit.exponent {
315 (exponent, &["32", "64"][..], true)
f20569fa 316 } else if num_lit.fraction.is_some() {
04454e1e 317 return true;
f20569fa 318 } else {
04454e1e 319 (&mut num_lit.integer, &["8", "16", "32", "64"][..], false)
f20569fa
XL
320 };
321
322 let mut split = part.rsplit('_');
323 let last_group = split.next().expect("At least one group");
324 if split.next().is_some() && mistyped_suffixes.contains(&last_group) {
04454e1e
FG
325 let main_part = &part[..part.len() - last_group.len()];
326 let missing_char;
327 if is_float {
328 missing_char = 'f';
329 } else {
330 let radix = match num_lit.radix {
331 Radix::Binary => 2,
332 Radix::Octal => 8,
333 Radix::Decimal => 10,
334 Radix::Hexadecimal => 16,
335 };
336 if let Ok(int) = u64::from_str_radix(&main_part.replace('_', ""), radix) {
337 missing_char = match (last_group, int) {
338 ("8", i) if i8::try_from(i).is_ok() => 'i',
339 ("16", i) if i16::try_from(i).is_ok() => 'i',
340 ("32", i) if i32::try_from(i).is_ok() => 'i',
341 ("64", i) if i64::try_from(i).is_ok() => 'i',
342 ("8", u) if u8::try_from(u).is_ok() => 'u',
343 ("16", u) if u16::try_from(u).is_ok() => 'u',
344 ("32", u) if u32::try_from(u).is_ok() => 'u',
345 ("64", _) => 'u',
346 _ => {
347 return true;
348 },
349 }
350 } else {
351 return true;
352 }
353 }
354 *part = main_part;
f20569fa
XL
355 let mut sugg = num_lit.format();
356 sugg.push('_');
357 sugg.push(missing_char);
358 sugg.push_str(last_group);
359 WarningType::MistypedLiteralSuffix.display(sugg, cx, span);
360 false
361 } else {
362 true
363 }
364 }
365
366 /// Checks whether the numeric literal matches the formatting of a UUID.
367 ///
368 /// Returns `true` if the radix is hexadecimal, and the groups match the
369 /// UUID format of 8-4-4-4-12.
add651ee 370 fn is_literal_uuid_formatted(num_lit: &NumericLiteral<'_>) -> bool {
f20569fa
XL
371 if num_lit.radix != Radix::Hexadecimal {
372 return false;
373 }
374
375 // UUIDs should not have a fraction
376 if num_lit.fraction.is_some() {
377 return false;
378 }
379
380 let group_sizes: Vec<usize> = num_lit.integer.split('_').map(str::len).collect();
381 if UUID_GROUP_LENS.len() == group_sizes.len() {
cdc7bbd5 382 iter::zip(&UUID_GROUP_LENS, &group_sizes).all(|(&a, &b)| a == b)
f20569fa
XL
383 } else {
384 false
385 }
386 }
387
388 /// Given the sizes of the digit groups of both integral and fractional
389 /// parts, and the length
390 /// of both parts, determine if the digits have been grouped consistently.
391 #[must_use]
392 fn parts_consistent(
393 int_group_size: Option<usize>,
394 frac_group_size: Option<usize>,
395 int_size: usize,
396 frac_size: usize,
397 ) -> bool {
398 match (int_group_size, frac_group_size) {
399 // No groups on either side of decimal point - trivially consistent.
400 (None, None) => true,
401 // Integral part has grouped digits, fractional part does not.
402 (Some(int_group_size), None) => frac_size <= int_group_size,
403 // Fractional part has grouped digits, integral part does not.
404 (None, Some(frac_group_size)) => int_size <= frac_group_size,
405 // Both parts have grouped digits. Groups should be the same size.
406 (Some(int_group_size), Some(frac_group_size)) => int_group_size == frac_group_size,
407 }
408 }
409
410 /// Returns the size of the digit groups (or None if ungrouped) if successful,
411 /// otherwise returns a `WarningType` for linting.
412 fn get_group_size<'a>(
413 groups: impl Iterator<Item = &'a str>,
414 radix: Radix,
415 lint_unreadable: bool,
416 ) -> Result<Option<usize>, WarningType> {
417 let mut groups = groups.map(str::len);
418
419 let first = groups.next().expect("At least one group");
420
9ffffee4
FG
421 if radix == Radix::Binary || radix == Radix::Octal || radix == Radix::Hexadecimal {
422 if let Some(second_size) = groups.next() {
423 if !groups.all(|i| i == second_size) || first > second_size {
424 return Err(WarningType::UnusualByteGroupings);
425 }
426 }
f20569fa
XL
427 }
428
429 if let Some(second) = groups.next() {
430 if !groups.all(|x| x == second) || first > second {
431 Err(WarningType::InconsistentDigitGrouping)
432 } else if second > 4 {
433 Err(WarningType::LargeDigitGroups)
434 } else {
435 Ok(Some(second))
436 }
437 } else if first > 5 && lint_unreadable {
438 Err(WarningType::UnreadableLiteral)
439 } else {
440 Ok(None)
441 }
442 }
443}
444
923072b8 445#[expect(clippy::module_name_repetitions)]
f20569fa
XL
446#[derive(Copy, Clone)]
447pub struct DecimalLiteralRepresentation {
448 threshold: u64,
449}
450
451impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]);
452
453impl EarlyLintPass for DecimalLiteralRepresentation {
454 fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
5099ac24 455 if in_external_macro(cx.sess(), expr.span) {
f20569fa
XL
456 return;
457 }
458
487cf647
FG
459 if let ExprKind::Lit(lit) = expr.kind {
460 self.check_lit(cx, lit, expr.span);
f20569fa
XL
461 }
462 }
463}
464
465impl DecimalLiteralRepresentation {
466 #[must_use]
467 pub fn new(threshold: u64) -> Self {
468 Self { threshold }
469 }
487cf647 470 fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
f20569fa 471 // Lint integral literals.
4b012472
FG
472 if let Ok(lit_kind) = LitKind::from_token_lit(lit)
473 && let LitKind::Int(val, _) = lit_kind
474 && let Some(src) = snippet_opt(cx, span)
475 && let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
476 && num_lit.radix == Radix::Decimal
477 && val >= u128::from(self.threshold)
478 {
479 let hex = format!("{val:#X}");
480 let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
481 let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
482 warning_type.display(num_lit.format(), cx, span);
483 });
f20569fa
XL
484 }
485 }
486
487 fn do_lint(digits: &str) -> Result<(), WarningType> {
488 if digits.len() == 1 {
489 // Lint for 1 digit literals, if someone really sets the threshold that low
490 if digits == "1"
491 || digits == "2"
492 || digits == "4"
493 || digits == "8"
494 || digits == "3"
495 || digits == "7"
496 || digits == "F"
497 {
498 return Err(WarningType::DecimalRepresentation);
499 }
500 } else if digits.len() < 4 {
501 // Lint for Literals with a hex-representation of 2 or 3 digits
502 let f = &digits[0..1]; // first digit
503 let s = &digits[1..]; // suffix
504
505 // Powers of 2
506 if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0'))
507 // Powers of 2 minus 1
508 || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
509 {
510 return Err(WarningType::DecimalRepresentation);
511 }
512 } else {
513 // Lint for Literals with a hex-representation of 4 digits or more
514 let f = &digits[0..1]; // first digit
515 let m = &digits[1..digits.len() - 1]; // middle digits, except last
516 let s = &digits[1..]; // suffix
517
518 // Powers of 2 with a margin of +15/-16
519 if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0'))
520 || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F'))
521 // Lint for representations with only 0s and Fs, while allowing 7 as the first
522 // digit
523 || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
524 {
525 return Err(WarningType::DecimalRepresentation);
526 }
527 }
528
529 Ok(())
530 }
531}