Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1fc1aee

Browse files
committedAug 17, 2021
Auto merge of #7565 - Jarcho:manual_split_once, r=llogiq
New lint `manual_split_once` This is a WIP because it still needs to recognize more patterns. Currently handles: ```rust s.splitn(2, ' ').next(); s.splitn(2, ' ').nth(0) s.splitn(2, ' ').nth(1); s.splitn(2, ' ').skip(0).next(); s.splitn(2, ' ').skip(1).next(); s.splitn(2, ' ').next_tuple(); // from itertools // as well as `unwrap()` and `?` forms ``` Still to do: ```rust let mut iter = s.splitn(2, ' '); (iter.next().unwrap(), iter.next()?) let mut iter = s.splitn(2, ' '); let key = iter.next().unwrap(); let value = iter.next()?; ``` Suggestions on other patterns to check for would be useful. I've done a search on github for uses of `splitn`. Still have yet to actually look through the results. There's also the question of whether the lint shouold trigger on all uses of `splitn` with two values, or only on recognized usages. The former could have false positives where it couldn't be replaced, but I'm not sure how common that would be. changelog: Add lint `manual_split_once`
2 parents cde7e6b + aab3267 commit 1fc1aee

23 files changed

+538
-120
lines changed
 

‎CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2754,6 +2754,7 @@ Released 2018-09-13
27542754
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
27552755
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
27562756
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
2757+
[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
27572758
[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
27582759
[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
27592760
[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap

‎clippy_lints/src/bytecount.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::source::snippet_with_applicability;
33
use clippy_utils::ty::match_type;
4-
use clippy_utils::visitors::LocalUsedVisitor;
4+
use clippy_utils::visitors::is_local_used;
55
use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs};
66
use if_chain::if_chain;
77
use rustc_errors::Applicability;
@@ -65,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
6565
return;
6666
};
6767
if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind();
68-
if !LocalUsedVisitor::new(cx, arg_id).check_expr(needle);
68+
if !is_local_used(cx, needle, arg_id);
6969
then {
7070
let haystack = if let ExprKind::MethodCall(path, _, args, _) =
7171
filter_recv.kind {

‎clippy_lints/src/collapsible_match.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::visitors::LocalUsedVisitor;
2+
use clippy_utils::visitors::is_local_used;
33
use clippy_utils::{is_lang_ctor, path_to_local, peel_ref_operators, SpanlessEq};
44
use if_chain::if_chain;
55
use rustc_hir::LangItem::OptionNone;
@@ -83,13 +83,12 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext
8383
// the "wild-like" branches must be equal
8484
if SpanlessEq::new(cx).eq_expr(wild_inner_arm.body, wild_outer_arm.body);
8585
// the binding must not be used in the if guard
86-
let mut used_visitor = LocalUsedVisitor::new(cx, binding_id);
8786
if match arm.guard {
8887
None => true,
89-
Some(Guard::If(expr) | Guard::IfLet(_, expr)) => !used_visitor.check_expr(expr),
88+
Some(Guard::If(expr) | Guard::IfLet(_, expr)) => !is_local_used(cx, expr, binding_id),
9089
};
9190
// ...or anywhere in the inner match
92-
if !arms_inner.iter().any(|arm| used_visitor.check_arm(arm));
91+
if !arms_inner.iter().any(|arm| is_local_used(cx, arm, binding_id));
9392
then {
9493
span_lint_and_then(
9594
cx,

‎clippy_lints/src/let_if_seq.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::source::snippet;
3-
use clippy_utils::{path_to_local_id, visitors::LocalUsedVisitor};
3+
use clippy_utils::{path_to_local_id, visitors::is_local_used};
44
use if_chain::if_chain;
55
use rustc_errors::Applicability;
66
use rustc_hir as hir;
@@ -65,11 +65,10 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
6565
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
6666
if let hir::StmtKind::Expr(if_) = expr.kind;
6767
if let hir::ExprKind::If(cond, then, ref else_) = if_.kind;
68-
let mut used_visitor = LocalUsedVisitor::new(cx, canonical_id);
69-
if !used_visitor.check_expr(cond);
68+
if !is_local_used(cx, cond, canonical_id);
7069
if let hir::ExprKind::Block(then, _) = then.kind;
7170
if let Some(value) = check_assign(cx, canonical_id, &*then);
72-
if !used_visitor.check_expr(value);
71+
if !is_local_used(cx, value, canonical_id);
7372
then {
7473
let span = stmt.span.to(if_.span);
7574

@@ -148,15 +147,13 @@ fn check_assign<'tcx>(
148147
if let hir::ExprKind::Assign(var, value, _) = expr.kind;
149148
if path_to_local_id(var, decl);
150149
then {
151-
let mut v = LocalUsedVisitor::new(cx, decl);
152-
153-
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| v.check_stmt(stmt)) {
154-
return None;
150+
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) {
151+
None
152+
} else {
153+
Some(value)
155154
}
156-
157-
return Some(value);
155+
} else {
156+
None
158157
}
159158
}
160-
161-
None
162159
}

‎clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
773773
methods::MANUAL_FILTER_MAP,
774774
methods::MANUAL_FIND_MAP,
775775
methods::MANUAL_SATURATING_ARITHMETIC,
776+
methods::MANUAL_SPLIT_ONCE,
776777
methods::MANUAL_STR_REPEAT,
777778
methods::MAP_COLLECT_RESULT_UNIT,
778779
methods::MAP_FLATTEN,
@@ -1319,6 +1320,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13191320
LintId::of(methods::MANUAL_FILTER_MAP),
13201321
LintId::of(methods::MANUAL_FIND_MAP),
13211322
LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
1323+
LintId::of(methods::MANUAL_SPLIT_ONCE),
13221324
LintId::of(methods::MANUAL_STR_REPEAT),
13231325
LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
13241326
LintId::of(methods::MAP_IDENTITY),
@@ -1617,6 +1619,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16171619
LintId::of(methods::ITER_COUNT),
16181620
LintId::of(methods::MANUAL_FILTER_MAP),
16191621
LintId::of(methods::MANUAL_FIND_MAP),
1622+
LintId::of(methods::MANUAL_SPLIT_ONCE),
16201623
LintId::of(methods::MAP_IDENTITY),
16211624
LintId::of(methods::OPTION_AS_REF_DEREF),
16221625
LintId::of(methods::OPTION_FILTER_MAP),

‎clippy_lints/src/loops/for_kv_map.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
33
use clippy_utils::source::snippet;
44
use clippy_utils::sugg;
55
use clippy_utils::ty::is_type_diagnostic_item;
6-
use clippy_utils::visitors::LocalUsedVisitor;
6+
use clippy_utils::visitors::is_local_used;
77
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
88
use rustc_lint::LateContext;
99
use rustc_middle::ty;
@@ -66,9 +66,7 @@ pub(super) fn check<'tcx>(
6666
fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
6767
match *pat {
6868
PatKind::Wild => true,
69-
PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => {
70-
!LocalUsedVisitor::new(cx, id).check_expr(body)
71-
},
69+
PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id),
7270
_ => false,
7371
}
7472
}

‎clippy_lints/src/loops/manual_flatten.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::utils::make_iterator_snippet;
22
use super::MANUAL_FLATTEN;
33
use clippy_utils::diagnostics::span_lint_and_then;
4-
use clippy_utils::visitors::LocalUsedVisitor;
4+
use clippy_utils::visitors::is_local_used;
55
use clippy_utils::{is_lang_ctor, path_to_local_id};
66
use if_chain::if_chain;
77
use rustc_errors::Applicability;
@@ -49,7 +49,7 @@ pub(super) fn check<'tcx>(
4949
let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
5050
if some_ctor || ok_ctor;
5151
// Ensure epxr in `if let` is not used afterwards
52-
if !LocalUsedVisitor::new(cx, pat_hir_id).check_arm(true_arm);
52+
if !is_local_used(cx, true_arm, pat_hir_id);
5353
then {
5454
let if_let_type = if some_ctor { "Some" } else { "Ok" };
5555
// Prepare the error message

‎clippy_lints/src/loops/needless_range_loop.rs

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ use super::NEEDLESS_RANGE_LOOP;
22
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
33
use clippy_utils::source::snippet;
44
use clippy_utils::ty::has_iter_method;
5-
use clippy_utils::visitors::LocalUsedVisitor;
6-
use clippy_utils::{
7-
contains_name, higher, is_integer_const, match_trait_method, path_to_local_id, paths, sugg, SpanlessEq,
8-
};
5+
use clippy_utils::visitors::is_local_used;
6+
use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq};
97
use if_chain::if_chain;
108
use rustc_ast::ast;
119
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -256,43 +254,36 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
256254
if let ExprKind::Path(ref seqpath) = seqexpr.kind;
257255
if let QPath::Resolved(None, seqvar) = *seqpath;
258256
if seqvar.segments.len() == 1;
259-
let index_used_directly = path_to_local_id(idx, self.var);
260-
let indexed_indirectly = {
261-
let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var);
262-
walk_expr(&mut used_visitor, idx);
263-
used_visitor.used
264-
};
265-
if indexed_indirectly || index_used_directly;
257+
if is_local_used(self.cx, idx, self.var);
266258
then {
267259
if self.prefer_mutable {
268260
self.indexed_mut.insert(seqvar.segments[0].ident.name);
269261
}
262+
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
270263
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
271264
match res {
272265
Res::Local(hir_id) => {
273266
let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
274267
let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id);
275268
let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id);
276-
if indexed_indirectly {
277-
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
278-
}
279269
if index_used_directly {
280270
self.indexed_directly.insert(
281271
seqvar.segments[0].ident.name,
282272
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
283273
);
274+
} else {
275+
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
284276
}
285277
return false; // no need to walk further *on the variable*
286278
}
287279
Res::Def(DefKind::Static | DefKind::Const, ..) => {
288-
if indexed_indirectly {
289-
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
290-
}
291280
if index_used_directly {
292281
self.indexed_directly.insert(
293282
seqvar.segments[0].ident.name,
294283
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
295284
);
285+
} else {
286+
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
296287
}
297288
return false; // no need to walk further *on the variable*
298289
}

‎clippy_lints/src/matches.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use clippy_utils::diagnostics::{
55
use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
66
use clippy_utils::sugg::Sugg;
77
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
8-
use clippy_utils::visitors::LocalUsedVisitor;
8+
use clippy_utils::visitors::is_local_used;
99
use clippy_utils::{
1010
get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_wild, meets_msrv, msrvs,
1111
path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks,
@@ -953,9 +953,7 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
953953
// Looking for unused bindings (i.e.: `_e`)
954954
for pat in inner.iter() {
955955
if let PatKind::Binding(_, id, ident, None) = pat.kind {
956-
if ident.as_str().starts_with('_')
957-
&& !LocalUsedVisitor::new(cx, id).check_expr(arm.body)
958-
{
956+
if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) {
959957
ident_bind_name = (&ident.name.as_str()).to_string();
960958
matching_wild = true;
961959
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
use clippy_utils::consts::{constant, Constant};
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::source::snippet_with_context;
4+
use clippy_utils::{is_diag_item_method, match_def_path, paths};
5+
use if_chain::if_chain;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath};
8+
use rustc_lint::LateContext;
9+
use rustc_middle::ty::{self, adjustment::Adjust};
10+
use rustc_span::{symbol::sym, Span, SyntaxContext};
11+
12+
use super::MANUAL_SPLIT_ONCE;
13+
14+
pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
15+
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
16+
return;
17+
}
18+
19+
let ctxt = expr.span.ctxt();
20+
let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id)) {
21+
Some(x) => x,
22+
None => return,
23+
};
24+
let (method_name, msg) = if method_name == "splitn" {
25+
("split_once", "manual implementation of `split_once`")
26+
} else {
27+
("rsplit_once", "manual implementation of `rsplit_once`")
28+
};
29+
30+
let mut app = Applicability::MachineApplicable;
31+
let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
32+
let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
33+
34+
match usage.kind {
35+
IterUsageKind::NextTuple => {
36+
span_lint_and_sugg(
37+
cx,
38+
MANUAL_SPLIT_ONCE,
39+
usage.span,
40+
msg,
41+
"try this",
42+
format!("{}.{}({})", self_snip, method_name, pat_snip),
43+
app,
44+
);
45+
},
46+
IterUsageKind::Next => {
47+
let self_deref = {
48+
let adjust = cx.typeck_results().expr_adjustments(self_arg);
49+
if adjust.is_empty() {
50+
String::new()
51+
} else if cx.typeck_results().expr_ty(self_arg).is_box()
52+
|| adjust
53+
.iter()
54+
.any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
55+
{
56+
format!("&{}", "*".repeat(adjust.len() - 1))
57+
} else {
58+
"*".repeat(adjust.len() - 2)
59+
}
60+
};
61+
let sugg = if usage.unwrap_kind.is_some() {
62+
format!(
63+
"{}.{}({}).map_or({}{}, |x| x.0)",
64+
&self_snip, method_name, pat_snip, self_deref, &self_snip
65+
)
66+
} else {
67+
format!(
68+
"Some({}.{}({}).map_or({}{}, |x| x.0))",
69+
&self_snip, method_name, pat_snip, self_deref, &self_snip
70+
)
71+
};
72+
73+
span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
74+
},
75+
IterUsageKind::Second => {
76+
let access_str = match usage.unwrap_kind {
77+
Some(UnwrapKind::Unwrap) => ".unwrap().1",
78+
Some(UnwrapKind::QuestionMark) => "?.1",
79+
None => ".map(|x| x.1)",
80+
};
81+
span_lint_and_sugg(
82+
cx,
83+
MANUAL_SPLIT_ONCE,
84+
usage.span,
85+
msg,
86+
"try this",
87+
format!("{}.{}({}){}", self_snip, method_name, pat_snip, access_str),
88+
app,
89+
);
90+
},
91+
}
92+
}
93+
94+
enum IterUsageKind {
95+
Next,
96+
Second,
97+
NextTuple,
98+
}
99+
100+
enum UnwrapKind {
101+
Unwrap,
102+
QuestionMark,
103+
}
104+
105+
struct IterUsage {
106+
kind: IterUsageKind,
107+
unwrap_kind: Option<UnwrapKind>,
108+
span: Span,
109+
}
110+
111+
fn parse_iter_usage(
112+
cx: &LateContext<'tcx>,
113+
ctxt: SyntaxContext,
114+
mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
115+
) -> Option<IterUsage> {
116+
let (kind, span) = match iter.next() {
117+
Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
118+
let (name, args) = if let ExprKind::MethodCall(name, _, [_, args @ ..], _) = e.kind {
119+
(name, args)
120+
} else {
121+
return None;
122+
};
123+
let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
124+
let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
125+
126+
match (&*name.ident.as_str(), args) {
127+
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Next, e.span),
128+
("next_tuple", []) => {
129+
if_chain! {
130+
if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
131+
if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
132+
if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did);
133+
if let ty::Tuple(subs) = subs.type_at(0).kind();
134+
if subs.len() == 2;
135+
then {
136+
return Some(IterUsage { kind: IterUsageKind::NextTuple, span: e.span, unwrap_kind: None });
137+
} else {
138+
return None;
139+
}
140+
}
141+
},
142+
("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
143+
if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
144+
let span = if name.ident.as_str() == "nth" {
145+
e.span
146+
} else {
147+
if_chain! {
148+
if let Some((_, Node::Expr(next_expr))) = iter.next();
149+
if let ExprKind::MethodCall(next_name, _, [_], _) = next_expr.kind;
150+
if next_name.ident.name == sym::next;
151+
if next_expr.span.ctxt() == ctxt;
152+
if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
153+
if cx.tcx.trait_of_item(next_id) == Some(iter_id);
154+
then {
155+
next_expr.span
156+
} else {
157+
return None;
158+
}
159+
}
160+
};
161+
match idx {
162+
0 => (IterUsageKind::Next, span),
163+
1 => (IterUsageKind::Second, span),
164+
_ => return None,
165+
}
166+
} else {
167+
return None;
168+
}
169+
},
170+
_ => return None,
171+
}
172+
},
173+
_ => return None,
174+
};
175+
176+
let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
177+
match e.kind {
178+
ExprKind::Call(
179+
Expr {
180+
kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, _)),
181+
..
182+
},
183+
_,
184+
) => {
185+
let parent_span = e.span.parent().unwrap();
186+
if parent_span.ctxt() == ctxt {
187+
(Some(UnwrapKind::QuestionMark), parent_span)
188+
} else {
189+
(None, span)
190+
}
191+
},
192+
_ if e.span.ctxt() != ctxt => (None, span),
193+
ExprKind::MethodCall(name, _, [_], _)
194+
if name.ident.name == sym::unwrap
195+
&& cx
196+
.typeck_results()
197+
.type_dependent_def_id(e.hir_id)
198+
.map_or(false, |id| is_diag_item_method(cx, id, sym::option_type)) =>
199+
{
200+
(Some(UnwrapKind::Unwrap), e.span)
201+
},
202+
_ => (None, span),
203+
}
204+
} else {
205+
(None, span)
206+
};
207+
208+
Some(IterUsage {
209+
kind,
210+
unwrap_kind,
211+
span,
212+
})
213+
}

