]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/manual_strip.rs
bump version to 1.79.0+dfsg1-1~bpo12+pve2
[rustc.git] / src / tools / clippy / clippy_lints / src / manual_strip.rs
CommitLineData
ed00b5ec 1use clippy_config::msrvs::{self, Msrv};
17df50a5 2use clippy_utils::consts::{constant, Constant};
cdc7bbd5
XL
3use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
4use clippy_utils::source::snippet;
5use clippy_utils::usage::mutated_variables;
487cf647 6use clippy_utils::{eq_expr_value, higher, match_def_path, paths};
f20569fa
XL
7use rustc_ast::ast::LitKind;
8use rustc_hir::def::Res;
5099ac24 9use rustc_hir::intravisit::{walk_expr, Visitor};
add651ee 10use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind};
5099ac24 11use rustc_lint::{LateContext, LateLintPass};
f20569fa 12use rustc_middle::ty;
4b012472 13use rustc_session::impl_lint_pass;
f20569fa
XL
14use rustc_span::source_map::Spanned;
15use rustc_span::Span;
16
f20569fa 17declare_clippy_lint! {
94222f64 18 /// ### What it does
f20569fa
XL
19 /// Suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing using
20 /// the pattern's length.
21 ///
94222f64 22 /// ### Why is this bad?
f20569fa
XL
23 /// Using `str:strip_{prefix,suffix}` is safer and may have better performance as there is no
24 /// slicing which may panic and the compiler does not need to insert this panic code. It is
25 /// also sometimes more readable as it removes the need for duplicating or storing the pattern
26 /// used by `str::{starts,ends}_with` and in the slicing.
27 ///
94222f64 28 /// ### Example
ed00b5ec 29 /// ```no_run
f20569fa
XL
30 /// let s = "hello, world!";
31 /// if s.starts_with("hello, ") {
32 /// assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
33 /// }
34 /// ```
35 /// Use instead:
ed00b5ec 36 /// ```no_run
f20569fa
XL
37 /// let s = "hello, world!";
38 /// if let Some(end) = s.strip_prefix("hello, ") {
39 /// assert_eq!(end.to_uppercase(), "WORLD!");
40 /// }
41 /// ```
a2a8927a 42 #[clippy::version = "1.48.0"]
f20569fa
XL
43 pub MANUAL_STRIP,
44 complexity,
45 "suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing"
46}
47
48pub struct ManualStrip {
487cf647 49 msrv: Msrv,
f20569fa
XL
50}
51
52impl ManualStrip {
53 #[must_use]
487cf647 54 pub fn new(msrv: Msrv) -> Self {
f20569fa
XL
55 Self { msrv }
56 }
57}
58
59impl_lint_pass!(ManualStrip => [MANUAL_STRIP]);
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62enum StripKind {
63 Prefix,
64 Suffix,
65}
66
67impl<'tcx> LateLintPass<'tcx> for ManualStrip {
68 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
487cf647 69 if !self.msrv.meets(msrvs::STR_STRIP_PREFIX) {
f20569fa
XL
70 return;
71 }
72
4b012472
FG
73 if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
74 && let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind
75 && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id)
76 && let ExprKind::Path(target_path) = &target_arg.kind
77 {
78 let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
79 StripKind::Prefix
80 } else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
81 StripKind::Suffix
82 } else {
83 return;
84 };
85 let target_res = cx.qpath_res(target_path, target_arg.hir_id);
86 if target_res == Res::Err {
87 return;
88 };
89
90 if let Res::Local(hir_id) = target_res
91 && let Some(used_mutably) = mutated_variables(then, cx)
92 && used_mutably.contains(&hir_id)
93 {
94 return;
95 }
f20569fa 96
4b012472
FG
97 let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
98 if !strippings.is_empty() {
99 let kind_word = match strip_kind {
100 StripKind::Prefix => "prefix",
101 StripKind::Suffix => "suffix",
102 };
f20569fa 103
4b012472
FG
104 let test_span = expr.span.until(then.span);
105 span_lint_and_then(
106 cx,
107 MANUAL_STRIP,
108 strippings[0],
e8be2606 109 format!("stripping a {kind_word} manually"),
4b012472 110 |diag| {
9c376795 111 diag.span_note(test_span, format!("the {kind_word} was tested here"));
f20569fa
XL
112 multispan_sugg(
113 diag,
e8be2606 114 format!("try using the `strip_{kind_word}` method"),
4b012472
FG
115 vec![(
116 test_span,
117 format!(
118 "if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
119 snippet(cx, target_arg.span, ".."),
120 snippet(cx, pattern.span, "..")
121 ),
122 )]
123 .into_iter()
124 .chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
17df50a5 125 );
4b012472
FG
126 },
127 );
f20569fa
XL
128 }
129 }
130 }
131
132 extract_msrv_attr!(LateContext);
133}
134
135// Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise.
136fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
4b012472
FG
137 if let ExprKind::MethodCall(_, arg, [], _) = expr.kind
138 && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
139 && match_def_path(cx, method_def_id, &paths::STR_LEN)
140 {
141 Some(arg)
142 } else {
143 None
f20569fa
XL
144 }
145}
146
147// Returns the length of the `expr` if it's a constant string or char.
148fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
49aad941 149 let value = constant(cx, cx.typeck_results(), expr)?;
f20569fa
XL
150 match value {
151 Constant::Str(value) => Some(value.len() as u128),
152 Constant::Char(value) => Some(value.len_utf8() as u128),
153 _ => None,
154 }
155}
156
157// Tests if `expr` equals the length of the pattern.
158fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
159 if let ExprKind::Lit(Spanned {
160 node: LitKind::Int(n, _),
161 ..
162 }) = expr.kind
163 {
c0240ec0 164 constant_length(cx, pattern).map_or(false, |length| *n == length)
f20569fa
XL
165 } else {
166 len_arg(cx, expr).map_or(false, |arg| eq_expr_value(cx, pattern, arg))
167 }
168}
169
170// Tests if `expr` is a `&str`.
171fn is_ref_str(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
cdc7bbd5 172 match cx.typeck_results().expr_ty_adjusted(expr).kind() {
f20569fa
XL
173 ty::Ref(_, ty, _) => ty.is_str(),
174 _ => false,
175 }
176}
177
178// Removes the outer `AddrOf` expression if needed.
179fn peel_ref<'a>(expr: &'a Expr<'_>) -> &'a Expr<'a> {
180 if let ExprKind::AddrOf(BorrowKind::Ref, _, unref) = &expr.kind {
181 unref
182 } else {
183 expr
184 }
185}
186
187// Find expressions where `target` is stripped using the length of `pattern`.
188// We'll suggest replacing these expressions with the result of the `strip_{prefix,suffix}`
189// method.
190fn find_stripping<'tcx>(
191 cx: &LateContext<'tcx>,
192 strip_kind: StripKind,
193 target: Res,
194 pattern: &'tcx Expr<'_>,
195 expr: &'tcx Expr<'_>,
196) -> Vec<Span> {
197 struct StrippingFinder<'a, 'tcx> {
198 cx: &'a LateContext<'tcx>,
199 strip_kind: StripKind,
200 target: Res,
201 pattern: &'tcx Expr<'tcx>,
202 results: Vec<Span>,
203 }
204
205 impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> {
f20569fa 206 fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
4b012472
FG
207 if is_ref_str(self.cx, ex)
208 && let unref = peel_ref(ex)
209 && let ExprKind::Index(indexed, index, _) = &unref.kind
210 && let Some(higher::Range { start, end, .. }) = higher::Range::hir(index)
211 && let ExprKind::Path(path) = &indexed.kind
212 && self.cx.qpath_res(path, ex.hir_id) == self.target
213 {
214 match (self.strip_kind, start, end) {
215 (StripKind::Prefix, Some(start), None) => {
216 if eq_pattern_length(self.cx, self.pattern, start) {
217 self.results.push(ex.span);
218 return;
219 }
220 },
221 (StripKind::Suffix, None, Some(end)) => {
222 if let ExprKind::Binary(
223 Spanned {
224 node: BinOpKind::Sub, ..
225 },
226 left,
227 right,
228 ) = end.kind
229 && let Some(left_arg) = len_arg(self.cx, left)
230 && let ExprKind::Path(left_path) = &left_arg.kind
231 && self.cx.qpath_res(left_path, left_arg.hir_id) == self.target
232 && eq_pattern_length(self.cx, self.pattern, right)
233 {
234 self.results.push(ex.span);
235 return;
236 }
237 },
238 _ => {},
f20569fa
XL
239 }
240 }
241
242 walk_expr(self, ex);
243 }
244 }
245
246 let mut finder = StrippingFinder {
247 cx,
248 strip_kind,
249 target,
250 pattern,
251 results: vec![],
252 };
253 walk_expr(&mut finder, expr);
254 finder.results
255}