| use clippy_utils::consts::{ConstEvalCtxt, Constant}; | |
| use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; | |
| use clippy_utils::msrvs::{self, Msrv}; | |
| use clippy_utils::res::{MaybeDef, MaybeResPath}; | |
| use clippy_utils::source::snippet_with_context; | |
| use clippy_utils::usage::local_used_after_expr; | |
| use clippy_utils::visitors::{Descend, for_each_expr}; | |
| use clippy_utils::{paths, sym}; | |
| use core::ops::ControlFlow; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::{ | |
| BindingMode, Expr, ExprKind, HirId, LangItem, LetStmt, MatchSource, Node, Pat, PatKind, Stmt, StmtKind, | |
| }; | |
| use rustc_lint::LateContext; | |
| use rustc_middle::ty; | |
| use rustc_span::{Span, Symbol, SyntaxContext}; | |
| use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN}; | |
| pub(super) fn check( | |
| cx: &LateContext<'_>, | |
| method_name: Symbol, | |
| 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(cx, 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: Symbol, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) { | |
| let mut app = Applicability::MachineApplicable; | |
| let r = if method_name == sym::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: Symbol, | |
| expr: &Expr<'_>, | |
| self_arg: &Expr<'_>, | |
| pat_arg: &Expr<'_>, | |
| usage: &IterUsage, | |
| ) { | |
| let ctxt = expr.span.ctxt(); | |
| let (msg, reverse) = if method_name == sym::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: Symbol, | |
| 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::LetStmt(local)) = parents.next()? | |
| && let PatKind::Binding(BindingMode::MUT, iter_binding_id, _, 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 == sym::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 => "?", | |
| }; | |
| // Add a multipart suggestion | |
| diag.multipart_suggestion( | |
| format!("replace with `{r}split_once`"), | |
| vec![ | |
| ( | |
| local.span, | |
| format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"), | |
| ), | |
| (first.span, String::new()), // Remove the first usage | |
| (second.span, String::new()), // Remove the second usage | |
| ], | |
| 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::Let(&LetStmt { | |
| pat: Pat { | |
| kind: PatKind::Binding(BindingMode::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(cx, init_expr, |e| { | |
| if e.res_local_id() == Some(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 | |
| && 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, | |
| } | |
| 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.name, args) { | |
| (sym::next, []) if cx.tcx.trait_of_assoc(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), | |
| (sym::next_tuple, []) => { | |
| return if paths::ITERTOOLS_NEXT_TUPLE.matches(cx, did) | |
| && let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind() | |
| && cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) | |
| && let ty::Tuple(subs) = subs.type_at(0).kind() | |
| && subs.len() == 2 | |
| { | |
| Some(IterUsage { | |
| kind: IterUsageKind::NextTuple, | |
| unwrap_kind: None, | |
| span: e.span, | |
| }) | |
| } else { | |
| None | |
| }; | |
| }, | |
| (sym::nth | sym::skip, [idx_expr]) if cx.tcx.trait_of_assoc(did) == Some(iter_id) => { | |
| if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval_local(idx_expr, ctxt) { | |
| let span = if name.ident.as_str() == "nth" { | |
| e.span | |
| } else if let Some((_, Node::Expr(next_expr))) = iter.next() | |
| && let ExprKind::MethodCall(next_name, _, [], _) = next_expr.kind | |
| && next_name.ident.name == sym::next | |
| && next_expr.span.ctxt() == ctxt | |
| && let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id) | |
| && cx.tcx.trait_of_assoc(next_id) == Some(iter_id) | |
| { | |
| 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), | |
| .. | |
| }, | |
| [_], | |
| ) if cx.tcx.qpath_is_lang_item(qpath, 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) | |
| .opt_parent(cx) | |
| .opt_impl_ty(cx) | |
| .is_diag_item(cx, sym::Option) => | |
| { | |
| (Some(UnwrapKind::Unwrap), e.span) | |
| }, | |
| _ => (None, span), | |
| } | |
| } else { | |
| (None, span) | |
| }; | |
| Some(IterUsage { | |
| kind, | |
| unwrap_kind, | |
| span, | |
| }) | |
| } |