‎clippy_lints/src/methods/mod.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod iter_nth_zero;
3333
mod iter_skip_next;
3434
mod iterator_step_by_zero;
3535
mod manual_saturating_arithmetic;
36+
mod manual_split_once;
3637
mod manual_str_repeat;
3738
mod map_collect_result_unit;
3839
mod map_flatten;
@@ -64,6 +65,7 @@ mod wrong_self_convention;
6465
mod zst_offset;
6566

6667
use bind_instead_of_map::BindInsteadOfMap;
68+
use clippy_utils::consts::{constant, Constant};
6769
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
6870
use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item};
6971
use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, meets_msrv, msrvs, paths, return_ty};
@@ -1771,6 +1773,29 @@ declare_clippy_lint! {
17711773
"manual implementation of `str::repeat`"
17721774
}
17731775

1776+
declare_clippy_lint! {
1777+
/// **What it does:** Checks for usages of `str::splitn(2, _)`
1778+
///
1779+
/// **Why is this bad?** `split_once` is both clearer in intent and slightly more efficient.
1780+
///
1781+
/// **Known problems:** None.
1782+
///
1783+
/// **Example:**
1784+
///
1785+
/// ```rust,ignore
1786+
/// // Bad
1787+
/// let (key, value) = _.splitn(2, '=').next_tuple()?;
1788+
/// let value = _.splitn(2, '=').nth(1)?;
1789+
///
1790+
/// // Good
1791+
/// let (key, value) = _.split_once('=')?;
1792+
/// let value = _.split_once('=')?.1;
1793+
/// ```
1794+
pub MANUAL_SPLIT_ONCE,
1795+
complexity,
1796+
"replace `.splitn(2, pat)` with `.split_once(pat)`"
1797+
}
1798+
17741799
pub struct Methods {
17751800
avoid_breaking_exported_api: bool,
17761801
msrv: Option<RustcVersion>,
@@ -1848,7 +1873,8 @@ impl_lint_pass!(Methods => [
18481873
IMPLICIT_CLONE,
18491874
SUSPICIOUS_SPLITN,
18501875
MANUAL_STR_REPEAT,
1851-
EXTEND_WITH_DRAIN
1876+
EXTEND_WITH_DRAIN,
1877+
MANUAL_SPLIT_ONCE
18521878
]);
18531879

18541880
/// Extracts a method call name, args, and `Span` of the method name.
@@ -2176,8 +2202,18 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
21762202
unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
21772203
}
21782204
},
2179-
("splitn" | "splitn_mut" | "rsplitn" | "rsplitn_mut", [count_arg, _]) => {
2180-
suspicious_splitn::check(cx, name, expr, recv, count_arg);
2205+
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
2206+
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
2207+
suspicious_splitn::check(cx, name, expr, recv, count);
2208+
if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
2209+
manual_split_once::check(cx, name, expr, recv, pat_arg);
2210+
}
2211+
}
2212+
},
2213+
("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
2214+
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
2215+
suspicious_splitn::check(cx, name, expr, recv, count);
2216+
}
21812217
},
21822218
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
21832219
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {

‎clippy_lints/src/methods/suspicious_splitn.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use clippy_utils::consts::{constant, Constant};
21
use clippy_utils::diagnostics::span_lint_and_note;
32
use if_chain::if_chain;
43
use rustc_ast::LitKind;
@@ -8,25 +7,18 @@ use rustc_span::source_map::Spanned;
87

98
use super::SUSPICIOUS_SPLITN;
109

11-
pub(super) fn check(
12-
cx: &LateContext<'_>,
13-
method_name: &str,
14-
expr: &Expr<'_>,
15-
self_arg: &Expr<'_>,
16-
count_arg: &Expr<'_>,
17-
) {
10+
pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) {
1811
if_chain! {
19-
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg);
2012
if count <= 1;
2113
if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
2214
if let Some(impl_id) = cx.tcx.impl_of_method(call_id);
2315
let lang_items = cx.tcx.lang_items();
2416
if lang_items.slice_impl() == Some(impl_id) || lang_items.str_impl() == Some(impl_id);
2517
then {
2618
// Ignore empty slice and string literals when used with a literal count.
27-
if (matches!(self_arg.kind, ExprKind::Array([]))
19+
if matches!(self_arg.kind, ExprKind::Array([]))
2820
|| matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty())
29-
) && matches!(count_arg.kind, ExprKind::Lit(_))
21+
3022
{
3123
return;
3224
}

‎clippy_lints/src/unused_self.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_help;
2-
use clippy_utils::visitors::LocalUsedVisitor;
2+
use clippy_utils::visitors::is_local_used;
33
use if_chain::if_chain;
44
use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind};
55
use rustc_lint::{LateContext, LateLintPass};
@@ -50,8 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
5050
if let ImplItemKind::Fn(.., body_id) = &impl_item.kind;
5151
let body = cx.tcx.hir().body(*body_id);
5252
if let [self_param, ..] = body.params;
53-
let self_hir_id = self_param.pat.hir_id;
54-
if !LocalUsedVisitor::new(cx, self_hir_id).check_body(body);
53+
if !is_local_used(cx, body, self_param.pat.hir_id);
5554
then {
5655
span_lint_and_help(
5756
cx,

‎clippy_lints/src/utils/conf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ define_Conf! {
132132
///
133133
/// Suppress lints whenever the suggested change would cause breakage for other crates.
134134
(avoid_breaking_exported_api: bool = true),
135-
/// Lint: MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE.
135+
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE.
136136
///
137137
/// The minimum rust version that the project supports
138138
(msrv: Option<String> = None),

‎clippy_utils/src/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
2121
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
2222
&option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
2323
// extract just major + minor version and ignore patch versions
24-
format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
24+
format!("rust-{}", n.rsplit_once('.').unwrap().1)
2525
}),
2626
lint
2727
));

