1 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_help, span_lint_and_sugg}
;
2 use clippy_utils
::source
::{snippet, snippet_with_applicability}
;
3 use clippy_utils
::ty
::is_type_diagnostic_item
;
4 use clippy_utils
::SpanlessEq
;
5 use clippy_utils
::{get_parent_expr, is_allowed, match_function_call, method_calls, paths}
;
6 use if_chain
::if_chain
;
7 use rustc_errors
::Applicability
;
8 use rustc_hir
::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath}
;
9 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
10 use rustc_middle
::lint
::in_external_macro
;
12 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
13 use rustc_span
::source_map
::Spanned
;
16 declare_clippy_lint
! {
17 /// **What it does:** Checks for string appends of the form `x = x + y` (without
20 /// **Why is this bad?** It's not really bad, but some people think that the
21 /// `.push_str(_)` method is more readable.
23 /// **Known problems:** None.
28 /// let mut x = "Hello".to_owned();
29 /// x = x + ", World";
33 /// x.push_str(", World");
35 pub STRING_ADD_ASSIGN
,
37 "using `x = x + ..` where x is a `String` instead of `push_str()`"
40 declare_clippy_lint
! {
41 /// **What it does:** Checks for all instances of `x + _` where `x` is of type
42 /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not*
45 /// **Why is this bad?** It's not bad in and of itself. However, this particular
46 /// `Add` implementation is asymmetric (the other operand need not be `String`,
47 /// but `x` does), while addition as mathematically defined is symmetric, also
48 /// the `String::push_str(_)` function is a perfectly good replacement.
49 /// Therefore, some dislike it and wish not to have it in their code.
51 /// That said, other people think that string addition, having a long tradition
52 /// in other languages is actually fine, which is why we decided to make this
53 /// particular lint `allow` by default.
55 /// **Known problems:** None.
60 /// let x = "Hello".to_owned();
65 "using `x + ..` where x is a `String` instead of `push_str()`"
68 declare_clippy_lint
! {
69 /// **What it does:** Checks for the `as_bytes` method called on string literals
70 /// that contain only ASCII characters.
72 /// **Why is this bad?** Byte string literals (e.g., `b"foo"`) can be used
73 /// instead. They are shorter but less discoverable than `as_bytes()`.
75 /// **Known Problems:**
76 /// `"str".as_bytes()` and the suggested replacement of `b"str"` are not
77 /// equivalent because they have different types. The former is `&[u8]`
78 /// while the latter is `&[u8; 3]`. That means in general they will have a
79 /// different set of methods and different trait implementations.
82 /// fn f(v: Vec<u8>) {}
84 /// f("...".as_bytes().to_owned()); // works
85 /// f(b"...".to_owned()); // does not work, because arg is [u8; 3] not Vec<u8>
87 /// fn g(r: impl std::io::Read) {}
89 /// g("...".as_bytes()); // works
90 /// g(b"..."); // does not work
93 /// The actual equivalent of `"str".as_bytes()` with the same type is not
94 /// `b"str"` but `&b"str"[..]`, which is a great deal of punctuation and not
95 /// more readable than a function call.
100 /// let bs = "a byte string".as_bytes();
103 /// let bs = b"a byte string";
105 pub STRING_LIT_AS_BYTES
,
107 "calling `as_bytes` on a string literal instead of using a byte string literal"
110 declare_lint_pass
!(StringAdd
=> [STRING_ADD
, STRING_ADD_ASSIGN
]);
112 impl<'tcx
> LateLintPass
<'tcx
> for StringAdd
{
113 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, e
: &'tcx Expr
<'_
>) {
114 if in_external_macro(cx
.sess(), e
.span
) {
118 if let ExprKind
::Binary(
120 node
: BinOpKind
::Add
, ..
126 if is_string(cx
, left
) {
127 if !is_allowed(cx
, STRING_ADD_ASSIGN
, e
.hir_id
) {
128 let parent
= get_parent_expr(cx
, e
);
129 if let Some(p
) = parent
{
130 if let ExprKind
::Assign(target
, _
, _
) = p
.kind
{
131 // avoid duplicate matches
132 if SpanlessEq
::new(cx
).eq_expr(target
, left
) {
142 "you added something to a string. Consider using `String::push_str()` instead",
145 } else if let ExprKind
::Assign(target
, src
, _
) = e
.kind
{
146 if is_string(cx
, target
) && is_add(cx
, src
, target
) {
151 "you assigned the result of adding something to this string. Consider using \
152 `String::push_str()` instead",
159 fn is_string(cx
: &LateContext
<'_
>, e
: &Expr
<'_
>) -> bool
{
160 is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(e
).peel_refs(), sym
::string_type
)
163 fn is_add(cx
: &LateContext
<'_
>, src
: &Expr
<'_
>, target
: &Expr
<'_
>) -> bool
{
167 node
: BinOpKind
::Add
, ..
171 ) => SpanlessEq
::new(cx
).eq_expr(target
, left
),
172 ExprKind
::Block(block
, _
) => {
173 block
.stmts
.is_empty() && block
.expr
.as_ref().map_or(false, |expr
| is_add(cx
, expr
, target
))
179 declare_clippy_lint
! {
180 /// **What it does:** Check if the string is transformed to byte array and casted back to string.
182 /// **Why is this bad?** It's unnecessary, the string can be used directly.
184 /// **Known problems:** None
188 /// let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap();
190 /// could be written as
192 /// let _ = &"Hello World!"[6..11];
194 pub STRING_FROM_UTF8_AS_BYTES
,
196 "casting string slices to byte slices and back"
199 // Max length a b"foo" string can take
200 const MAX_LENGTH_BYTE_STRING_LIT
: usize = 32;
202 declare_lint_pass
!(StringLitAsBytes
=> [STRING_LIT_AS_BYTES
, STRING_FROM_UTF8_AS_BYTES
]);
204 impl<'tcx
> LateLintPass
<'tcx
> for StringLitAsBytes
{
205 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, e
: &'tcx Expr
<'_
>) {
206 use rustc_ast
::LitKind
;
209 // Find std::str::converts::from_utf8
210 if let Some(args
) = match_function_call(cx
, e
, &paths
::STR_FROM_UTF8
);
212 // Find string::as_bytes
213 if let ExprKind
::AddrOf(BorrowKind
::Ref
, _
, args
) = args
[0].kind
;
214 if let ExprKind
::Index(left
, right
) = args
.kind
;
215 let (method_names
, expressions
, _
) = method_calls(left
, 1);
216 if method_names
.len() == 1;
217 if expressions
.len() == 1;
218 if expressions
[0].len() == 1;
219 if method_names
[0] == sym
!(as_bytes
);
222 if let ExprKind
::Struct(QPath
::LangItem(LangItem
::Range
, _
), _
, _
) = right
.kind
;
225 let mut applicability
= Applicability
::MachineApplicable
;
226 let string_expression
= &expressions
[0][0];
228 let snippet_app
= snippet_with_applicability(
230 string_expression
.span
, "..",
236 STRING_FROM_UTF8_AS_BYTES
,
238 "calling a slice of `as_bytes()` with `from_utf8` should be not necessary",
240 format
!("Some(&{}[{}])", snippet_app
, snippet(cx
, right
.span
, "..")),
247 if let ExprKind
::MethodCall(path
, _
, args
, _
) = &e
.kind
;
248 if path
.ident
.name
== sym
!(as_bytes
);
249 if let ExprKind
::Lit(lit
) = &args
[0].kind
;
250 if let LitKind
::Str(lit_content
, _
) = &lit
.node
;
252 let callsite
= snippet(cx
, args
[0].span
.source_callsite(), r
#""foo""#);
253 let mut applicability
= Applicability
::MachineApplicable
;
254 if callsite
.starts_with("include_str!") {
259 "calling `as_bytes()` on `include_str!(..)`",
260 "consider using `include_bytes!(..)` instead",
261 snippet_with_applicability(cx
, args
[0].span
, r
#""foo""#, &mut applicability).replacen(
268 } else if lit_content
.as_str().is_ascii()
269 && lit_content
.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT
270 && !args
[0].span
.from_expansion()
276 "calling `as_bytes()` on a string literal",
277 "consider using a byte string literal instead",
280 snippet_with_applicability(cx
, args
[0].span
, r
#""foo""#, &mut applicability)
289 if let ExprKind
::MethodCall(path
, _
, [recv
], _
) = &e
.kind
;
290 if path
.ident
.name
== sym
!(into_bytes
);
291 if let ExprKind
::MethodCall(path
, _
, [recv
], _
) = &recv
.kind
;
292 if matches
!(&*path
.ident
.name
.as_str(), "to_owned" | "to_string");
293 if let ExprKind
::Lit(lit
) = &recv
.kind
;
294 if let LitKind
::Str(lit_content
, _
) = &lit
.node
;
296 if lit_content
.as_str().is_ascii();
297 if lit_content
.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT
;
298 if !recv
.span
.from_expansion();
300 let mut applicability
= Applicability
::MachineApplicable
;
306 "calling `into_bytes()` on a string literal",
307 "consider using a byte string literal instead",
310 snippet_with_applicability(cx
, recv
.span
, r
#""..""#, &mut applicability)
319 declare_clippy_lint
! {
320 /// **What it does:** This lint checks for `.to_string()` method calls on values of type `&str`.
322 /// **Why is this bad?** The `to_string` method is also used on other types to convert them to a string.
323 /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be better
324 /// expressed with `.to_owned()`.
326 /// **Known problems:** None.
331 /// // example code where clippy issues a warning
332 /// let _ = "str".to_string();
336 /// // example code which does not raise clippy warning
337 /// let _ = "str".to_owned();
341 "using `to_string()` on a `&str`, which should be `to_owned()`"
344 declare_lint_pass
!(StrToString
=> [STR_TO_STRING
]);
346 impl LateLintPass
<'_
> for StrToString
{
347 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &Expr
<'_
>) {
349 if let ExprKind
::MethodCall(path
, _
, args
, _
) = &expr
.kind
;
350 if path
.ident
.name
== sym
!(to_string
);
351 let ty
= cx
.typeck_results().expr_ty(&args
[0]);
352 if let ty
::Ref(_
, ty
, ..) = ty
.kind();
353 if *ty
.kind() == ty
::Str
;
359 "`to_string()` called on a `&str`",
361 "consider using `.to_owned()`",
368 declare_clippy_lint
! {
369 /// **What it does:** This lint checks for `.to_string()` method calls on values of type `String`.
371 /// **Why is this bad?** The `to_string` method is also used on other types to convert them to a string.
372 /// When called on a `String` it only clones the `String`, which can be better expressed with `.clone()`.
373 /// **Known problems:** None.
378 /// // example code where clippy issues a warning
379 /// let msg = String::from("Hello World");
380 /// let _ = msg.to_string();
384 /// // example code which does not raise clippy warning
385 /// let msg = String::from("Hello World");
386 /// let _ = msg.clone();
388 pub STRING_TO_STRING
,
390 "using `to_string()` on a `String`, which should be `clone()`"
393 declare_lint_pass
!(StringToString
=> [STRING_TO_STRING
]);
395 impl LateLintPass
<'_
> for StringToString
{
396 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &Expr
<'_
>) {
398 if let ExprKind
::MethodCall(path
, _
, args
, _
) = &expr
.kind
;
399 if path
.ident
.name
== sym
!(to_string
);
400 let ty
= cx
.typeck_results().expr_ty(&args
[0]);
401 if is_type_diagnostic_item(cx
, ty
, sym
::string_type
);
407 "`to_string()` called on a `String`",
409 "consider using `.clone()`",