Skip to content

Commit be15e60

Browse files
committed
Auto merge of #10053 - naosense:fix_9933, r=xFrednet
improve `manual_is_ascii_check ` check Sorry, not familiar the api, i can only check the method name of expression `<expr-1>.contains(<expr-2>)` after read clippy book and hints from #9933 . i dont know how to check 1. if <expr-1> is a specific range 2. <expr-2> is a character r? `@xFrednet` could you please provide some more hints? 😝️ --- changelog: Enhancement: [`manual_is_ascii_check`]: Now detects ranges with `.contains()` calls [#10053](#10053) <!-- changelog_checked -->
2 parents 3b67e6a + 1f862c2 commit be15e60

File tree

4 files changed

+141
-40
lines changed

4 files changed

+141
-40
lines changed

clippy_lints/src/manual_is_ascii_check.rs

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use clippy_utils::msrvs::{self, Msrv};
2-
use clippy_utils::{diagnostics::span_lint_and_sugg, in_constant, macros::root_macro_call, source::snippet};
2+
use clippy_utils::{diagnostics::span_lint_and_sugg, higher, in_constant, macros::root_macro_call, source::snippet};
3+
use rustc_ast::ast::RangeLimits;
34
use rustc_ast::LitKind::{Byte, Char};
45
use rustc_errors::Applicability;
5-
use rustc_hir::{Expr, ExprKind, PatKind, RangeEnd};
6+
use rustc_hir::{BorrowKind, Expr, ExprKind, PatKind, RangeEnd};
67
use rustc_lint::{LateContext, LateLintPass};
78
use rustc_session::{declare_tool_lint, impl_lint_pass};
8-
use rustc_span::{def_id::DefId, sym};
9+
use rustc_span::{def_id::DefId, sym, Span};
910