‎clippy_utils/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![feature(in_band_lifetimes)]
33
#![feature(iter_zip)]
44
#![feature(rustc_private)]
5+
#![feature(control_flow_enum)]
56
#![recursion_limit = "512"]
67
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
78
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]

‎clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ macro_rules! msrv_aliases {
1313
// names may refer to stabilized feature flags or library items
1414
msrv_aliases! {
1515
1,53,0 { OR_PATTERNS }
16+
1,52,0 { STR_SPLIT_ONCE }
1617
1,50,0 { BOOL_THEN }
1718
1,46,0 { CONST_IF_MATCH }
1819
1,45,0 { STR_STRIP_PREFIX }

‎clippy_utils/src/paths.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
6868
pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
6969
pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
7070
pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
71+
pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
7172
#[cfg(feature = "internal-lints")]
7273
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
7374
#[cfg(feature = "internal-lints")]

‎clippy_utils/src/visitors.rs

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visito
44
use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt};
55
use rustc_lint::LateContext;
66
use rustc_middle::hir::map::Map;
7+
use std::ops::ControlFlow;
78

89
/// returns `true` if expr contains match expr desugared from try
910
fn contains_try(expr: &hir::Expr<'_>) -> bool {
@@ -133,62 +134,6 @@ where
133134
}
134135
}
135136

