Skip to content

Commit 77c3ea0

Browse files
committed
New lint is_digit_ascii_radix
1 parent 9ee05a0 commit 77c3ea0

18 files changed

+160
-14
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3354,6 +3354,7 @@ Released 2018-09-13
33543354
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
33553355
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
33563356
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
3357+
[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
33573358
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
33583359
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
33593360
[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count

clippy_lints/src/case_sensitive_file_extension_comparisons.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &
4343
if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind;
4444
if (2..=6).contains(&ext_literal.as_str().len());
4545
if ext_literal.as_str().starts_with('.');
46-
if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_digit(10))
47-
|| ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_digit(10));
46+
if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit())
47+
|| ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit());
4848
then {
4949
let mut ty = ctx.typeck_results().expr_ty(obj);
5050
ty = match ty.kind() {

clippy_lints/src/doc.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
727727
/// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
728728
/// Plurals are also excluded (`IDs` is ok).
729729
fn is_camel_case(s: &str) -> bool {
730-
if s.starts_with(|c: char| c.is_digit(10)) {
730+
if s.starts_with(|c: char| c.is_ascii_digit()) {
731731
return false;
732732
}
733733

clippy_lints/src/lib.register_all.rs

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
163163
LintId::of(methods::FLAT_MAP_IDENTITY),
164164
LintId::of(methods::INSPECT_FOR_EACH),
165165
LintId::of(methods::INTO_ITER_ON_REF),
166+
LintId::of(methods::IS_DIGIT_ASCII_RADIX),
166167
LintId::of(methods::ITERATOR_STEP_BY_ZERO),
167168
LintId::of(methods::ITER_CLONED_COLLECT),
168169
LintId::of(methods::ITER_COUNT),

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ store.register_lints(&[
300300
methods::INEFFICIENT_TO_STRING,
301301
methods::INSPECT_FOR_EACH,
302302
methods::INTO_ITER_ON_REF,
303+
methods::IS_DIGIT_ASCII_RADIX,
303304
methods::ITERATOR_STEP_BY_ZERO,
304305
methods::ITER_CLONED_COLLECT,
305306
methods::ITER_COUNT,

clippy_lints/src/lib.register_style.rs

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
6060
LintId::of(methods::CHARS_LAST_CMP),
6161
LintId::of(methods::CHARS_NEXT_CMP),
6262
LintId::of(methods::INTO_ITER_ON_REF),
63+
LintId::of(methods::IS_DIGIT_ASCII_RADIX),
6364
LintId::of(methods::ITER_CLONED_COLLECT),
6465
LintId::of(methods::ITER_NEXT_SLICE),
6566
LintId::of(methods::ITER_NTH_ZERO),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! Lint for `c.is_digit(10)`
2+
3+
use super::IS_DIGIT_ASCII_RADIX;
4+
use clippy_utils::{
5+
consts::constant_full_int, consts::FullInt, diagnostics::span_lint_and_sugg, meets_msrv, msrvs,
6+
source::snippet_with_applicability,
7+
};
8+
use rustc_errors::Applicability;
9+
use rustc_hir::Expr;
10+
use rustc_lint::LateContext;
11+
use rustc_semver::RustcVersion;
12+
13+
pub(super) fn check<'tcx>(
14+
cx: &LateContext<'tcx>,
15+
expr: &'tcx Expr<'_>,
16+
self_arg: &'tcx Expr<'_>,
17+
radix: &'tcx Expr<'_>,
18+
msrv: Option<&RustcVersion>,
19+
) {
20+
if !meets_msrv(msrv, &msrvs::IS_ASCII_DIGIT) {
21+
return;
22+
}
23+
24+
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_char() {
25+
return;
26+
}
27+
28+
if let Some(radix_val) = constant_full_int(cx, cx.typeck_results(), radix) {
29+
let (num, replacement) = match radix_val {
30+
FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"),
31+
FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"),
32+
_ => return,
33+
};
34+
let mut applicability = Applicability::MachineApplicable;
35+
36+
span_lint_and_sugg(
37+
cx,
38+
IS_DIGIT_ASCII_RADIX,
39+
expr.span,
40+
&format!("use of `char::is_digit` with literal radix of {}", num),
41+
"try",
42+
format!(
43+
"{}.{}()",
44+
snippet_with_applicability(cx, self_arg.span, "..", &mut applicability),
45+
replacement
46+
),
47+
applicability,
48+
);
49+
}
50+
}

clippy_lints/src/methods/mod.rs

+33
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod implicit_clone;
2626
mod inefficient_to_string;
2727
mod inspect_for_each;
2828
mod into_iter_on_ref;
29+
mod is_digit_ascii_radix;
2930
mod iter_cloned_collect;
3031
mod iter_count;
3132
mod iter_next_slice;
@@ -2106,6 +2107,36 @@ declare_clippy_lint! {
21062107
"using `.collect::<Vec<String>>().join(\"\")` on an iterator"
21072108
}
21082109

2110+
declare_clippy_lint! {
2111+
/// ### What it does
2112+
/// Finds usages of [`char::is_digit`]
2113+
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that
2114+
/// can be replaced with [`is_ascii_digit`]
2115+
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or
2116+
/// [`is_ascii_hexdigit`]
2117+
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit).
2118+
///
2119+
/// ### Why is this bad?
2120+
/// `is_digit(..)` is slower and requires specifying the radix.
2121+
///
2122+
/// ### Example
2123+
/// ```rust
2124+
/// let c: char = '6';
2125+
/// c.is_digit(10);
2126+
/// c.is_digit(16);
2127+
/// ```
2128+
/// Use instead:
2129+
/// ```rust
2130+
/// let c: char = '6';
2131+
/// c.is_ascii_digit();
2132+
/// c.is_ascii_hexdigit();
2133+
/// ```
2134+
#[clippy::version = "1.61.0"]
2135+
pub IS_DIGIT_ASCII_RADIX,
2136+
style,
2137+
"use of `char::is_digit(..)` with literal radix of 10 or 16"
2138+
}
2139+
21092140
pub struct Methods {
21102141
avoid_breaking_exported_api: bool,
21112142
msrv: Option<RustcVersion>,
@@ -2193,6 +2224,7 @@ impl_lint_pass!(Methods => [
21932224
UNNECESSARY_TO_OWNED,
21942225
UNNECESSARY_JOIN,
21952226
ERR_EXPECT,
2227+
IS_DIGIT_ASCII_RADIX,
21962228
]);
21972229

21982230
/// Extracts a method call name, args, and `Span` of the method name.
@@ -2488,6 +2520,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
24882520
},
24892521
("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
24902522
("is_file", []) => filetype_is_file::check(cx, expr, recv),
2523+
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, msrv),
24912524
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
24922525
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
24932526
("join", [join_arg]) => {

clippy_lints/src/misc_early/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ impl MiscEarlyLints {
361361
// See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
362362
// FIXME: Find a better way to detect those cases.
363363
let lit_snip = match snippet_opt(cx, lit.span) {
364-
Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip,
364+
Some(snip) if snip.chars().next().map_or(false, |c| c.is_ascii_digit()) => snip,
365365
_ => return,
366366
};
367367

clippy_lints/src/non_expressive_names.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
197197
if interned_name.chars().any(char::is_uppercase) {
198198
return;
199199
}
200-
if interned_name.chars().all(|c| c.is_digit(10) || c == '_') {
200+
if interned_name.chars().all(|c| c.is_ascii_digit() || c == '_') {
201201
span_lint(
202202
self.0.cx,
203203
JUST_UNDERSCORES_AND_DIGITS,

clippy_utils/src/msrvs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ msrv_aliases! {
3232
1,28,0 { FROM_BOOL }
3333
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
3434
1,16,0 { STR_REPEAT }
35+
1,24,0 { IS_ASCII_DIGIT }
3536
}

clippy_utils/src/numeric_literal.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl<'a> NumericLiteral<'a> {
5757
.trim_start()
5858
.chars()
5959
.next()
60-
.map_or(false, |c| c.is_digit(10))
60+
.map_or(false, |c| c.is_ascii_digit())
6161
{
6262
let (unsuffixed, suffix) = split_suffix(src, lit_kind);
6363
let float = matches!(lit_kind, LitKind::Float(..));

tests/ui/is_digit_ascii_radix.fixed

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::is_digit_ascii_radix)]
4+
5+
const TEN: u32 = 10;
6+
7+
fn main() {
8+
let c: char = '6';
9+
10+
// Should trigger the lint.
11+
let _ = c.is_ascii_digit();
12+
let _ = c.is_ascii_hexdigit();
13+
let _ = c.is_ascii_hexdigit();
14+
15+
// Should not trigger the lint.
16+
let _ = c.is_digit(11);
17+
let _ = c.is_digit(TEN);
18+
}

tests/ui/is_digit_ascii_radix.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::is_digit_ascii_radix)]
4+
5+
const TEN: u32 = 10;
6+
7+
fn main() {
8+
let c: char = '6';
9+
10+
// Should trigger the lint.
11+
let _ = c.is_digit(10);
12+
let _ = c.is_digit(16);
13+
let _ = c.is_digit(0x10);
14+
15+
// Should not trigger the lint.
16+
let _ = c.is_digit(11);
17+
let _ = c.is_digit(TEN);
18+
}

tests/ui/is_digit_ascii_radix.stderr

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: use of `char::is_digit` with literal radix of 10
2+
--> $DIR/is_digit_ascii_radix.rs:11:13
3+
|
4+
LL | let _ = c.is_digit(10);
5+
| ^^^^^^^^^^^^^^ help: try: `c.is_ascii_digit()`
6+
|
7+
= note: `-D clippy::is-digit-ascii-radix` implied by `-D warnings`
8+
9+
error: use of `char::is_digit` with literal radix of 16
10+
--> $DIR/is_digit_ascii_radix.rs:12:13
11+
|
12+
LL | let _ = c.is_digit(16);
13+
| ^^^^^^^^^^^^^^ help: try: `c.is_ascii_hexdigit()`
14+
15+
error: use of `char::is_digit` with literal radix of 16
16+
--> $DIR/is_digit_ascii_radix.rs:13:13
17+
|
18+
LL | let _ = c.is_digit(0x10);
19+
| ^^^^^^^^^^^^^^^^ help: try: `c.is_ascii_hexdigit()`
20+
21+
error: aborting due to 3 previous errors
22+

tests/ui/to_digit_is_some.fixed

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ fn main() {
66
let c = 'x';
77
let d = &c;
88

9-
let _ = d.is_digit(10);
10-
let _ = char::is_digit(c, 10);
9+
let _ = d.is_digit(8);
10+
let _ = char::is_digit(c, 8);
1111
}

tests/ui/to_digit_is_some.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ fn main() {
66
let c = 'x';
77
let d = &c;
88

9-
let _ = d.to_digit(10).is_some();
10-
let _ = char::to_digit(c, 10).is_some();
9+
let _ = d.to_digit(8).is_some();
10+
let _ = char::to_digit(c, 8).is_some();
1111
}

tests/ui/to_digit_is_some.stderr

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
error: use of `.to_digit(..).is_some()`
22
--> $DIR/to_digit_is_some.rs:9:13
33
|
4-
LL | let _ = d.to_digit(10).is_some();
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `d.is_digit(10)`
4+
LL | let _ = d.to_digit(8).is_some();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `d.is_digit(8)`
66
|
77
= note: `-D clippy::to-digit-is-some` implied by `-D warnings`
88

99
error: use of `.to_digit(..).is_some()`
1010
--> $DIR/to_digit_is_some.rs:10:13
1111
|
12-
LL | let _ = char::to_digit(c, 10).is_some();
13-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `char::is_digit(c, 10)`
12+
LL | let _ = char::to_digit(c, 8).is_some();
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `char::is_digit(c, 8)`
1414

1515
error: aborting due to 2 previous errors
1616

0 commit comments

Comments
 (0)