1011
declare_clippy_lint! {
1112
/// ### What it does
@@ -23,6 +24,10 @@ declare_clippy_lint! {
2324
/// assert!(matches!(b'X', b'A'..=b'Z'));
2425
/// assert!(matches!('2', '0'..='9'));
2526
/// assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
27+
///
28+
/// ('0'..='9').contains(&'0');
29+
/// ('a'..='z').contains(&'a');
30+
/// ('A'..='Z').contains(&'A');
2631
/// }
2732
/// ```
2833
/// Use instead:
@@ -32,6 +37,10 @@ declare_clippy_lint! {
3237
/// assert!(b'X'.is_ascii_uppercase());
3338
/// assert!('2'.is_ascii_digit());
3439
/// assert!('x'.is_ascii_alphabetic());
40+
///
41+
/// '0'.is_ascii_digit();
42+
/// 'a'.is_ascii_lowercase();
43+
/// 'A'.is_ascii_uppercase();
3544
/// }
3645
/// ```
3746
#[clippy::version = "1.66.0"]
@@ -75,47 +84,59 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
7584
return;
7685
}
7786

78-
let Some(macro_call) = root_macro_call(expr.span) else { return };
79-
80-
if is_matches_macro(cx, macro_call.def_id) {
87+
if let Some(macro_call) = root_macro_call(expr.span)
88+
&& is_matches_macro(cx, macro_call.def_id) {
8189
if let ExprKind::Match(recv, [arm, ..], _) = expr.kind {
8290
let range = check_pat(&arm.pat.kind);
83-
84-
if let Some(sugg) = match range {
85-
CharRange::UpperChar => Some("is_ascii_uppercase"),
86-
CharRange::LowerChar => Some("is_ascii_lowercase"),
87-
CharRange::FullChar => Some("is_ascii_alphabetic"),
88-
CharRange::Digit => Some("is_ascii_digit"),
89-
CharRange::Otherwise => None,
90-
} {
91-
let default_snip = "..";
92-
// `snippet_with_applicability` may set applicability to `MaybeIncorrect` for
93-
// macro span, so we check applicability manually by comparing `recv` is not default.
94-
let recv = snippet(cx, recv.span, default_snip);
95-
96-
let applicability = if recv == default_snip {
97-
Applicability::HasPlaceholders
98-
} else {
99-
Applicability::MachineApplicable
100-
};
101-
102-
span_lint_and_sugg(
103-
cx,
104-
MANUAL_IS_ASCII_CHECK,
105-
macro_call.span,
106-
"manual check for common ascii range",
107-
"try",
108-
format!("{recv}.{sugg}()"),
109-
applicability,
110-
);
111-
}
91+
check_is_ascii(cx, macro_call.span, recv, &range);
92+
}
93+
} else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind
94+
&& path.ident.name == sym!(contains)
95+
&& let Some(higher::Range { start: Some(start), end: Some(end), limits: RangeLimits::Closed })
96+
= higher::Range::hir(receiver) {
97+
let range = check_range(start, end);
98+
if let ExprKind::AddrOf(BorrowKind::Ref, _, e) = arg.kind {
99+
check_is_ascii(cx, expr.span, e, &range);
100+
} else {
101+
check_is_ascii(cx, expr.span, arg, &range);
112102
}
113103
}
114104
}
115105

116106
extract_msrv_attr!(LateContext);
117107
}
118108

109+
fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &CharRange) {
110+
if let Some(sugg) = match range {
111+
CharRange::UpperChar => Some("is_ascii_uppercase"),
112+
CharRange::LowerChar => Some("is_ascii_lowercase"),
113+
CharRange::FullChar => Some("is_ascii_alphabetic"),
114+
CharRange::Digit => Some("is_ascii_digit"),
115+
CharRange::Otherwise => None,
116+
} {
117+
let default_snip = "..";
118+
// `snippet_with_applicability` may set applicability to `MaybeIncorrect` for
119+
// macro span, so we check applicability manually by comparing `recv` is not default.
120+
let recv = snippet(cx, recv.span, default_snip);
121+
122+
let applicability = if recv == default_snip {
123+
Applicability::HasPlaceholders
124+
} else {
125+
Applicability::MachineApplicable
126+
};
127+
128+
span_lint_and_sugg(
129+
cx,
130+
MANUAL_IS_ASCII_CHECK,
131+
span,
132+
"manual check for common ascii range",
133+
"try",
134+
format!("{recv}.{sugg}()"),
135+
applicability,
136+
);
137+
}
138+
}
139+
119140
fn check_pat(pat_kind: &PatKind<'_>) -> CharRange {
120141
match pat_kind {
121142
PatKind::Or(pats) => {

tests/ui/manual_is_ascii_check.fixed

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ fn main() {
1515
assert!('x'.is_ascii_alphabetic());
1616

1717
assert!(matches!('x', 'A'..='Z' | 'a'..='z' | '_'));
18+
19+
b'0'.is_ascii_digit();
20+
b'a'.is_ascii_lowercase();
21+
b'A'.is_ascii_uppercase();
22+
23+
'0'.is_ascii_digit();
24+
'a'.is_ascii_lowercase();
25+
'A'.is_ascii_uppercase();
26+
27+
let cool_letter = &'g';
28+
cool_letter.is_ascii_digit();
29+
cool_letter.is_ascii_lowercase();
30+
cool_letter.is_ascii_uppercase();
1831
}
1932

2033
#[clippy::msrv = "1.23"]

tests/ui/manual_is_ascii_check.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ fn main() {
1515
assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
1616

1717
assert!(matches!('x', 'A'..='Z' | 'a'..='z' | '_'));
18+
19+
(b'0'..=b'9').contains(&b'0');
20+
(b'a'..=b'z').contains(&b'a');
21+
(b'A'..=b'Z').contains(&b'A');
22+
23+
('0'..='9').contains(&'0');
24+
('a'..='z').contains(&'a');
25+
('A'..='Z').contains(&'A');
26+
27+
let cool_letter = &'g';
28+
('0'..='9').contains(cool_letter);
29+
('a'..='z').contains(cool_letter);
30+
('A'..='Z').contains(cool_letter);
1831
}
1932

2033
#[clippy::msrv = "1.23"]

tests/ui/manual_is_ascii_check.stderr

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,28 +43,82 @@ LL | assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
4343
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_alphabetic()`
4444

4545
error: manual check for common ascii range
46-
--> $DIR/manual_is_ascii_check.rs:29:13
46+
--> $DIR/manual_is_ascii_check.rs:19:5
47+
|
48+
LL | (b'0'..=b'9').contains(&b'0');
49+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'0'.is_ascii_digit()`
50+
51+
error: manual check for common ascii range
52+
--> $DIR/manual_is_ascii_check.rs:20:5
53+
|
54+
LL | (b'a'..=b'z').contains(&b'a');
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'a'.is_ascii_lowercase()`
56+
57+
error: manual check for common ascii range
58+
--> $DIR/manual_is_ascii_check.rs:21:5
59+
|
60+
LL | (b'A'..=b'Z').contains(&b'A');
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'A'.is_ascii_uppercase()`
62+
63+
error: manual check for common ascii range
64+
--> $DIR/manual_is_ascii_check.rs:23:5
65+
|
66+
LL | ('0'..='9').contains(&'0');
67+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'0'.is_ascii_digit()`
68+
69+
error: manual check for common ascii range
70+
--> $DIR/manual_is_ascii_check.rs:24:5
71+
|
72+
LL | ('a'..='z').contains(&'a');
73+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'a'.is_ascii_lowercase()`
74+
75+
error: manual check for common ascii range
76+
--> $DIR/manual_is_ascii_check.rs:25:5
77+
|
78+
LL | ('A'..='Z').contains(&'A');
79+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'A'.is_ascii_uppercase()`
80+
81+
error: manual check for common ascii range
82+
--> $DIR/manual_is_ascii_check.rs:28:5
83+
|
84+
LL | ('0'..='9').contains(cool_letter);
85+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cool_letter.is_ascii_digit()`
86+
87+
error: manual check for common ascii range
88+
--> $DIR/manual_is_ascii_check.rs:29:5
89+
|
90+
LL | ('a'..='z').contains(cool_letter);
91+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cool_letter.is_ascii_lowercase()`
92+
93+
error: manual check for common ascii range
94+
--> $DIR/manual_is_ascii_check.rs:30:5
95+
|
96+
LL | ('A'..='Z').contains(cool_letter);
97+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cool_letter.is_ascii_uppercase()`
98+
99+
error: manual check for common ascii range
100+
--> $DIR/manual_is_ascii_check.rs:42:13
47101
|
48102
LL | assert!(matches!(b'1', b'0'..=b'9'));
49103
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'1'.is_ascii_digit()`
50104

51105
error: manual check for common ascii range
52-
--> $DIR/manual_is_ascii_check.rs:30:13
106+
--> $DIR/manual_is_ascii_check.rs:43:13
53107
|
54108
LL | assert!(matches!('X', 'A'..='Z'));
55109
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'X'.is_ascii_uppercase()`
56110

57111
error: manual check for common ascii range
58-
--> $DIR/manual_is_ascii_check.rs:31:13
112+
--> $DIR/manual_is_ascii_check.rs:44:13
59113
|
60114
LL | assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
61115
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_alphabetic()`
62116

63117
error: manual check for common ascii range
64-
--> $DIR/manual_is_ascii_check.rs:41:23
118+
--> $DIR/manual_is_ascii_check.rs:54:23
65119
|
66120
LL | const FOO: bool = matches!('x', '0'..='9');
67121
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_digit()`
68122

69-
error: aborting due to 11 previous errors
123+
error: aborting due to 20 previous errors
70124

0 commit comments

Comments
 (0)