136-
pub struct LocalUsedVisitor<'hir> {
137-
hir: Map<'hir>,
138-
pub local_hir_id: HirId,
139-
pub used: bool,
140-
}
141-
142-
impl<'hir> LocalUsedVisitor<'hir> {
143-
pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
144-
Self {
145-
hir: cx.tcx.hir(),
146-
local_hir_id,
147-
used: false,
148-
}
149-
}
150-
151-
fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
152-
visit(self, t);
153-
std::mem::replace(&mut self.used, false)
154-
}
155-
156-
pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
157-
self.check(arm, Self::visit_arm)
158-
}
159-
160-
pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
161-
self.check(body, Self::visit_body)
162-
}
163-
164-
pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
165-
self.check(expr, Self::visit_expr)
166-
}
167-
168-
pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
169-
self.check(stmt, Self::visit_stmt)
170-
}
171-
}
172-
173-
impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
174-
type Map = Map<'v>;
175-
176-
fn visit_expr(&mut self, expr: &'v Expr<'v>) {
177-
if self.used {
178-
return;
179-
}
180-
if path_to_local_id(expr, self.local_hir_id) {
181-
self.used = true;
182-
} else {
183-
walk_expr(self, expr);
184-
}
185-
}
186-
187-
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
188-
NestedVisitorMap::OnlyBodies(self.hir)
189-
}
190-
}
191-
192137
/// A type which can be visited.
193138
pub trait Visitable<'tcx> {
194139
/// Calls the corresponding `visit_*` function on the visitor.
@@ -203,7 +148,22 @@ macro_rules! visitable_ref {
203148
}
204149
};
205150
}
151+
visitable_ref!(Arm, visit_arm);
206152
visitable_ref!(Block, visit_block);
153+
visitable_ref!(Body, visit_body);
154+
visitable_ref!(Expr, visit_expr);
155+
visitable_ref!(Stmt, visit_stmt);
156+
157+
// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
158+
// where
159+
// I::Item: Visitable<'tcx>,
160+
// {
161+
// fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
162+
// for x in self {
163+
// x.visit(visitor);
164+
// }
165+
// }
166+
// }
207167

