| use clippy_config::msrvs::{self, Msrv}; |
| use clippy_utils::consts::{constant, Constant}; |
| use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; |
| use clippy_utils::source::snippet_with_context; |
| use clippy_utils::usage::local_used_after_expr; |
| use clippy_utils::visitors::{for_each_expr_with_closures, Descend}; |
| use clippy_utils::{is_diag_item_method, match_def_path, path_to_local_id, paths}; |
| use core::ops::ControlFlow; |
| use if_chain::if_chain; |
| use rustc_errors::Applicability; |
| use rustc_hir::{ |
| BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind, |
| }; |
| use rustc_lint::LateContext; |
| use rustc_middle::ty; |
| use rustc_span::{sym, Span, Symbol, SyntaxContext}; |
| |
| use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN}; |
| |
| pub(super) fn check( |
| cx: &LateContext<'_>, |
| method_name: &str, |
| expr: &Expr<'_>, |
| self_arg: &Expr<'_>, |
| pat_arg: &Expr<'_>, |
| count: u128, |
| msrv: &Msrv, |
| ) { |
| if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() { |
| return; |
| } |
| |
| let needless = |usage_kind| match usage_kind { |
| IterUsageKind::Nth(n) => count > n + 1, |
| IterUsageKind::NextTuple => count > 2, |
| }; |
| let manual = count == 2 && msrv.meets(msrvs::STR_SPLIT_ONCE); |
| |
| match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir().parent_iter(expr.hir_id)) { |
| Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg), |
| Some(usage) if manual => check_manual_split_once(cx, method_name, expr, self_arg, pat_arg, &usage), |
| None if manual => { |
| check_manual_split_once_indirect(cx, method_name, expr, self_arg, pat_arg); |
| }, |
| _ => {}, |
| } |
| } |
| |
| fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) { |
| let mut app = Applicability::MachineApplicable; |
| let r = if method_name == "splitn" { "" } else { "r" }; |
| |
| span_lint_and_sugg( |
| cx, |
| NEEDLESS_SPLITN, |
| expr.span, |
| &format!("unnecessary use of `{r}splitn`"), |
| "try", |
| format!( |
| "{}.{r}split({})", |
| snippet_with_context(cx, self_arg.span, expr.span.ctxt(), "..", &mut app).0, |
| snippet_with_context(cx, pat_arg.span, expr.span.ctxt(), "..", &mut app).0, |
| ), |
| app, |
| ); |
| } |
| |
| fn check_manual_split_once( |
| cx: &LateContext<'_>, |
| method_name: &str, |
| expr: &Expr<'_>, |
| self_arg: &Expr<'_>, |
| pat_arg: &Expr<'_>, |
| usage: &IterUsage, |
| ) { |
| let ctxt = expr.span.ctxt(); |
| let (msg, reverse) = if method_name == "splitn" { |
| ("manual implementation of `split_once`", false) |
| } else { |
| ("manual implementation of `rsplit_once`", true) |
| }; |
| |
| let mut app = Applicability::MachineApplicable; |
| let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0; |
| let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0; |
| |
| let sugg = match usage.kind { |
| IterUsageKind::NextTuple => { |
| if reverse { |
| format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))") |
| } else { |
| format!("{self_snip}.split_once({pat_snip})") |
| } |
| }, |
| IterUsageKind::Nth(1) => { |
| let (r, field) = if reverse { ("r", 0) } else { ("", 1) }; |
| |
| match usage.unwrap_kind { |
| Some(UnwrapKind::Unwrap) => { |
| format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}") |
| }, |
| Some(UnwrapKind::QuestionMark) => { |
| format!("{self_snip}.{r}split_once({pat_snip})?.{field}") |
| }, |
| None => { |
| format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})") |
| }, |
| } |
| }, |
| IterUsageKind::Nth(_) => return, |
| }; |
| |
| span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try", sugg, app); |
| } |
| |
| /// checks for |
| /// |
| /// ```no_run |
| /// let mut iter = "a.b.c".splitn(2, '.'); |
| /// let a = iter.next(); |
| /// let b = iter.next(); |
| /// ``` |
| fn check_manual_split_once_indirect( |
| cx: &LateContext<'_>, |
| method_name: &str, |
| expr: &Expr<'_>, |
| self_arg: &Expr<'_>, |
| pat_arg: &Expr<'_>, |
| ) -> Option<()> { |
| let ctxt = expr.span.ctxt(); |
| let mut parents = cx.tcx.hir().parent_iter(expr.hir_id); |
| if let (_, Node::Local(local)) = parents.next()? |
| && let PatKind::Binding(BindingAnnotation::MUT, iter_binding_id, iter_ident, None) = local.pat.kind |
| && let (iter_stmt_id, Node::Stmt(_)) = parents.next()? |
| && let (_, Node::Block(enclosing_block)) = parents.next()? |
| && let mut stmts = enclosing_block |
| .stmts |
| .iter() |
| .skip_while(|stmt| stmt.hir_id != iter_stmt_id) |
| .skip(1) |
| && let first = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)? |
| && let second = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)? |
| && first.unwrap_kind == second.unwrap_kind |
| && first.name != second.name |
| && !local_used_after_expr(cx, iter_binding_id, second.init_expr) |
| { |
| let (r, lhs, rhs) = if method_name == "splitn" { |
| ("", first.name, second.name) |
| } else { |
| ("r", second.name, first.name) |
| }; |
| let msg = format!("manual implementation of `{r}split_once`"); |
| |
| let mut app = Applicability::MachineApplicable; |
| let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0; |
| let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0; |
| |
| span_lint_and_then(cx, MANUAL_SPLIT_ONCE, local.span, &msg, |diag| { |
| diag.span_label(first.span, "first usage here"); |
| diag.span_label(second.span, "second usage here"); |
| |
| let unwrap = match first.unwrap_kind { |
| UnwrapKind::Unwrap => ".unwrap()", |
| UnwrapKind::QuestionMark => "?", |
| }; |
| diag.span_suggestion_verbose( |
| local.span, |
| format!("try `{r}split_once`"), |
| format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"), |
| app, |
| ); |
| |
| let remove_msg = format!("remove the `{iter_ident}` usages"); |
| diag.span_suggestion(first.span, remove_msg.clone(), "", app); |
| diag.span_suggestion(second.span, remove_msg, "", app); |
| }); |
| } |
| |
| Some(()) |
| } |
| |
| #[derive(Debug)] |
| struct IndirectUsage<'a> { |
| name: Symbol, |
| span: Span, |
| init_expr: &'a Expr<'a>, |
| unwrap_kind: UnwrapKind, |
| } |
| |
| /// returns `Some(IndirectUsage)` for e.g. |
| /// |
| /// ```ignore |
| /// let name = binding.next()?; |
| /// let name = binding.next().unwrap(); |
| /// ``` |
| fn indirect_usage<'tcx>( |
| cx: &LateContext<'tcx>, |
| stmt: &Stmt<'tcx>, |
| binding: HirId, |
| ctxt: SyntaxContext, |
| ) -> Option<IndirectUsage<'tcx>> { |
| if let StmtKind::Local(&Local { |
| pat: Pat { |
| kind: PatKind::Binding(BindingAnnotation::NONE, _, ident, None), |
| .. |
| }, |
| init: Some(init_expr), |
| hir_id: local_hir_id, |
| .. |
| }) = stmt.kind |
| { |
| let mut path_to_binding = None; |
| let _: Option<!> = for_each_expr_with_closures(cx, init_expr, |e| { |
| if path_to_local_id(e, binding) { |
| path_to_binding = Some(e); |
| } |
| ControlFlow::Continue(Descend::from(path_to_binding.is_none())) |
| }); |
| |
| let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id); |
| let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?; |
| |
| let (parent_id, _) = parents.find(|(_, node)| { |
| !matches!( |
| node, |
| Node::Expr(Expr { |
| kind: ExprKind::Match(.., MatchSource::TryDesugar(_)), |
| .. |
| }) |
| ) |
| })?; |
| |
| if let IterUsage { |
| kind: IterUsageKind::Nth(0), |
| unwrap_kind: Some(unwrap_kind), |
| .. |
| } = iter_usage |
| { |
| if parent_id == local_hir_id { |
| return Some(IndirectUsage { |
| name: ident.name, |
| span: stmt.span, |
| init_expr, |
| unwrap_kind, |
| }); |
| } |
| } |
| } |
| |
| None |
| } |
| |
| #[derive(Debug, Clone, Copy)] |
| enum IterUsageKind { |
| Nth(u128), |
| NextTuple, |
| } |
| |
| #[derive(Debug, PartialEq, Eq)] |
| enum UnwrapKind { |
| Unwrap, |
| QuestionMark, |
| } |
| |
| #[derive(Debug)] |
| struct IterUsage { |
| kind: IterUsageKind, |
| unwrap_kind: Option<UnwrapKind>, |
| span: Span, |
| } |
| |
| #[allow(clippy::too_many_lines)] |
| fn parse_iter_usage<'tcx>( |
| cx: &LateContext<'tcx>, |
| ctxt: SyntaxContext, |
| mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>, |
| ) -> Option<IterUsage> { |
| let (kind, span) = match iter.next() { |
| Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => { |
| let ExprKind::MethodCall(name, _, args, _) = e.kind else { |
| return None; |
| }; |
| let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?; |
| let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?; |
| |
| match (name.ident.as_str(), args) { |
| ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), |
| ("next_tuple", []) => { |
| return if_chain! { |
| if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE); |
| if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind(); |
| if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()); |
| if let ty::Tuple(subs) = subs.type_at(0).kind(); |
| if subs.len() == 2; |
| then { |
| Some(IterUsage { |
| kind: IterUsageKind::NextTuple, |
| span: e.span, |
| unwrap_kind: None |
| }) |
| } else { |
| None |
| } |
| }; |
| }, |
| ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => { |
| if let Some(Constant::Int(idx)) = constant(cx, cx.typeck_results(), idx_expr) { |
| let span = if name.ident.as_str() == "nth" { |
| e.span |
| } else { |
| if_chain! { |
| if let Some((_, Node::Expr(next_expr))) = iter.next(); |
| if let ExprKind::MethodCall(next_name, _, [], _) = next_expr.kind; |
| if next_name.ident.name == sym::next; |
| if next_expr.span.ctxt() == ctxt; |
| if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id); |
| if cx.tcx.trait_of_item(next_id) == Some(iter_id); |
| then { |
| next_expr.span |
| } else { |
| return None; |
| } |
| } |
| }; |
| (IterUsageKind::Nth(idx), span) |
| } else { |
| return None; |
| } |
| }, |
| _ => return None, |
| } |
| }, |
| _ => return None, |
| }; |
| |
| let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() { |
| match e.kind { |
| ExprKind::Call( |
| Expr { |
| kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)), |
| .. |
| }, |
| _, |
| ) => { |
| let parent_span = e.span.parent_callsite().unwrap(); |
| if parent_span.ctxt() == ctxt { |
| (Some(UnwrapKind::QuestionMark), parent_span) |
| } else { |
| (None, span) |
| } |
| }, |
| _ if e.span.ctxt() != ctxt => (None, span), |
| ExprKind::MethodCall(name, _, [], _) |
| if name.ident.name == sym::unwrap |
| && cx |
| .typeck_results() |
| .type_dependent_def_id(e.hir_id) |
| .map_or(false, |id| is_diag_item_method(cx, id, sym::Option)) => |
| { |
| (Some(UnwrapKind::Unwrap), e.span) |
| }, |
| _ => (None, span), |
| } |
| } else { |
| (None, span) |
| }; |
| |
| Some(IterUsage { |
| kind, |
| unwrap_kind, |
| span, |
| }) |
| } |