]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/str_splitn.rs
Merge commit 'c4416f20dcaec5d93077f72470e83e150fb923b1' into sync-rustfmt
[rust.git] / src / tools / clippy / clippy_lints / src / methods / str_splitn.rs
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::expr_visitor;
6 use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths};
7 use if_chain::if_chain;
8 use rustc_errors::Applicability;
9 use rustc_hir::intravisit::Visitor;
10 use rustc_hir::{
11     BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind,
12 };
13 use rustc_lint::LateContext;
14 use rustc_middle::ty;
15 use rustc_semver::RustcVersion;
16 use rustc_span::{sym, Span, Symbol, SyntaxContext};
17
18 use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
19
20 pub(super) fn check(
21     cx: &LateContext<'_>,
22     method_name: &str,
23     expr: &Expr<'_>,
24     self_arg: &Expr<'_>,
25     pat_arg: &Expr<'_>,
26     count: u128,
27     msrv: Option<RustcVersion>,
28 ) {
29     if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
30         return;
31     }
32
33     let needless = |usage_kind| match usage_kind {
34         IterUsageKind::Nth(n) => count > n + 1,
35         IterUsageKind::NextTuple => count > 2,
36     };
37     let manual = count == 2 && meets_msrv(msrv, msrvs::STR_SPLIT_ONCE);
38
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),
42         None if manual => {
43             check_manual_split_once_indirect(cx, method_name, expr, self_arg, pat_arg);
44         },
45         _ => {},
46     }
47 }
48
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" };
52
53     span_lint_and_sugg(
54         cx,
55         NEEDLESS_SPLITN,
56         expr.span,
57         &format!("unnecessary use of `{r}splitn`"),
58         "try this",
59         format!(
60             "{}.{r}split({})",
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,
63         ),
64         app,
65     );
66 }
67
68 fn check_manual_split_once(
69     cx: &LateContext<'_>,
70     method_name: &str,
71     expr: &Expr<'_>,
72     self_arg: &Expr<'_>,
73     pat_arg: &Expr<'_>,
74     usage: &IterUsage,
75 ) {
76     let ctxt = expr.span.ctxt();
77     let (msg, reverse) = if method_name == "splitn" {
78         ("manual implementation of `split_once`", false)
79     } else {
80         ("manual implementation of `rsplit_once`", true)
81     };
82
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;
86
87     let sugg = match usage.kind {
88         IterUsageKind::NextTuple => {
89             if reverse {
90                 format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))")
91             } else {
92                 format!("{self_snip}.split_once({pat_snip})")
93             }
94         },
95         IterUsageKind::Nth(1) => {
96             let (r, field) = if reverse { ("r", 0) } else { ("", 1) };
97
98             match usage.unwrap_kind {
99                 Some(UnwrapKind::Unwrap) => {
100                     format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}")
101                 },
102                 Some(UnwrapKind::QuestionMark) => {
103                     format!("{self_snip}.{r}split_once({pat_snip})?.{field}")
104                 },
105                 None => {
106                     format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})")
107                 },
108             }
109         },
110         IterUsageKind::Nth(_) => return,
111     };
112
113     span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
114 }
115
116 /// checks for
117 ///
118 /// ```
119 /// let mut iter = "a.b.c".splitn(2, '.');
120 /// let a = iter.next();
121 /// let b = iter.next();
122 /// ```
123 fn check_manual_split_once_indirect(
124     cx: &LateContext<'_>,
125     method_name: &str,
126     expr: &Expr<'_>,
127     self_arg: &Expr<'_>,
128     pat_arg: &Expr<'_>,
129 ) -> Option<()> {
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::Mutable, 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()?
136
137         && let mut stmts = enclosing_block
138             .stmts
139             .iter()
140             .skip_while(|stmt| stmt.hir_id != iter_stmt_id)
141             .skip(1)
142
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)
148     {
149         let (r, lhs, rhs) = if method_name == "splitn" {
150             ("", first.name, second.name)
151         } else {
152             ("r", second.name, first.name)
153         };
154         let msg = format!("manual implementation of `{r}split_once`");
155
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;
159
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");
163
164             let unwrap = match first.unwrap_kind {
165                 UnwrapKind::Unwrap => ".unwrap()",
166                 UnwrapKind::QuestionMark => "?",
167             };
168             diag.span_suggestion_verbose(
169                 local.span,
170                 &format!("try `{r}split_once`"),
171                 format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
172                 app,
173             );
174
175             let remove_msg = format!("remove the `{iter_ident}` usages");
176             diag.span_suggestion(
177                 first.span,
178                 &remove_msg,
179                 "",
180                 app,
181             );
182             diag.span_suggestion(
183                 second.span,
184                 &remove_msg,
185                 "",
186                 app,
187             );
188         });
189     }
190
191     Some(())
192 }
193
194 #[derive(Debug)]
195 struct IndirectUsage<'a> {
196     name: Symbol,
197     span: Span,
198     init_expr: &'a Expr<'a>,
199     unwrap_kind: UnwrapKind,
200 }
201
202 /// returns `Some(IndirectUsage)` for e.g.
203 ///
204 /// ```ignore
205 /// let name = binding.next()?;
206 /// let name = binding.next().unwrap();
207 /// ```
208 fn indirect_usage<'tcx>(
209     cx: &LateContext<'tcx>,
210     stmt: &Stmt<'tcx>,
211     binding: HirId,
212     ctxt: SyntaxContext,
213 ) -> Option<IndirectUsage<'tcx>> {
214     if let StmtKind::Local(Local {
215         pat:
216             Pat {
217                 kind: PatKind::Binding(BindingAnnotation::Unannotated, _, ident, None),
218                 ..
219             },
220         init: Some(init_expr),
221         hir_id: local_hir_id,
222         ..
223     }) = stmt.kind
224     {
225         let mut path_to_binding = None;
226         expr_visitor(cx, |expr| {
227             if path_to_local_id(expr, binding) {
228                 path_to_binding = Some(expr);
229             }
230
231             path_to_binding.is_none()
232         })
233         .visit_expr(init_expr);
234
235         let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id);
236         let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?;
237
238         let (parent_id, _) = parents.find(|(_, node)| {
239             !matches!(
240                 node,
241                 Node::Expr(Expr {
242                     kind: ExprKind::Match(.., MatchSource::TryDesugar),
243                     ..
244                 })
245             )
246         })?;
247
248         if let IterUsage {
249             kind: IterUsageKind::Nth(0),
250             unwrap_kind: Some(unwrap_kind),
251             ..
252         } = iter_usage
253         {
254             if parent_id == *local_hir_id {
255                 return Some(IndirectUsage {
256                     name: ident.name,
257                     span: stmt.span,
258                     init_expr,
259                     unwrap_kind,
260                 });
261             }
262         }
263     }
264
265     None
266 }
267
268 #[derive(Debug, Clone, Copy)]
269 enum IterUsageKind {
270     Nth(u128),
271     NextTuple,
272 }
273
274 #[derive(Debug, PartialEq, Eq)]
275 enum UnwrapKind {
276     Unwrap,
277     QuestionMark,
278 }
279
280 #[derive(Debug)]
281 struct IterUsage {
282     kind: IterUsageKind,
283     unwrap_kind: Option<UnwrapKind>,
284     span: Span,
285 }
286
287 #[allow(clippy::too_many_lines)]
288 fn parse_iter_usage<'tcx>(
289     cx: &LateContext<'tcx>,
290     ctxt: SyntaxContext,
291     mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
292 ) -> Option<IterUsage> {
293     let (kind, span) = match iter.next() {
294         Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
295             let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind {
296                 (name, args)
297             } else {
298                 return None;
299             };
300             let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
301             let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
302
303             match (name.ident.as_str(), args) {
304                 ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
305                 ("next_tuple", []) => {
306                     return if_chain! {
307                         if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
308                         if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
309                         if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did());
310                         if let ty::Tuple(subs) = subs.type_at(0).kind();
311                         if subs.len() == 2;
312                         then {
313                             Some(IterUsage {
314                                 kind: IterUsageKind::NextTuple,
315                                 span: e.span,
316                                 unwrap_kind: None
317                             })
318                         } else {
319                             None
320                         }
321                     };
322                 },
323                 ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
324                     if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
325                         let span = if name.ident.as_str() == "nth" {
326                             e.span
327                         } else {
328                             if_chain! {
329                                 if let Some((_, Node::Expr(next_expr))) = iter.next();
330                                 if let ExprKind::MethodCall(next_name, [_], _) = next_expr.kind;
331                                 if next_name.ident.name == sym::next;
332                                 if next_expr.span.ctxt() == ctxt;
333                                 if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
334                                 if cx.tcx.trait_of_item(next_id) == Some(iter_id);
335                                 then {
336                                     next_expr.span
337                                 } else {
338                                     return None;
339                                 }
340                             }
341                         };
342                         (IterUsageKind::Nth(idx), span)
343                     } else {
344                         return None;
345                     }
346                 },
347                 _ => return None,
348             }
349         },
350         _ => return None,
351     };
352
353     let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
354         match e.kind {
355             ExprKind::Call(
356                 Expr {
357                     kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)),
358                     ..
359                 },
360                 _,
361             ) => {
362                 let parent_span = e.span.parent_callsite().unwrap();
363                 if parent_span.ctxt() == ctxt {
364                     (Some(UnwrapKind::QuestionMark), parent_span)
365                 } else {
366                     (None, span)
367                 }
368             },
369             _ if e.span.ctxt() != ctxt => (None, span),
370             ExprKind::MethodCall(name, [_], _)
371                 if name.ident.name == sym::unwrap
372                     && cx
373                         .typeck_results()
374                         .type_dependent_def_id(e.hir_id)
375                         .map_or(false, |id| is_diag_item_method(cx, id, sym::Option)) =>
376             {
377                 (Some(UnwrapKind::Unwrap), e.span)
378             },
379             _ => (None, span),
380         }
381     } else {
382         (None, span)
383     };
384
385     Some(IterUsage {
386         kind,
387         unwrap_kind,
388         span,
389     })
390 }