Skip to content

Commit cbdc36a

Browse files
committed
skip warning when generic evolved;
suggest explicit type when its inferred in closure
1 parent c6bf954 commit cbdc36a

File tree

4 files changed

+151
-31
lines changed

4 files changed

+151
-31
lines changed

clippy_lints/src/manual_is_ascii_check.rs

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use clippy_config::msrvs::{self, Msrv};
2-
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::diagnostics::span_lint_and_then;
33
use clippy_utils::macros::matching_root_macro_call;
44
use clippy_utils::sugg::Sugg;
5-
use clippy_utils::{higher, in_constant};
5+
use clippy_utils::{higher, in_constant, path_to_local, peel_ref_operators};
66
use rustc_ast::ast::RangeLimits;
77
use rustc_ast::LitKind::{Byte, Char};
88
use rustc_errors::Applicability;
9-
use rustc_hir::{BorrowKind, Expr, ExprKind, PatKind, RangeEnd};
9+
use rustc_hir::{Expr, ExprKind, Node, Param, PatKind, RangeEnd};
1010
use rustc_lint::{LateContext, LateLintPass};
11+
use rustc_middle::ty;
1112
use rustc_session::impl_lint_pass;
1213
use rustc_span::{sym, Span};
1314

@@ -99,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
99100
if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) {
100101
if let ExprKind::Match(recv, [arm, ..], _) = expr.kind {
101102
let range = check_pat(&arm.pat.kind);
102-
check_is_ascii(cx, macro_call.span, recv, &range);
103+
check_is_ascii(cx, macro_call.span, recv, &range, None);
103104
}
104105
} else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind
105106
&& path.ident.name == sym!(contains)
@@ -108,42 +109,67 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
108109
end: Some(end),
109110
limits: RangeLimits::Closed,
110111
}) = higher::Range::hir(receiver)
112+
&& !matches!(cx.typeck_results().expr_ty(arg).peel_refs().kind(), ty::Param(_))
111113
{
114+
let arg = peel_ref_operators(cx, arg);
115+
let ty_sugg = get_ty_sugg(cx, arg, start);
112116
let range = check_range(start, end);
113-
if let ExprKind::AddrOf(BorrowKind::Ref, _, e) = arg.kind {
114-
check_is_ascii(cx, expr.span, e, &range);
115-
} else {
116-
check_is_ascii(cx, expr.span, arg, &range);
117-
}
117+
check_is_ascii(cx, expr.span, arg, &range, ty_sugg);
118118
}
119119
}
120120

121121
extract_msrv_attr!(LateContext);
122122
}
123123

