Skip to content

Commit 8e464b6

Browse files
author
Michael A. Plikk
committed
Add lint for misstyped literal casting
1 parent 131c8f8 commit 8e464b6

File tree

6 files changed

+123
-16
lines changed

6 files changed

+123
-16
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ All notable changes to this project will be documented in this file.
645645
[`cmp_owned`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#cmp_owned
646646
[`collapsible_if`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#collapsible_if
647647
[`const_static_lifetime`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#const_static_lifetime
648+
[`copy_iterator`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#copy_iterator
648649
[`crosspointer_transmute`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#crosspointer_transmute
649650
[`cyclomatic_complexity`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#cyclomatic_complexity
650651
[`decimal_literal_representation`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#decimal_literal_representation
@@ -748,6 +749,7 @@ All notable changes to this project will be documented in this file.
748749
[`misrefactored_assign_op`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#misrefactored_assign_op
749750
[`missing_docs_in_private_items`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
750751
[`missing_inline_in_public_items`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#missing_inline_in_public_items
752+
[`mistyped_literal_suffixes`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
751753
[`mixed_case_hex_literals`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
752754
[`module_inception`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#module_inception
753755
[`modulo_one`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#modulo_one
@@ -800,6 +802,7 @@ All notable changes to this project will be documented in this file.
800802
[`print_with_newline`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#print_with_newline
801803
[`println_empty_string`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#println_empty_string
802804
[`ptr_arg`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#ptr_arg
805+
[`ptr_offset_with_cast`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
803806
[`pub_enum_variant_names`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#pub_enum_variant_names
804807
[`question_mark`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#question_mark
805808
[`range_minus_one`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#range_minus_one

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ We are currently in the process of discussing Clippy 1.0 via the RFC process in
99

1010
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
1111

12-
[There are 273 lints included in this crate!](https://rust-lang-nursery.github.io/rust-clippy/master/index.html)
12+
[There are 275 lints included in this crate!](https://rust-lang-nursery.github.io/rust-clippy/master/index.html)
1313

1414
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1515

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
543543
lifetimes::NEEDLESS_LIFETIMES,
544544
literal_representation::INCONSISTENT_DIGIT_GROUPING,
545545
literal_representation::LARGE_DIGIT_GROUPS,
546+
literal_representation::MISTYPED_LITERAL_SUFFIXES,
546547
literal_representation::UNREADABLE_LITERAL,
547548
loops::EMPTY_LOOP,
548549
loops::EXPLICIT_COUNTER_LOOP,
@@ -869,6 +870,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
869870
infinite_iter::INFINITE_ITER,
870871
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
871872
invalid_ref::INVALID_REF,
873+
literal_representation::MISTYPED_LITERAL_SUFFIXES,
872874
loops::FOR_LOOP_OVER_OPTION,
873875
loops::FOR_LOOP_OVER_RESULT,
874876
loops::ITER_NEXT_LOOP,

clippy_lints/src/literal_representation.rs

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ declare_clippy_lint! {
2626
"long integer literal without underscores"
2727
}
2828

29+
/// **What it does:** Warns for mistyped suffix in literals
30+
///
31+
/// **Why is this bad?** This is most probably a typo
32+
///
33+
/// **Known problems:**
34+
/// - Recommends unsigned suffix even if it's a negative number. Should only recommend the signed suffix on those cases.
35+
/// - Assumes `_128` should have been `_i128`, even though `_128` is a valid grouping for decimal and octal numbers
36+
///
37+
/// **Example:**
38+
///
39+
/// ```rust
40+
/// 2_32
41+
/// ```
42+
declare_clippy_lint! {
43+
pub MISTYPED_LITERAL_SUFFIXES,
44+
correctness,
45+
"mistyped literal suffix"
46+
}
47+
2948
/// **What it does:** Warns if an integral or floating-point constant is
3049
/// grouped inconsistently with underscores.
3150
///
@@ -136,17 +155,22 @@ impl<'a> DigitInfo<'a> {
136155
};
137156

138157
let mut last_d = '\0';
158+
let len = sans_prefix.len();
139159
for (d_idx, d) in sans_prefix.char_indices() {
140-
if !float && (d == 'i' || d == 'u') || float && (d == 'f' || d == 'e' || d == 'E') {
160+
if !float && (d == 'i' || d == 'u') ||
161+
float && (d == 'f' || d == 'e' || d == 'E') ||
162+
(!float && is_possible_suffix_index(&d, d_idx, len)) {
141163
let suffix_start = if last_d == '_' { d_idx - 1 } else { d_idx };
142164
let (digits, suffix) = sans_prefix.split_at(suffix_start);
143-
return Self {
144-
digits,
145-
radix,
146-
prefix,
147-
suffix: Some(suffix),
148-
float,
149-
};
165+
if d != '_' || is_mistyped_suffix(suffix) {
166+
return Self {
167+
digits,
168+
radix,
169+
prefix,
170+
suffix: Some(suffix),
171+
float,
172+
};
173+
}
150174
}
151175
last_d = d
152176
}
@@ -161,7 +185,7 @@ impl<'a> DigitInfo<'a> {
161185
}
162186
}
163187

164-
/// Returns digits grouped in a sensible way.
188+
/// Returns literal formatted in a sensible way.
165189
crate fn grouping_hint(&self) -> String {
166190
let group_size = self.radix.suggest_grouping();
167191
if self.digits.contains('.') {
@@ -211,11 +235,18 @@ impl<'a> DigitInfo<'a> {
211235
if self.radix == Radix::Hexadecimal && nb_digits_to_fill != 0 {
212236
hint = format!("{:0>4}{}", &hint[..nb_digits_to_fill], &hint[nb_digits_to_fill..]);
213237
}
238+
let suffix_hint = match self.suffix {
239+
Some(suffix) if is_mistyped_suffix(suffix) => {
240+
format!("_i{}", &suffix[1..])
241+
},
242+
Some(suffix) => suffix.to_string(),
243+
None => String::new()
244+
};
214245
format!(
215246
"{}{}{}",
216247
self.prefix.unwrap_or(""),
217248
hint,
218-
self.suffix.unwrap_or("")
249+
suffix_hint
219250
)
220251
}
221252
}
@@ -226,11 +257,22 @@ enum WarningType {
226257
InconsistentDigitGrouping,
227258
LargeDigitGroups,
228259
DecimalRepresentation,
260+
MistypedLiteralSuffix
229261
}
230262

231263
impl WarningType {
232264
crate fn display(&self, grouping_hint: &str, cx: &EarlyContext<'_>, span: syntax_pos::Span) {
233265
match self {
266+
WarningType::MistypedLiteralSuffix => {
267+
span_lint_and_sugg(
268+
cx,
269+
MISTYPED_LITERAL_SUFFIXES,
270+
span,
271+
"mistyped literal suffix",
272+
"did you mean to write",
273+
grouping_hint.to_string()
274+
)
275+
},
234276
WarningType::UnreadableLiteral => span_lint_and_sugg(
235277
cx,
236278
UNREADABLE_LITERAL,
@@ -303,7 +345,7 @@ impl LiteralDigitGrouping {
303345
if char::to_digit(firstch, 10).is_some();
304346
then {
305347
let digit_info = DigitInfo::new(&src, false);
306-
let _ = Self::do_lint(digit_info.digits).map_err(|warning_type| {
348+
let _ = Self::do_lint(digit_info.digits, digit_info.suffix).map_err(|warning_type| {
307349
warning_type.display(&digit_info.grouping_hint(), cx, lit.span)
308350
});
309351
}
@@ -325,12 +367,12 @@ impl LiteralDigitGrouping {
325367

326368
// Lint integral and fractional parts separately, and then check consistency of digit
327369
// groups if both pass.
328-
let _ = Self::do_lint(parts[0])
370+
let _ = Self::do_lint(parts[0], None)
329371
.map(|integral_group_size| {
330372
if parts.len() > 1 {
331373
// Lint the fractional part of literal just like integral part, but reversed.
332374
let fractional_part = &parts[1].chars().rev().collect::<String>();
333-
let _ = Self::do_lint(fractional_part)
375+
let _ = Self::do_lint(fractional_part, None)
334376
.map(|fractional_group_size| {
335377
let consistent = Self::parts_consistent(integral_group_size,
336378
fractional_group_size,
@@ -373,7 +415,12 @@ impl LiteralDigitGrouping {
373415

374416
/// Performs lint on `digits` (no decimal point) and returns the group
375417
/// size on success or `WarningType` when emitting a warning.
376-
fn do_lint(digits: &str) -> Result<usize, WarningType> {
418+
fn do_lint(digits: &str, suffix: Option<&str>) -> Result<usize, WarningType> {
419+
if let Some(suffix) = suffix {
420+
if is_mistyped_suffix(suffix) {
421+
return Err(WarningType::MistypedLiteralSuffix);
422+
}
423+
}
377424
// Grab underscore indices with respect to the units digit.
378425
let underscore_positions: Vec<usize> = digits
379426
.chars()
@@ -504,3 +551,11 @@ impl LiteralRepresentation {
504551
Ok(())
505552
}
506553
}
554+
555+
fn is_mistyped_suffix(suffix: &str) -> bool {
556+
["_8", "_16", "_32", "_64", "_128"].contains(&suffix)
557+
}
558+
559+
fn is_possible_suffix_index(d: &char, d_idx: usize, len: usize) -> bool {
560+
*d == '_' && ((len > 4 && d_idx == len - 4) || (len > 3 && d_idx == len - 3) || (len > 2 && d_idx == len - 2))
561+
}

tests/ui/literals.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,11 @@ fn main() {
4343
let fail11 = 0xabcdeff;
4444
let fail12 = 0xabcabcabcabcabcabc;
4545
let fail13 = 0x1_23456_78901_usize;
46+
47+
let fail14 = 2_32;
48+
let fail15 = 4_64;
49+
let fail16 = 7_8;
50+
let fail17 = 23_16;
51+
let fail18 = 23_128;
52+
let fail19 = 12_3456_21;
4653
}

tests/ui/literals.stderr

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,45 @@ error: digit groups should be smaller
120120
|
121121
= note: `-D clippy::large-digit-groups` implied by `-D warnings`
122122

123-
error: aborting due to 16 previous errors
123+
error: mistyped literal suffix
124+
--> $DIR/literals.rs:47:18
125+
|
126+
47 | let fail14 = 2_32;
127+
| ^^^^ help: did you mean to write: `2_i32`
128+
|
129+
= note: #[deny(clippy::mistyped_literal_suffixes)] on by default
130+
131+
error: mistyped literal suffix
132+
--> $DIR/literals.rs:48:18
133+
|
134+
48 | let fail15 = 4_64;
135+
| ^^^^ help: did you mean to write: `4_i64`
136+
137+
error: mistyped literal suffix
138+
--> $DIR/literals.rs:49:18
139+
|
140+
49 | let fail16 = 7_8;
141+
| ^^^ help: did you mean to write: `7_i8`
142+
143+
error: mistyped literal suffix
144+
--> $DIR/literals.rs:50:18
145+
|
146+
50 | let fail17 = 23_16;
147+
| ^^^^^ help: did you mean to write: `23_i16`
148+
149+
error: mistyped literal suffix
150+
--> $DIR/literals.rs:51:18
151+
|
152+
51 | let fail18 = 23_128;
153+
| ^^^^^^ help: did you mean to write: `23_i128`
154+
155+
error: digits grouped inconsistently by underscores
156+
--> $DIR/literals.rs:52:18
157+
|
158+
52 | let fail19 = 12_3456_21;
159+
| ^^^^^^^^^^ help: consider: `12_345_621`
160+
|
161+
= note: `-D clippy::inconsistent-digit-grouping` implied by `-D warnings`
162+
163+
error: aborting due to 22 previous errors
124164

0 commit comments

Comments
 (0)