Skip to content

Commit 06aae81

Browse files
committed
In string_patterns, use span instead of char to reduce allocation and complexity and some nits changes.
1 parent db89f1d commit 06aae81

4 files changed

+74
-49
lines changed

clippy_lints/src/string_patterns.rs

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
use std::cmp::Ordering;
22
use std::ops::ControlFlow;
33

4-
use clippy_utils::diagnostics::span_lint_and_sugg;
4+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
55
use clippy_utils::macros::matching_root_macro_call;
66
use clippy_utils::path_to_local_id;
7-
use clippy_utils::source::str_literal_to_char_literal;
7+
use clippy_utils::source::{snippet_opt, str_literal_to_char_literal};
88
use clippy_utils::visitors::{for_each_expr, Descend};
9+
use itertools::Itertools;
910
use rustc_ast::{BinOpKind, LitKind};
1011
use rustc_errors::Applicability;
1112
use rustc_hir::{Expr, ExprKind, PatKind, QPath};
1213
use rustc_lint::{LateContext, LateLintPass};
1314
use rustc_middle::ty;
1415
use rustc_session::declare_lint_pass;
15-
use rustc_span::sym;
16+
use rustc_span::{sym, Span};
1617

1718
declare_clippy_lint! {
1819
/// ### What it does
@@ -110,17 +111,17 @@ fn check_single_char_pattern_lint(cx: &LateContext<'_>, arg: &Expr<'_>) {
110111
}
111112
}
112113