208168
/// Calls the given function for each break expression.
209169
pub fn visit_break_exprs<'tcx>(
@@ -260,3 +220,48 @@ pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
260220
v.visit_expr(&cx.tcx.hir().body(body).value);
261221
v.found
262222
}
223+
224+
/// Calls the given function for each usage of the given local.
225+
pub fn for_each_local_usage<'tcx, B>(
226+
cx: &LateContext<'tcx>,
227+
visitable: impl Visitable<'tcx>,
228+
id: HirId,
229+
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
230+
) -> ControlFlow<B> {
231+
struct V<'tcx, B, F> {
232+
map: Map<'tcx>,
233+
id: HirId,
234+
f: F,
235+
res: ControlFlow<B>,
236+
}
237+
impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>> Visitor<'tcx> for V<'tcx, B, F> {
238+
type Map = Map<'tcx>;
239+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
240+
NestedVisitorMap::OnlyBodies(self.map)
241+
}
242+
243+
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
244+
if self.res.is_continue() {
245+
if path_to_local_id(e, self.id) {
246+
self.res = (self.f)(e);
247+
} else {
248+
walk_expr(self, e);
249+
}
250+
}
251+
}
252+
}
253+
254+
let mut v = V {
255+
map: cx.tcx.hir(),
256+
id,
257+
f,
258+
res: ControlFlow::CONTINUE,
259+
};
260+
visitable.visit(&mut v);
261+
v.res
262+
}
263+
264+
/// Checks if the given local is used.
265+
pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
266+
for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break()
267+
}

