Skip to content

Commit 5344236

Browse files
committed
Auto merge of #8631 - Alexendoo:splitn-overlap, r=xFrednet
Remove overlap between `manual_split_once` and `needless_splitn` changelog: Remove overlap between [`manual_split_once`] and [`needless_splitn`]. Fixes some incorrect `rsplitn` suggestions for [`manual_split_once`] Things that can trigger `needless_splitn` no longer trigger `manual_split_once`, e.g. ```rust s.[r]splitn(2, '=').next(); s.[r]splitn(2, '=').nth(0); s.[r]splitn(3, '=').next_tuple(); ``` Fixes some suggestions: ```rust let s = "should not match"; s.rsplitn(2, '.').nth(1); // old -> Some("should not match") Some(s.rsplit_once('.').map_or(s, |x| x.0)); // new -> None s.rsplit_once('.').map(|x| x.0); s.rsplitn(2, '.').nth(1)?; // old -> "should not match" s.rsplit_once('.').map_or(s, |x| x.0); // new -> early returns s.rsplit_once('.')?.0; ```
2 parents c7e6863 + 6fba897 commit 5344236

9 files changed

+216
-259
lines changed

clippy_lints/src/methods/mod.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2574,12 +2574,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
25742574
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
25752575
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
25762576
suspicious_splitn::check(cx, name, expr, recv, count);
2577-
if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
2578-
str_splitn::check_manual_split_once(cx, name, expr, recv, pat_arg);
2579-
}
2580-
if count >= 2 {
2581-
str_splitn::check_needless_splitn(cx, name, expr, recv, pat_arg, count);
2582-
}
2577+
str_splitn::check(cx, name, expr, recv, pat_arg, count, msrv);
25832578
}
25842579
},
25852580
("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
Lines changed: 76 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,77 @@
11
use clippy_utils::consts::{constant, Constant};
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::source::snippet_with_context;
4-
use clippy_utils::{is_diag_item_method, match_def_path, paths};
4+
use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, paths};
55
use if_chain::if_chain;
66
use rustc_errors::Applicability;
77
use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath};
88
use rustc_lint::LateContext;
9-
use rustc_middle::ty::{self, adjustment::Adjust};
9+
use rustc_middle::ty;
10+
use rustc_semver::RustcVersion;
1011
use rustc_span::{symbol::sym, Span, SyntaxContext};
1112

12-
use super::MANUAL_SPLIT_ONCE;
13+
use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
1314

