]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/methods/str_splitn.rs
New upstream version 1.61.0+dfsg1
[rustc.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.len() < 2 {
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().saturating_sub(1)))
56 } else {
57 "*".repeat(adjust.len().saturating_sub(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 }