113-
fn get_char_value(expr: &Expr<'_>) -> Option<String> {
114+
fn get_char_span<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Span> {
115+
if !cx.typeck_results().expr_ty_adjusted(expr).is_char() || expr.span.from_expansion() {
116+
return None;
117+
}
114118
match expr.kind {
115-
ExprKind::Lit(lit) => match lit.node {
116-
LitKind::Char(c) => Some(format!("'{}'", c.escape_default())),
117-
_ => None,
118-
},
119+
ExprKind::Lit(lit) if let LitKind::Char(_) = lit.node => Some(lit.span),
119120
ExprKind::Path(QPath::Resolved(_, path)) => {
120121
if path.segments.len() == 1 {
121122
let segment = &path.segments[0];
122123
if segment.args.is_none() {
123-
Some(segment.ident.name.to_string())
124+
Some(segment.ident.span)
124125
} else {
125126
None
126127
}
@@ -138,44 +139,39 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<
138139
&& let body = cx.tcx.hir().body(closure.body)
139140
&& let PatKind::Binding(_, binding, ..) = body.params[0].pat.kind
140141
{
141-
let mut set_chars: Vec<String> = Vec::new();
142+
let mut set_char_spans: Vec<Span> = Vec::new();
142143

143144
// We want to retrieve all the comparisons done.
144145
// They are ordered in a nested way and so we need to traverse the AST to collect them all.
145146
if for_each_expr(body.value, |sub_expr| -> ControlFlow<(), Descend> {
146147
match sub_expr.kind {
147148
ExprKind::Binary(op, left, right) if op.node == BinOpKind::Eq => {
148149
if path_to_local_id(left, binding)
149-
&& cx.typeck_results().expr_ty_adjusted(right).kind() == &ty::Char
150-
&& let Some(c) = get_char_value(right)
150+
&& let Some(span) = get_char_span(cx, right)
151151
{
152-
set_chars.push(c);
152+
set_char_spans.push(span);
153153
ControlFlow::Continue(Descend::No)
154154
} else if path_to_local_id(right, binding)
155-
&& cx.typeck_results().expr_ty_adjusted(left).kind() == &ty::Char
156-
&& let Some(c) = get_char_value(left)
155+
&& let Some(span) = get_char_span(cx, left)
157156
{
158-
set_chars.push(c);
157+
set_char_spans.push(span);
159158
ControlFlow::Continue(Descend::No)
160159
} else {
161160
ControlFlow::Break(())
162161
}
163162
},
164163
ExprKind::Binary(op, _, _) if op.node == BinOpKind::Or => ControlFlow::Continue(Descend::Yes),
165164
ExprKind::Match(match_value, [arm, _], _) => {
166-
if matching_root_macro_call(cx, sub_expr.span, sym::matches_macro).is_none() {
167-
return ControlFlow::Break(());
168-
}
169-
if arm.guard.is_some() {
170-
return ControlFlow::Break(());
171-
}
172-
if !path_to_local_id(match_value, binding) {
165+
if matching_root_macro_call(cx, sub_expr.span, sym::matches_macro).is_none()
166+
|| arm.guard.is_some()
167+
|| !path_to_local_id(match_value, binding)
168+
{
173169
return ControlFlow::Break(());
174170
}
175171
if arm.pat.walk_short(|pat| match pat.kind {
176172
PatKind::Lit(expr) if let ExprKind::Lit(lit) = expr.kind => {
177-
if let LitKind::Char(c) = lit.node {
178-
set_chars.push(format!("'{}'", c.escape_default()));
173+
if let LitKind::Char(_) = lit.node {
174+
set_char_spans.push(lit.span);
179175
}
180176
true
181177
},
@@ -194,27 +190,37 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<
194190
{
195191
return;
196192
}
197-
match set_chars.len().cmp(&1) {
198-
Ordering::Equal => span_lint_and_sugg(
199-
cx,
200-
MANUAL_PATTERN_CHAR_COMPARISON,
201-
method_arg.span,
202-
"this manual char comparison can be written more succinctly",
203-
"consider using a `char`",
204-
set_chars[0].clone(),
205-
applicability,
206-
),
207-
Ordering::Greater => span_lint_and_sugg(
208-
cx,
209-
MANUAL_PATTERN_CHAR_COMPARISON,
210-
method_arg.span,
211-
"this manual char comparison can be written more succinctly",
212-
"consider using an array of `char`",
213-
format!("[{}]", set_chars.join(", ")),
214-
applicability,
215-
),
216-
Ordering::Less => {},
217-
}
193+
span_lint_and_then(
194+
cx,
195+
MANUAL_PATTERN_CHAR_COMPARISON,
196+
method_arg.span,
197+
"this manual char comparison can be written more succinctly",
198+
|diag| match set_char_spans.len().cmp(&1) {
199+
Ordering::Equal => {
200+
diag.span_suggestion(
201+
method_arg.span,
202+
"consider using a `char`",
203+
snippet_opt(cx, set_char_spans[0]).unwrap(),
204+
applicability,
205+
);
206+
},
207+
Ordering::Greater => {
208+
diag.span_suggestion(
209+
method_arg.span,
210+
"consider using an array of `char`",
211+
format!(
212+
"[{}]",
213+
set_char_spans
214+
.into_iter()
215+
.map(|span| snippet_opt(cx, span).unwrap())
216+
.join(", ")
217+
),
218+
applicability,
219+
);
220+
},
221+
Ordering::Less => {},
222+
},
223+
)
218224
}
219225
}
220226

@@ -228,9 +234,8 @@ impl<'tcx> LateLintPass<'tcx> for StringPatterns {
228234
&& let Some(&(_, pos)) = PATTERN_METHODS
229235
.iter()
230236
.find(|(array_method_name, _)| *array_method_name == method_name)
231-
&& args.len() > pos
237+
&& let Some(arg) = args.get(pos)
232238
{
233-
let arg = &args[pos];
234239
check_single_char_pattern_lint(cx, arg);
235240

236241
check_manual_pattern_char_comparison(cx, arg);

tests/ui/manual_pattern_char_comparison.fixed

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ fn main() {
1818
sentence.split(['\n', 'X', 'Y']);
1919
sentence.splitn(3, 'X');
2020
sentence.splitn(3, ['X', 'W']);
21+
sentence.find('🎈');
2122

2223
let not_str = NotStr;
2324
not_str.find(|c: char| c == 'X');
@@ -39,4 +40,10 @@ fn main() {
3940

4041
"".find(|c| matches!(c, 'a' | '1'..'4'));
4142
"".find(|c| c == 'a' || matches!(c, '1'..'4'));
43+
macro_rules! m {
44+
($e:expr) => {
45+
$e == '?'
46+
};
47+
}
48+
"".find(|c| m!(c));
4249
}

tests/ui/manual_pattern_char_comparison.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ fn main() {
1818
sentence.split(|c: char| matches!(c, '\n' | 'X' | 'Y'));
1919
sentence.splitn(3, |c: char| matches!(c, 'X'));
2020
sentence.splitn(3, |c: char| matches!(c, 'X' | 'W'));
21+
sentence.find(|c| c == '🎈');
2122

2223
let not_str = NotStr;
2324
not_str.find(|c: char| c == 'X');
@@ -39,4 +40,10 @@ fn main() {
3940

4041
"".find(|c| matches!(c, 'a' | '1'..'4'));
4142
"".find(|c| c == 'a' || matches!(c, '1'..'4'));
43+
macro_rules! m {
44+
($e:expr) => {
45+
$e == '?'
46+
};
47+
}
48+
"".find(|c| m!(c));
4249
}

tests/ui/manual_pattern_char_comparison.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,11 @@ error: this manual char comparison can be written more succinctly
4949
LL | sentence.splitn(3, |c: char| matches!(c, 'X' | 'W'));
5050
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an array of `char`: `['X', 'W']`
5151

52-
error: aborting due to 8 previous errors
52+
error: this manual char comparison can be written more succinctly
53+
--> tests/ui/manual_pattern_char_comparison.rs:21:19
54+
|
55+
LL | sentence.find(|c| c == '🎈');
56+
| ^^^^^^^^^^^^^ help: consider using a `char`: `'🎈'`
57+
58+
error: aborting due to 9 previous errors
5359

0 commit comments

Comments
 (0)