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};
12 use super::MANUAL_SPLIT_ONCE;
14 pub(super) fn check_manual_split_once(
21 if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
25 let ctxt = expr.span.ctxt();
26 let (method_name, msg, reverse) = if method_name == "splitn" {
27 ("split_once", "manual implementation of `split_once`", false)
29 ("rsplit_once", "manual implementation of `rsplit_once`", true)
31 let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), reverse) {
36 let mut app = Applicability::MachineApplicable;
37 let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
38 let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
40 let sugg = match usage.kind {
41 IterUsageKind::NextTuple => {
42 format!("{}.{}({})", self_snip, method_name, pat_snip)
44 IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip),
45 IterUsageKind::Next | IterUsageKind::Second => {
47 let adjust = cx.typeck_results().expr_adjustments(self_arg);
48 if adjust.is_empty() {
50 } else if cx.typeck_results().expr_ty(self_arg).is_box()
53 .any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
55 format!("&{}", "*".repeat(adjust.len() - 1))
57 "*".repeat(adjust.len() - 2)
60 if matches!(usage.kind, IterUsageKind::Next) {
61 match usage.unwrap_kind {
62 Some(UnwrapKind::Unwrap) => {
64 format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip)
67 "{}.{}({}).map_or({}{}, |x| x.0)",
68 self_snip, method_name, pat_snip, self_deref, &self_snip
72 Some(UnwrapKind::QuestionMark) => {
74 "{}.{}({}).map_or({}{}, |x| x.0)",
75 self_snip, method_name, pat_snip, self_deref, &self_snip
80 "Some({}.{}({}).map_or({}{}, |x| x.0))",
81 &self_snip, method_name, pat_snip, self_deref, &self_snip
86 match usage.unwrap_kind {
87 Some(UnwrapKind::Unwrap) => {
89 // In this case, no better suggestion is offered.
92 format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip)
94 Some(UnwrapKind::QuestionMark) => {
95 format!("{}.{}({})?.1", self_snip, method_name, pat_snip)
98 format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip)
105 span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
122 unwrap_kind: Option<UnwrapKind>,
126 #[allow(clippy::too_many_lines)]
127 fn parse_iter_usage<'tcx>(
128 cx: &LateContext<'tcx>,
130 mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
132 ) -> Option<IterUsage> {
133 let (kind, span) = match iter.next() {
134 Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
135 let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind {
140 let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
141 let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
143 match (name.ident.as_str(), args) {
144 ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
146 (IterUsageKind::Second, e.span)
148 (IterUsageKind::Next, e.span)
151 ("next_tuple", []) => {
153 if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
154 if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
155 if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did);
156 if let ty::Tuple(subs) = subs.type_at(0).kind();
160 kind: if reverse { IterUsageKind::RNextTuple } else { IterUsageKind::NextTuple },
169 ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
170 if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
171 let span = if name.ident.as_str() == "nth" {
175 if let Some((_, Node::Expr(next_expr))) = iter.next();
176 if let ExprKind::MethodCall(next_name, [_], _) = next_expr.kind;
177 if next_name.ident.name == sym::next;
178 if next_expr.span.ctxt() == ctxt;
179 if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
180 if cx.tcx.trait_of_item(next_id) == Some(iter_id);
188 match if reverse { idx ^ 1 } else { idx } {
189 0 => (IterUsageKind::Next, span),
190 1 => (IterUsageKind::Second, span),
203 let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
207 kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)),
212 let parent_span = e.span.parent_callsite().unwrap();
213 if parent_span.ctxt() == ctxt {
214 (Some(UnwrapKind::QuestionMark), parent_span)
219 _ if e.span.ctxt() != ctxt => (None, span),
220 ExprKind::MethodCall(name, [_], _)
221 if name.ident.name == sym::unwrap
224 .type_dependent_def_id(e.hir_id)
225 .map_or(false, |id| is_diag_item_method(cx, id, sym::Option)) =>
227 (Some(UnwrapKind::Unwrap), e.span)
242 use super::NEEDLESS_SPLITN;
244 pub(super) fn check_needless_splitn(
245 cx: &LateContext<'_>,
252 if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
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`")
260 (true, "unnecessary use of `rsplitn`")
264 if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count);
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
285 cx: &LateContext<'tcx>,
287 mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
291 Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
292 let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind {
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);
301 match (name.ident.as_str(), args) {
302 ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
305 ("next_tuple", []) if count > 2 => {
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) {