Skip to content

Commit 936d19c

Browse files
committed
skip warning when generic evolved;
suggest explicit type when its inferred in closure
1 parent 7e650b7 commit 936d19c

File tree

4 files changed

+168
-17
lines changed

4 files changed

+168
-17
lines changed

clippy_lints/src/manual_is_ascii_check.rs

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
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::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;
7-
use rustc_ast::LitKind::{Byte, Char};
7+
use rustc_ast::LitKind::{self, Byte, Char};
8+
use rustc_data_structures::fx::FxHashMap;
89
use rustc_errors::Applicability;
9-
use rustc_hir::{BorrowKind, Expr, ExprKind, PatKind, RangeEnd};
10+
use rustc_hir::{BodyId, Closure, Expr, ExprKind, FnDecl, HirId, PatKind, RangeEnd, TyKind};
1011
use rustc_lint::{LateContext, LateLintPass};
12+
use rustc_middle::ty;
1113
use rustc_session::impl_lint_pass;
1214
use rustc_span::def_id::DefId;
1315
use rustc_span::{sym, Span};
@@ -59,12 +61,16 @@ impl_lint_pass!(ManualIsAsciiCheck => [MANUAL_IS_ASCII_CHECK]);
5961

6062
pub struct ManualIsAsciiCheck {
6163
msrv: Msrv,
64+
parent_closure_params_map: FxHashMap<HirId, Span>,
6265
}
6366

6467
impl ManualIsAsciiCheck {
6568
#[must_use]
6669
pub fn new(msrv: Msrv) -> Self {
67-
Self { msrv }
70+
Self {
71+
msrv,
72+
parent_closure_params_map: FxHashMap::default(),
73+
}
6874
}
6975
}
7076

@@ -97,12 +103,16 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
97103
return;
98104
}
99105

106+
if let ExprKind::Closure(Closure { fn_decl, body, .. }) = expr.kind {
107+
self.parent_closure_params_map = params_with_inferred_ty_map(cx, *body, fn_decl);
108+
}
109+
100110
if let Some(macro_call) = root_macro_call(expr.span)
101111
&& is_matches_macro(cx, macro_call.def_id)
102112
{
103113
if let ExprKind::Match(recv, [arm, ..], _) = expr.kind {
104114
let range = check_pat(&arm.pat.kind);
105-
check_is_ascii(cx, macro_call.span, recv, &range);
115+
check_is_ascii(cx, macro_call.span, recv, &range, None);
106116
}
107117
} else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind
108118
&& path.ident.name == sym!(contains)
@@ -112,19 +122,58 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
112122
limits: RangeLimits::Closed,
113123
}) = higher::Range::hir(receiver)
114124
{
115-
let range = check_range(start, end);
116-
if let ExprKind::AddrOf(BorrowKind::Ref, _, e) = arg.kind {
117-
check_is_ascii(cx, expr.span, e, &range);
118-
} else {
119-
check_is_ascii(cx, expr.span, arg, &range);
125+
let arg_ty = cx.typeck_results().expr_ty(arg).peel_refs();
126+
if matches!(arg_ty.kind(), ty::Param(_)) {
127+
return;
120128
}
129+
130+
let arg = peel_ref_operators(cx, arg);
131+
let ty_sugg = get_ty_sugg(arg, &self.parent_closure_params_map, start);
132+
let range = check_range(start, end);
133+
check_is_ascii(cx, expr.span, peel_ref_operators(cx, arg), &range, ty_sugg);
134+
}
135+
}
136+
137+
fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
138+
if let ExprKind::Closure(_) = expr.kind {
139+
self.parent_closure_params_map = FxHashMap::default();
121140
}
122141
}
123142

124143
extract_msrv_attr!(LateContext);
125144
}
126145

127-
fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &CharRange) {
146+
fn get_ty_sugg(arg: &Expr<'_>, map: &FxHashMap<HirId, Span>, range_expr: &Expr<'_>) -> Option<(Span, &'static str)> {
147+
let ExprKind::Lit(lit) = range_expr.kind else {
148+
return None;
149+
};
150+
let sugg_ty_span = path_to_local(arg).and_then(|id| map.get(&id)).copied()?;
151+
let sugg_ty_str = match lit.node {
152+
LitKind::Char(_) => "char",
153+
LitKind::Byte(_) => "u8",
154+
_ => return None,
155+
};
156+
157+
Some((sugg_ty_span, sugg_ty_str))
158+
}
159+
160+
fn params_with_inferred_ty_map(cx: &LateContext<'_>, body_id: BodyId, fn_decl: &FnDecl<'_>) -> FxHashMap<HirId, Span> {
161+
let params = cx.tcx.hir().body(body_id).params.iter();
162+
let fn_decl_input_tys = fn_decl.inputs.iter();
163+
// Could just collect params with `span` == `ty_span` but this should be safer
164+
params
165+
.zip(fn_decl_input_tys)
166+
.filter_map(|(param, ty)| {
167+
if let TyKind::Infer = ty.kind {
168+
Some((param.pat.hir_id, param.span))
169+
} else {
170+
None
171+
}
172+
})
173+
.collect()
174+
}
175+
176+
fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &CharRange, ty_sugg: Option<(Span, &str)>) {
128177
if let Some(sugg) = match range {
129178
CharRange::UpperChar => Some("is_ascii_uppercase"),
130179
CharRange::LowerChar => Some("is_ascii_lowercase"),
@@ -137,14 +186,22 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha
137186
let mut app = Applicability::MachineApplicable;
138187
let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par();
139188

140-
span_lint_and_sugg(
189+
span_lint_and_then(
141190
cx,
142191
MANUAL_IS_ASCII_CHECK,
143192
span,
144193
"manual check for common ascii range",
145-
"try",
146-
format!("{recv}.{sugg}()"),
147-
app,
194+
|diag| {
195+
diag.span_suggestion(span, "try", format!("{recv}.{sugg}()"), app);
196+
if let Some((ty_span, ty_str)) = ty_sugg {
197+
diag.span_suggestion(
198+
ty_span,
199+
"also make sure to label the correct type",
200+
format!("{recv}: {ty_str}"),
201+
app,
202+
);
203+
}
204+
},
148205
);
149206
}
150207
}

tests/ui/manual_is_ascii_check.fixed

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,29 @@ 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+
}

tests/ui/manual_is_ascii_check.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,29 @@ 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+
}

tests/ui/manual_is_ascii_check.stderr

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,47 @@ 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+
--> $DIR/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+
--> $DIR/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+
--> $DIR/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| c.is_ascii_uppercase());
157+
| ~~~~~~~~~~~~~~~~~~~~~~
158+
help: also make sure to label the correct type
159+
|
160+
LL | take_while(|c: char| ('A'..='Z').contains(&c));
161+
| ~~~~~~~
162+
163+
error: manual check for common ascii range
164+
--> $DIR/manual_is_ascii_check.rs:82:20
165+
|
166+
LL | take_while(|c| (b'A'..=b'Z').contains(&c));
167+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
168+
|
169+
help: try
170+
|
171+
LL | take_while(|c| c.is_ascii_uppercase());
172+
| ~~~~~~~~~~~~~~~~~~~~~~
173+
help: also make sure to label the correct type
174+
|
175+
LL | take_while(|c: u8| (b'A'..=b'Z').contains(&c));
176+
| ~~~~~
177+
178+
error: aborting due to 26 previous errors
137179

0 commit comments

Comments
 (0)