‎tests/compile-test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ fn third_party_crates() -> String {
3939
"clippy_lints",
4040
"clippy_utils",
4141
"if_chain",
42+
"itertools",
4243
"quote",
4344
"regex",
4445
"serde",

‎tests/ui/manual_split_once.fixed

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// run-rustfix
2+
3+
#![feature(custom_inner_attributes)]
4+
#![warn(clippy::manual_split_once)]
5+
#![allow(clippy::iter_skip_next, clippy::iter_nth_zero)]
6+
7+
extern crate itertools;
8+
9+
#[allow(unused_imports)]
10+
use itertools::Itertools;
11+
12+
fn main() {
13+
let _ = Some("key=value".split_once('=').map_or("key=value", |x| x.0));
14+
let _ = "key=value".splitn(2, '=').nth(2);
15+
let _ = "key=value".split_once('=').map_or("key=value", |x| x.0);
16+
let _ = "key=value".split_once('=').map_or("key=value", |x| x.0);
17+
let _ = "key=value".split_once('=').unwrap().1;
18+
let _ = "key=value".split_once('=').unwrap().1;
19+
let (_, _) = "key=value".split_once('=').unwrap();
20+
21+
let s = String::from("key=value");
22+
let _ = s.split_once('=').map_or(&*s, |x| x.0);
23+
24+
let s = Box::<str>::from("key=value");
25+
let _ = s.split_once('=').map_or(&*s, |x| x.0);
26+
27+
let s = &"key=value";
28+
let _ = s.split_once('=').map_or(*s, |x| x.0);
29+
30+
fn _f(s: &str) -> Option<&str> {
31+
let _ = s.split_once("key=value").map_or(s, |x| x.0);
32+
let _ = s.split_once("key=value")?.1;
33+
let _ = s.split_once("key=value")?.1;
34+
None
35+
}
36+
37+
// Don't lint, slices don't have `split_once`
38+
let _ = [0, 1, 2].splitn(2, |&x| x == 1).nth(1).unwrap();
39+
}
40+
41+
fn _msrv_1_51() {
42+
#![clippy::msrv = "1.51"]
43+
// `str::split_once` was stabilized in 1.16. Do not lint this
44+
let _ = "key=value".splitn(2, '=').nth(1).unwrap();
45+
}
46+
47+
fn _msrv_1_52() {
48+
#![clippy::msrv = "1.52"]
49+
let _ = "key=value".split_once('=').unwrap().1;
50+
}

‎tests/ui/manual_split_once.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// run-rustfix
2+
3+
#![feature(custom_inner_attributes)]
4+
#![warn(clippy::manual_split_once)]
5+
#![allow(clippy::iter_skip_next, clippy::iter_nth_zero)]
6+
7+
extern crate itertools;
8+
9+
#[allow(unused_imports)]
10+
use itertools::Itertools;
11+
12+
fn main() {
13+
let _ = "key=value".splitn(2, '=').next();
14+
let _ = "key=value".splitn(2, '=').nth(2);
15+
let _ = "key=value".splitn(2, '=').next().unwrap();
16+
let _ = "key=value".splitn(2, '=').nth(0).unwrap();
17+
let _ = "key=value".splitn(2, '=').nth(1).unwrap();
18+
let _ = "key=value".splitn(2, '=').skip(1).next().unwrap();
19+
let (_, _) = "key=value".splitn(2, '=').next_tuple().unwrap();
20+
21+
let s = String::from("key=value");
22+
let _ = s.splitn(2, '=').next().unwrap();
23+
24+
let s = Box::<str>::from("key=value");
25+
let _ = s.splitn(2, '=').nth(0).unwrap();
26+
27+
let s = &"key=value";
28+
let _ = s.splitn(2, '=').skip(0).next().unwrap();
29+
30+
fn _f(s: &str) -> Option<&str> {
31+
let _ = s.splitn(2, "key=value").next()?;
32+
let _ = s.splitn(2, "key=value").nth(1)?;
33+
let _ = s.splitn(2, "key=value").skip(1).next()?;
34+
None
35+
}
36+
37+
// Don't lint, slices don't have `split_once`
38+
let _ = [0, 1, 2].splitn(2, |&x| x == 1).nth(1).unwrap();
39+
}
40+
41+
fn _msrv_1_51() {
42+
#![clippy::msrv = "1.51"]
43+
// `str::split_once` was stabilized in 1.16. Do not lint this
44+
let _ = "key=value".splitn(2, '=').nth(1).unwrap();
45+
}
46+
47+
fn _msrv_1_52() {
48+
#![clippy::msrv = "1.52"]
49+
let _ = "key=value".splitn(2, '=').nth(1).unwrap();
50+
}

‎tests/ui/manual_split_once.stderr

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
error: manual implementation of `split_once`
2+
--> $DIR/manual_split_once.rs:13:13
3+
|
4+
LL | let _ = "key=value".splitn(2, '=').next();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `Some("key=value".split_once('=').map_or("key=value", |x| x.0))`
6+
|
7+
= note: `-D clippy::manual-split-once` implied by `-D warnings`
8+
9+
error: manual implementation of `split_once`
10+
--> $DIR/manual_split_once.rs:15:13
11+
|
12+
LL | let _ = "key=value".splitn(2, '=').next().unwrap();
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').map_or("key=value", |x| x.0)`
14+
15+
error: manual implementation of `split_once`
16+
--> $DIR/manual_split_once.rs:16:13
17+
|
18+
LL | let _ = "key=value".splitn(2, '=').nth(0).unwrap();
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').map_or("key=value", |x| x.0)`
20+
21+
error: manual implementation of `split_once`
22+
--> $DIR/manual_split_once.rs:17:13
23+
|
24+
LL | let _ = "key=value".splitn(2, '=').nth(1).unwrap();
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1`
26+
27+
error: manual implementation of `split_once`
28+
--> $DIR/manual_split_once.rs:18:13
29+
|
30+
LL | let _ = "key=value".splitn(2, '=').skip(1).next().unwrap();
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1`
32+
33+
error: manual implementation of `split_once`
34+
--> $DIR/manual_split_once.rs:19:18
35+
|
36+
LL | let (_, _) = "key=value".splitn(2, '=').next_tuple().unwrap();
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=')`
38+
39+
error: manual implementation of `split_once`
40+
--> $DIR/manual_split_once.rs:22:13
41+
|
42+
LL | let _ = s.splitn(2, '=').next().unwrap();
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once('=').map_or(&*s, |x| x.0)`
44+
45+
error: manual implementation of `split_once`
46+
--> $DIR/manual_split_once.rs:25:13
47+
|
48+
LL | let _ = s.splitn(2, '=').nth(0).unwrap();
49+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once('=').map_or(&*s, |x| x.0)`
50+
51+
error: manual implementation of `split_once`
52+
--> $DIR/manual_split_once.rs:28:13
53+
|
54+
LL | let _ = s.splitn(2, '=').skip(0).next().unwrap();
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once('=').map_or(*s, |x| x.0)`
56+
57+
error: manual implementation of `split_once`
58+
--> $DIR/manual_split_once.rs:31:17
59+
|
60+
LL | let _ = s.splitn(2, "key=value").next()?;
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value").map_or(s, |x| x.0)`
62+
63+
error: manual implementation of `split_once`
64+
--> $DIR/manual_split_once.rs:32:17
65+
|
66+
LL | let _ = s.splitn(2, "key=value").nth(1)?;
67+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value")?.1`
68+
69+
error: manual implementation of `split_once`
70+
--> $DIR/manual_split_once.rs:33:17
71+
|
72+
LL | let _ = s.splitn(2, "key=value").skip(1).next()?;
73+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value")?.1`
74+
75+
error: manual implementation of `split_once`
76+
--> $DIR/manual_split_once.rs:49:13
77+
|
78+
LL | let _ = "key=value".splitn(2, '=').nth(1).unwrap();
79+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1`
80+
81+
error: aborting due to 13 previous errors
82+

0 commit comments

Comments
 (0)
Please sign in to comment.