1 use clippy_utils::consts::{constant, Constant};
2 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
3 use clippy_utils::source::snippet_with_context;
4 use clippy_utils::usage::local_used_after_expr;
5 use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
6 use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths};
7 use core::ops::ControlFlow;
8 use if_chain::if_chain;
9 use rustc_errors::Applicability;
11 BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind,
13 use rustc_lint::LateContext;
15 use rustc_semver::RustcVersion;
16 use rustc_span::{sym, Span, Symbol, SyntaxContext};
18 use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
27 msrv: Option<RustcVersion>,
29 if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
33 let needless = |usage_kind| match usage_kind {
34 IterUsageKind::Nth(n) => count > n + 1,
35 IterUsageKind::NextTuple => count > 2,
37 let manual = count == 2 && meets_msrv(msrv, msrvs::STR_SPLIT_ONCE);
39 match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir().parent_iter(expr.hir_id)) {
40 Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg),
41 Some(usage) if manual => check_manual_split_once(cx, method_name, expr, self_arg, pat_arg, &usage),
43 check_manual_split_once_indirect(cx, method_name, expr, self_arg, pat_arg);
49 fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
50 let mut app = Applicability::MachineApplicable;
51 let r = if method_name == "splitn" { "" } else { "r" };
57 &format!("unnecessary use of `{r}splitn`"),
61 snippet_with_context(cx, self_arg.span, expr.span.ctxt(), "..", &mut app).0,
62 snippet_with_context(cx, pat_arg.span, expr.span.ctxt(), "..", &mut app).0,
68 fn check_manual_split_once(
76 let ctxt = expr.span.ctxt();
77 let (msg, reverse) = if method_name == "splitn" {
78 ("manual implementation of `split_once`", false)
80 ("manual implementation of `rsplit_once`", true)
83 let mut app = Applicability::MachineApplicable;
84 let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
85 let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
87 let sugg = match usage.kind {
88 IterUsageKind::NextTuple => {
90 format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))")
92 format!("{self_snip}.split_once({pat_snip})")
95 IterUsageKind::Nth(1) => {
96 let (r, field) = if reverse { ("r", 0) } else { ("", 1) };
98 match usage.unwrap_kind {
99 Some(UnwrapKind::Unwrap) => {
100 format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}")
102 Some(UnwrapKind::QuestionMark) => {
103 format!("{self_snip}.{r}split_once({pat_snip})?.{field}")
106 format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})")
110 IterUsageKind::Nth(_) => return,
113 span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
119 /// let mut iter = "a.b.c".splitn(2, '.');
120 /// let a = iter.next();
121 /// let b = iter.next();
123 fn check_manual_split_once_indirect(
124 cx: &LateContext<'_>,
130 let ctxt = expr.span.ctxt();
131 let mut parents = cx.tcx.hir().parent_iter(expr.hir_id);
132 if let (_, Node::Local(local)) = parents.next()?
133 && let PatKind::Binding(BindingAnnotation::MUT, iter_binding_id, iter_ident, None) = local.pat.kind
134 && let (iter_stmt_id, Node::Stmt(_)) = parents.next()?
135 && let (_, Node::Block(enclosing_block)) = parents.next()?
137 && let mut stmts = enclosing_block
140 .skip_while(|stmt| stmt.hir_id != iter_stmt_id)
143 && let first = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
144 && let second = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
145 && first.unwrap_kind == second.unwrap_kind
146 && first.name != second.name
147 && !local_used_after_expr(cx, iter_binding_id, second.init_expr)
149 let (r, lhs, rhs) = if method_name == "splitn" {
150 ("", first.name, second.name)
152 ("r", second.name, first.name)
154 let msg = format!("manual implementation of `{r}split_once`");
156 let mut app = Applicability::MachineApplicable;
157 let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
158 let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
160 span_lint_and_then(cx, MANUAL_SPLIT_ONCE, local.span, &msg, |diag| {
161 diag.span_label(first.span, "first usage here");
162 diag.span_label(second.span, "second usage here");
164 let unwrap = match first.unwrap_kind {
165 UnwrapKind::Unwrap => ".unwrap()",
166 UnwrapKind::QuestionMark => "?",
168 diag.span_suggestion_verbose(
170 &format!("try `{r}split_once`"),
171 format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
175 let remove_msg = format!("remove the `{iter_ident}` usages");
176 diag.span_suggestion(
182 diag.span_suggestion(
195 struct IndirectUsage<'a> {
198 init_expr: &'a Expr<'a>,
199 unwrap_kind: UnwrapKind,
202 /// returns `Some(IndirectUsage)` for e.g.
205 /// let name = binding.next()?;
206 /// let name = binding.next().unwrap();
208 fn indirect_usage<'tcx>(
209 cx: &LateContext<'tcx>,
213 ) -> Option<IndirectUsage<'tcx>> {
214 if let StmtKind::Local(&Local {
216 kind: PatKind::Binding(BindingAnnotation::NONE, _, ident, None),
219 init: Some(init_expr),
220 hir_id: local_hir_id,
224 let mut path_to_binding = None;
225 let _: Option<!> = for_each_expr_with_closures(cx, init_expr, |e| {
226 if path_to_local_id(e, binding) {
227 path_to_binding = Some(e);
229 ControlFlow::Continue(Descend::from(path_to_binding.is_none()))
232 let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id);
233 let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?;
235 let (parent_id, _) = parents.find(|(_, node)| {
239 kind: ExprKind::Match(.., MatchSource::TryDesugar),
246 kind: IterUsageKind::Nth(0),
247 unwrap_kind: Some(unwrap_kind),
251 if parent_id == local_hir_id {
252 return Some(IndirectUsage {
265 #[derive(Debug, Clone, Copy)]
271 #[derive(Debug, PartialEq, Eq)]
280 unwrap_kind: Option<UnwrapKind>,
284 #[allow(clippy::too_many_lines)]
285 fn parse_iter_usage<'tcx>(
286 cx: &LateContext<'tcx>,
288 mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
289 ) -> Option<IterUsage> {
290 let (kind, span) = match iter.next() {
291 Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
292 let ExprKind::MethodCall(name, _, [args @ ..], _) = e.kind else {
295 let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
296 let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
298 match (name.ident.as_str(), args) {
299 ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
300 ("next_tuple", []) => {
302 if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
303 if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
304 if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did());
305 if let ty::Tuple(subs) = subs.type_at(0).kind();
309 kind: IterUsageKind::NextTuple,
318 ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
319 if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
320 let span = if name.ident.as_str() == "nth" {
324 if let Some((_, Node::Expr(next_expr))) = iter.next();
325 if let ExprKind::MethodCall(next_name, _, [], _) = next_expr.kind;
326 if next_name.ident.name == sym::next;
327 if next_expr.span.ctxt() == ctxt;
328 if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
329 if cx.tcx.trait_of_item(next_id) == Some(iter_id);
337 (IterUsageKind::Nth(idx), span)
348 let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
352 kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)),
357 let parent_span = e.span.parent_callsite().unwrap();
358 if parent_span.ctxt() == ctxt {
359 (Some(UnwrapKind::QuestionMark), parent_span)
364 _ if e.span.ctxt() != ctxt => (None, span),
365 ExprKind::MethodCall(name, _, [], _)
366 if name.ident.name == sym::unwrap
369 .type_dependent_def_id(e.hir_id)
370 .map_or(false, |id| is_diag_item_method(cx, id, sym::Option)) =>
372 (Some(UnwrapKind::Unwrap), e.span)