14-
pub(super) fn check_manual_split_once(
15+
pub(super) fn check(
1516
cx: &LateContext<'_>,
1617
method_name: &str,
1718
expr: &Expr<'_>,
1819
self_arg: &Expr<'_>,
1920
pat_arg: &Expr<'_>,
21+
count: u128,
22+
msrv: Option<&RustcVersion>,
2023
) {
21-
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
24+
if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
2225
return;
2326
}
2427

2528
let ctxt = expr.span.ctxt();
26-
let (method_name, msg, reverse) = if method_name == "splitn" {
27-
("split_once", "manual implementation of `split_once`", false)
28-
} else {
29-
("rsplit_once", "manual implementation of `rsplit_once`", true)
29+
let Some(usage) = parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id)) else { return };
30+
31+
let needless = match usage.kind {
32+
IterUsageKind::Nth(n) => count > n + 1,
33+
IterUsageKind::NextTuple => count > 2,
3034
};
31-
let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), reverse) {
32-
Some(x) => x,
33-
None => return,
35+
36+
if needless {
37+
let mut app = Applicability::MachineApplicable;
38+
let (r, message) = if method_name == "splitn" {
39+
("", "unnecessary use of `splitn`")
40+
} else {
41+
("r", "unnecessary use of `rsplitn`")
42+
};
43+
44+
span_lint_and_sugg(
45+
cx,
46+
NEEDLESS_SPLITN,
47+
expr.span,
48+
message,
49+
"try this",
50+
format!(
51+
"{}.{r}split({})",
52+
snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0,
53+
snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0,
54+
),
55+
app,
56+
);
57+
} else if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
58+
check_manual_split_once(cx, method_name, expr, self_arg, pat_arg, &usage);
59+
}
60+
}
61+
62+
fn check_manual_split_once(
63+
cx: &LateContext<'_>,
64+
method_name: &str,
65+
expr: &Expr<'_>,
66+
self_arg: &Expr<'_>,
67+
pat_arg: &Expr<'_>,
68+
usage: &IterUsage,
69+
) {
70+
let ctxt = expr.span.ctxt();
71+
let (msg, reverse) = if method_name == "splitn" {
72+
("manual implementation of `split_once`", false)
73+
} else {
74+
("manual implementation of `rsplit_once`", true)
3475
};
3576

3677
let mut app = Applicability::MachineApplicable;
@@ -39,77 +80,36 @@ pub(super) fn check_manual_split_once(
3980

4081
let sugg = match usage.kind {
4182
IterUsageKind::NextTuple => {
42-
format!("{}.{}({})", self_snip, method_name, pat_snip)
43-
},
44-
IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip),
45-
IterUsageKind::Next | IterUsageKind::Second => {
46-
let self_deref = {
47-
let adjust = cx.typeck_results().expr_adjustments(self_arg);
48-
if adjust.len() < 2 {
49-
String::new()
50-
} else if cx.typeck_results().expr_ty(self_arg).is_box()
51-
|| adjust
52-
.iter()
53-
.any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
54-
{
55-
format!("&{}", "*".repeat(adjust.len().saturating_sub(1)))
56-
} else {
57-
"*".repeat(adjust.len().saturating_sub(2))
58-
}
59-
};
60-
if matches!(usage.kind, IterUsageKind::Next) {
61-
match usage.unwrap_kind {
62-
Some(UnwrapKind::Unwrap) => {
63-
if reverse {
64-
format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip)
65-
} else {
66-
format!(
67-
"{}.{}({}).map_or({}{}, |x| x.0)",
68-
self_snip, method_name, pat_snip, self_deref, &self_snip
69-
)
70-
}
71-
},
72-
Some(UnwrapKind::QuestionMark) => {
73-
format!(
74-
"{}.{}({}).map_or({}{}, |x| x.0)",
75-
self_snip, method_name, pat_snip, self_deref, &self_snip
76-
)
77-
},
78-
None => {
79-
format!(
80-
"Some({}.{}({}).map_or({}{}, |x| x.0))",
81-
&self_snip, method_name, pat_snip, self_deref, &self_snip
82-
)
83-
},
84-
}
83+
if reverse {
84+
format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))")
8585
} else {
86-
match usage.unwrap_kind {
87-
Some(UnwrapKind::Unwrap) => {
88-
if reverse {
89-
// In this case, no better suggestion is offered.
90-
return;
91-
}
92-
format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip)
93-
},
94-
Some(UnwrapKind::QuestionMark) => {
95-
format!("{}.{}({})?.1", self_snip, method_name, pat_snip)
96-
},
97-
None => {
98-
format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip)
99-
},
100-
}
86+
format!("{self_snip}.split_once({pat_snip})")
10187
}
10288
},
89+
IterUsageKind::Nth(1) => {
90+
let (r, field) = if reverse { ("r", 0) } else { ("", 1) };
91+
92+
match usage.unwrap_kind {
93+
Some(UnwrapKind::Unwrap) => {
94+
format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}")
95+
},
96+
Some(UnwrapKind::QuestionMark) => {
97+
format!("{self_snip}.{r}split_once({pat_snip})?.{field}")
98+
},
99+
None => {
100+
format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})")
101+
},
102+
}
103+
},
104+
IterUsageKind::Nth(_) => return,
103105
};
104106

105107
span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
106108
}
107109

108110
enum IterUsageKind {
109-
Next,
110-
Second,
111+
Nth(u128),
111112
NextTuple,
112-
RNextTuple,
113113
}
114114