124-
fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &CharRange) {
125-
if let Some(sugg) = match range {
126-
CharRange::UpperChar => Some("is_ascii_uppercase"),
127-
CharRange::LowerChar => Some("is_ascii_lowercase"),
128-
CharRange::FullChar => Some("is_ascii_alphabetic"),
129-
CharRange::Digit => Some("is_ascii_digit"),
130-
CharRange::HexDigit => Some("is_ascii_hexdigit"),
131-
CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => None,
132-
} {
133-
let default_snip = "..";
134-
let mut app = Applicability::MachineApplicable;
135-
let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par();
124+
fn get_ty_sugg(cx: &LateContext<'_>, arg: &Expr<'_>, bound_expr: &Expr<'_>) -> Option<(Span, &'static str)> {
125+
if let ExprKind::Lit(lit) = bound_expr.kind
126+
&& let local_hid = path_to_local(arg)?
127+
&& let Node::Param(Param { ty_span, span, .. }) = cx.tcx.parent_hir_node(local_hid)
128+
// `ty_span` and `span` are the same for inferred type, thus a type suggestion must be given
129+
&& ty_span == span
130+
{
131+
let ty_str = match lit.node {
132+
Char(_) => "char",
133+
Byte(_) => "u8",
134+
_ => return None,
135+
};
136+
return Some((*ty_span, ty_str));
137+
}
138+
None
139+
}
136140

137-
span_lint_and_sugg(
138-
cx,
139-
MANUAL_IS_ASCII_CHECK,
140-
span,
141-
"manual check for common ascii range",
142-
"try",
143-
format!("{recv}.{sugg}()"),
144-
app,
145-
);
141+
fn check_is_ascii(
142+
cx: &LateContext<'_>,
143+
span: Span,
144+
recv: &Expr<'_>,
145+
range: &CharRange,
146+
ty_sugg: Option<(Span, &'_ str)>,
147+
) {
148+
let sugg = match range {
149+
CharRange::UpperChar => "is_ascii_uppercase",
150+
CharRange::LowerChar => "is_ascii_lowercase",
151+
CharRange::FullChar => "is_ascii_alphabetic",
152+
CharRange::Digit => "is_ascii_digit",
153+
CharRange::HexDigit => "is_ascii_hexdigit",
154+
CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => return,
155+
};
156+
let default_snip = "..";
157+
let mut app = Applicability::MachineApplicable;
158+
let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par();
159+
let mut suggestion = vec![(span, format!("{recv}.{sugg}()"))];
160+
if let Some((ty_span, ty_str)) = ty_sugg {
161+
suggestion.push((ty_span, format!("{recv}: {ty_str}")));
146162
}
163+
164+
span_lint_and_then(
165+
cx,
166+
MANUAL_IS_ASCII_CHECK,
167+
span,
168+
"manual check for common ascii range",
169+
|diag| {
170+
diag.multipart_suggestion("try", suggestion, app);
171+
},
172+
);
147173
}
148174

149175
fn check_pat(pat_kind: &PatKind<'_>) -> CharRange {

tests/ui/manual_is_ascii_check.fixed

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,30 @@ fn msrv_1_47() {
5555
const FOO: bool = 'x'.is_ascii_digit();
5656
const BAR: bool = 'x'.is_ascii_hexdigit();
5757
}
58+
59+
#[allow(clippy::deref_addrof, clippy::needless_borrow)]
60+
fn with_refs() {
61+
let cool_letter = &&'g';
62+
cool_letter.is_ascii_digit();
63+
cool_letter.is_ascii_lowercase();
64+
}
65+
66+
fn generics() {
67+
fn a<U>(u: &U) -> bool
68+
where
69+
char: PartialOrd<U>,
70+
U: PartialOrd<char> + ?Sized,
71+
{
72+
('A'..='Z').contains(u)
73+
}
74+
75+
fn take_while<Item, F>(cond: F)
76+
where
77+
Item: Sized,
78+
F: Fn(Item) -> bool,
79+
{
80+
}
81+
take_while(|c: char| c.is_ascii_uppercase());
82+
take_while(|c: u8| c.is_ascii_uppercase());
83+
take_while(|c: char| c.is_ascii_uppercase());
84+
}

tests/ui/manual_is_ascii_check.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,30 @@ fn msrv_1_47() {
5555
const FOO: bool = matches!('x', '0'..='9');
5656
const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
5757
}
58+
59+
#[allow(clippy::deref_addrof, clippy::needless_borrow)]
60+
fn with_refs() {
61+
let cool_letter = &&'g';
62+
('0'..='9').contains(&&cool_letter);
63+
('a'..='z').contains(*cool_letter);
64+
}
65+
66+
fn generics() {
67+
fn a<U>(u: &U) -> bool
68+
where
69+
char: PartialOrd<U>,
70+
U: PartialOrd<char> + ?Sized,
71+
{
72+
('A'..='Z').contains(u)
73+
}
74+
75+
fn take_while<Item, F>(cond: F)
76+
where
77+
Item: Sized,
78+
F: Fn(Item) -> bool,
79+
{
80+
}
81+
take_while(|c| ('A'..='Z').contains(&c));
82+
take_while(|c| (b'A'..=b'Z').contains(&c));
83+
take_while(|c: char| ('A'..='Z').contains(&c));
84+
}

tests/ui/manual_is_ascii_check.stderr

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,45 @@ error: manual check for common ascii range
133133
LL | const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
134134
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_hexdigit()`
135135

136-
error: aborting due to 22 previous errors
136+
error: manual check for common ascii range
137+
--> tests/ui/manual_is_ascii_check.rs:62:5
138+
|
139+
LL | ('0'..='9').contains(&&cool_letter);
140+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cool_letter.is_ascii_digit()`
141+
142+
error: manual check for common ascii range
143+
--> tests/ui/manual_is_ascii_check.rs:63:5
144+
|
145+
LL | ('a'..='z').contains(*cool_letter);
146+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cool_letter.is_ascii_lowercase()`
147+
148+
error: manual check for common ascii range
149+
--> tests/ui/manual_is_ascii_check.rs:81:20
150+
|
151+
LL | take_while(|c| ('A'..='Z').contains(&c));
152+
| ^^^^^^^^^^^^^^^^^^^^^^^^
153+
|
154+
help: try
155+
|
156+
LL | take_while(|c: char| c.is_ascii_uppercase());
157+
| ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
158+
159+
error: manual check for common ascii range
160+
--> tests/ui/manual_is_ascii_check.rs:82:20
161+
|
162+
LL | take_while(|c| (b'A'..=b'Z').contains(&c));
163+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
164+
|
165+
help: try
166+
|
167+
LL | take_while(|c: u8| c.is_ascii_uppercase());
168+
| ~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
169+
170+
error: manual check for common ascii range
171+
--> tests/ui/manual_is_ascii_check.rs:83:26
172+
|
173+
LL | take_while(|c: char| ('A'..='Z').contains(&c));
174+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `c.is_ascii_uppercase()`
175+
176+
error: aborting due to 27 previous errors
137177

0 commit comments

Comments
 (0)