]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/str_splitn.rs
Move `{core,std}::stream::Stream` to `{core,std}::async_iter::AsyncIterator`.
[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;
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};
11
12 use super::MANUAL_SPLIT_ONCE;
13
14 pub(super) fn check_manual_split_once(
15     cx: &LateContext<'_>,
16     method_name: &str,
17     expr: &Expr<'_>,
18     self_arg: &Expr<'_>,
19     pat_arg: &Expr<'_>,
20 ) {
21     if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
22         return;
23     }
24
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)
28     } else {
29         ("rsplit_once", "manual implementation of `rsplit_once`", true)
30     };
31     let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), reverse) {
32         Some(x) => x,
33         None => return,
34     };
35
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;
39
40     let sugg = match usage.kind {
41         IterUsageKind::NextTuple => {
42             format!("{}.{}({})", self_snip, method_name, pat_snip)
43         },
44         IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip),
45         IterUsageKind::Next | IterUsageKind::Second => {
46             let self_deref = {
47                 let adjust = cx.typeck_results().expr_adjustments(self_arg);
48                 if adjust.is_empty() {
49                     String::new()
50                 } else if cx.typeck_results().expr_ty(self_arg).is_box()
51                     || adjust
52                         .iter()
53                         .any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
54                 {
55                     format!("&{}", "*".repeat(adjust.len() - 1))
56                 } else {
57                     "*".repeat(adjust.len() - 2)
58                 }
59             };
60             if matches!(usage.kind, IterUsageKind::Next) {
61                 match usage.unwrap_kind {
62                     Some(UnwrapKind::Unwrap) => {
63                         if reverse {
64                             format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip)
65                         } else {
66                             format!(
67                                 "{}.{}({}).map_or({}{}, |x| x.0)",
68                                 self_snip, method_name, pat_snip, self_deref, &self_snip
69                             )
70                         }
71                     },
72                     Some(UnwrapKind::QuestionMark) => {
73                         format!(
74                             "{}.{}({}).map_or({}{}, |x| x.0)",
75                             self_snip, method_name, pat_snip, self_deref, &self_snip
76                         )
77                     },
78                     None => {
79                         format!(
80                             "Some({}.{}({}).map_or({}{}, |x| x.0))",
81                             &self_snip, method_name, pat_snip, self_deref, &self_snip
82                         )
83                     },
84                 }
85             } else {
86                 match usage.unwrap_kind {
87                     Some(UnwrapKind::Unwrap) => {
88                         if reverse {
89                             // In this case, no better suggestion is offered.
90                             return;
91                         }
92                         format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip)
93                     },
94                     Some(UnwrapKind::QuestionMark) => {
95                         format!("{}.{}({})?.1", self_snip, method_name, pat_snip)
96                     },
97                     None => {
98                         format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip)
99                     },
100                 }
101             }
102         },
103     };
104
105     span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
106 }
107
108 enum IterUsageKind {
109     Next,
110     Second,
111     NextTuple,
112     RNextTuple,
113 }
114
115 enum UnwrapKind {
116     Unwrap,
117     QuestionMark,
118 }
119
120 struct IterUsage {
121     kind: IterUsageKind,
122     unwrap_kind: Option<UnwrapKind>,
123     span: Span,
124 }
125
126 #[allow(clippy::too_many_lines)]
127 fn parse_iter_usage<'tcx>(
128     cx: &LateContext<'tcx>,
129     ctxt: SyntaxContext,
130     mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
131     reverse: bool,
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 {
136                 (name, args)
137             } else {
138                 return None;
139             };
140             let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
141             let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
142
143             match (name.ident.as_str(), args) {
144                 ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
145                     if reverse {
146                         (IterUsageKind::Second, e.span)
147                     } else {
148                         (IterUsageKind::Next, e.span)
149                     }
150                 },
151                 ("next_tuple", []) => {
152                     return if_chain! {
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();
157                         if subs.len() == 2;
158                         then {
159                             Some(IterUsage {
160                                 kind: if reverse { IterUsageKind::RNextTuple } else { IterUsageKind::NextTuple },
161                                 span: e.span,
162                                 unwrap_kind: None
163                             })
164                         } else {
165                             None
166                         }
167                     };
168                 },
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" {
172                             e.span
173                         } else {
174                             if_chain! {
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);
181                                 then {
182                                     next_expr.span
183                                 } else {
184                                     return None;
185                                 }
186                             }
187                         };
188                         match if reverse { idx ^ 1 } else { idx } {
189                             0 => (IterUsageKind::Next, span),
190                             1 => (IterUsageKind::Second, span),
191                             _ => return None,
192                         }
193                     } else {
194                         return None;
195                     }
196                 },
197                 _ => return None,
198             }
199         },
200         _ => return None,
201     };
202
203     let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
204         match e.kind {
205             ExprKind::Call(
206                 Expr {
207                     kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)),
208                     ..
209                 },
210                 _,
211             ) => {
212                 let parent_span = e.span.parent_callsite().unwrap();
213                 if parent_span.ctxt() == ctxt {
214                     (Some(UnwrapKind::QuestionMark), parent_span)
215                 } else {
216                     (None, span)
217                 }
218             },
219             _ if e.span.ctxt() != ctxt => (None, span),
220             ExprKind::MethodCall(name, [_], _)
221                 if name.ident.name == sym::unwrap
222                     && cx
223                         .typeck_results()
224                         .type_dependent_def_id(e.hir_id)
225                         .map_or(false, |id| is_diag_item_method(cx, id, sym::Option)) =>
226             {
227                 (Some(UnwrapKind::Unwrap), e.span)
228             },
229             _ => (None, span),
230         }
231     } else {
232         (None, span)
233     };
234
235     Some(IterUsage {
236         kind,
237         unwrap_kind,
238         span,
239     })
240 }
241
242 use super::NEEDLESS_SPLITN;
243
244 pub(super) fn check_needless_splitn(
245     cx: &LateContext<'_>,
246     method_name: &str,
247     expr: &Expr<'_>,
248     self_arg: &Expr<'_>,
249     pat_arg: &Expr<'_>,
250     count: u128,
251 ) {
252     if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
253         return;
254     }
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`")
259     } else {
260         (true, "unnecessary use of `rsplitn`")
261     };
262     if_chain! {
263         if count >= 2;
264         if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count);
265         then {
266             span_lint_and_sugg(
267                 cx,
268                 NEEDLESS_SPLITN,
269                 expr.span,
270                 message,
271                 "try this",
272                 format!(
273                     "{}.{}({})",
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
277                 ),
278                 app,
279             );
280         }
281     }
282 }
283
284 fn check_iter<'tcx>(
285     cx: &LateContext<'tcx>,
286     ctxt: SyntaxContext,
287     mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
288     count: u128,
289 ) -> bool {
290     match iter.next() {
291         Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
292             let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind {
293                 (name, args)
294             } else {
295                 return false;
296             };
297             if_chain! {
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);
300                 then {
301                     match (name.ident.as_str(), args) {
302                         ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
303                             return true;
304                         },
305                         ("next_tuple", []) if count > 2 => {
306                             return true;
307                         },
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) {
310                                 if count > idx + 1 {
311                                     return true;
312                                 }
313                             }
314                         },
315                         _ =>  return false,
316                     }
317                 }
318             }
319         },
320         _ => return false,
321     };
322     false
323 }