115115
enum UnwrapKind {
@@ -128,7 +128,6 @@ fn parse_iter_usage<'tcx>(
128128
cx: &LateContext<'tcx>,
129129
ctxt: SyntaxContext,
130130
mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
131-
reverse: bool,
132131
) -> Option<IterUsage> {
133132
let (kind, span) = match iter.next() {
134133
Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
@@ -141,13 +140,7 @@ fn parse_iter_usage<'tcx>(
141140
let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
142141

143142
match (name.ident.as_str(), args) {
144-
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
145-
if reverse {
146-
(IterUsageKind::Second, e.span)
147-
} else {
148-
(IterUsageKind::Next, e.span)
149-
}
150-
},
143+
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
151144
("next_tuple", []) => {
152145
return if_chain! {
153146
if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
@@ -157,7 +150,7 @@ fn parse_iter_usage<'tcx>(
157150
if subs.len() == 2;
158151
then {
159152
Some(IterUsage {
160-
kind: if reverse { IterUsageKind::RNextTuple } else { IterUsageKind::NextTuple },
153+
kind: IterUsageKind::NextTuple,
161154
span: e.span,
162155
unwrap_kind: None
163156
})
@@ -185,11 +178,7 @@ fn parse_iter_usage<'tcx>(
185178
}
186179
}
187180
};
188-
match if reverse { idx ^ 1 } else { idx } {
189-
0 => (IterUsageKind::Next, span),
190-
1 => (IterUsageKind::Second, span),
191-
_ => return None,
192-
}
181+
(IterUsageKind::Nth(idx), span)
193182
} else {
194183
return None;
195184
}
@@ -238,86 +227,3 @@ fn parse_iter_usage<'tcx>(
238227
span,
239228
})
240229
}
241-
242-
use super::NEEDLESS_SPLITN;
243-
244-
pub(super) fn check_needless_splitn(
245-
cx: &LateContext<'_>,
246-
method_name: &str,
247-
expr: &Expr<'_>,
248-
self_arg: &Expr<'_>,
249-
pat_arg: &Expr<'_>,
250-
count: u128,
251-
) {
252-
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
253-
return;
254-
}
255-
let ctxt = expr.span.ctxt();
256-
let mut app = Applicability::MachineApplicable;
257-
let (reverse, message) = if method_name == "splitn" {
258-
(false, "unnecessary use of `splitn`")
259-
} else {
260-
(true, "unnecessary use of `rsplitn`")
261-
};
262-
if_chain! {
263-
if count >= 2;
264-
if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count);
265-
then {
266-
span_lint_and_sugg(
267-
cx,
268-
NEEDLESS_SPLITN,
269-
expr.span,
270-
message,
271-
"try this",
272-
format!(
273-
"{}.{}({})",
274-
snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0,
275-
if reverse {"rsplit"} else {"split"},
276-
snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0
277-
),
278-
app,
279-
);
280-
}
281-
}
282-
}
283-
284-
fn check_iter<'tcx>(
285-
cx: &LateContext<'tcx>,
286-
ctxt: SyntaxContext,
287-
mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
288-
count: u128,
289-
) -> bool {
290-
match iter.next() {
291-
Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
292-
let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind {
293-
(name, args)
294-
} else {
295-
return false;
296-
};
297-
if_chain! {
298-
if let Some(did) = cx.typeck_results().type_dependent_def_id(e.hir_id);
299-
if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
300-
then {
301-
match (name.ident.as_str(), args) {
302-
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
303-
return true;
304-
},
305-
("next_tuple", []) if count > 2 => {
306-
return true;
307-
},
308-
("nth", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
309-
if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
310-
if count > idx + 1 {
311-
return true;
312-
}
313-
}
314-
},
315-
_ => return false,
316-
}
317-
}
318-
}
319-
},
320-
_ => return false,
321-
};
322-
false
323-
}

tests/ui/crashes/ice-8250.stderr

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
error: manual implementation of `split_once`
2-
--> $DIR/ice-8250.rs:2:13
3-
|
4-
LL | let _ = s[1..].splitn(2, '.').next()?;
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s[1..].split_once('.').map_or(s[1..], |x| x.0)`
6-
|
7-
= note: `-D clippy::manual-split-once` implied by `-D warnings`
8-
91
error: unnecessary use of `splitn`
102
--> $DIR/ice-8250.rs:2:13
113
|
@@ -14,5 +6,5 @@ LL | let _ = s[1..].splitn(2, '.').next()?;
146
|
157
= note: `-D clippy::needless-splitn` implied by `-D warnings`
168

17-
error: aborting due to 2 previous errors
9+
error: aborting due to previous error
1810

0 commit comments

Comments
 (0)