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