Skip to content

Commit fa78b09

Browse files
Add lint for string.extend("str".chars())
fixes #792
1 parent 0ab7e6c commit fa78b09

File tree

5 files changed

+88
-2
lines changed

5 files changed

+88
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ All notable changes to this project will be documented in this file.
359359
[`str_to_string`]: https://github.com/Manishearth/rust-clippy/wiki#str_to_string
360360
[`string_add`]: https://github.com/Manishearth/rust-clippy/wiki#string_add
361361
[`string_add_assign`]: https://github.com/Manishearth/rust-clippy/wiki#string_add_assign
362+
[`string_extend_chars`]: https://github.com/Manishearth/rust-clippy/wiki#string_extend_chars
362363
[`string_lit_as_bytes`]: https://github.com/Manishearth/rust-clippy/wiki#string_lit_as_bytes
363364
[`string_to_string`]: https://github.com/Manishearth/rust-clippy/wiki#string_to_string
364365
[`stutter`]: https://github.com/Manishearth/rust-clippy/wiki#stutter

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ You can check out this great service at [clippy.bashy.io](https://clippy.bashy.i
182182

183183
## Lints
184184

185-
There are 177 lints included in this crate:
185+
There are 178 lints included in this crate:
186186

187187
name | default | triggers on
188188
-----------------------------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------
@@ -327,6 +327,7 @@ name
327327
[single_match_else](https://github.com/Manishearth/rust-clippy/wiki#single_match_else) | allow | a match statement with a two arms where the second arm's pattern is a wildcard instead of `if let`
328328
[string_add](https://github.com/Manishearth/rust-clippy/wiki#string_add) | allow | using `x + ..` where x is a `String` instead of `push_str()`
329329
[string_add_assign](https://github.com/Manishearth/rust-clippy/wiki#string_add_assign) | allow | using `x = x + ..` where x is a `String` instead of `push_str()`
330+
[string_extend_chars](https://github.com/Manishearth/rust-clippy/wiki#string_extend_chars) | warn | using `x.extend(s.chars())` where s is a `&str`
330331
[string_lit_as_bytes](https://github.com/Manishearth/rust-clippy/wiki#string_lit_as_bytes) | warn | calling `as_bytes` on a string literal instead of using a byte string literal
331332
[stutter](https://github.com/Manishearth/rust-clippy/wiki#stutter) | allow | type names prefixed/postfixed with their containing module's name
332333
[suspicious_assignment_formatting](https://github.com/Manishearth/rust-clippy/wiki#suspicious_assignment_formatting) | warn | suspicious formatting of `*=`, `-=` or `!=`

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
391391
methods::SEARCH_IS_SOME,
392392
methods::SHOULD_IMPLEMENT_TRAIT,
393393
methods::SINGLE_CHAR_PATTERN,
394+
methods::STRING_EXTEND_CHARS,
394395
methods::TEMPORARY_CSTRING_AS_PTR,
395396
methods::WRONG_SELF_CONVENTION,
396397
minmax::MIN_MAX,

clippy_lints/src/methods.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,32 @@ declare_lint! {
490490
"using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead"
491491
}
492492

493+
/// **What it does:** Checks for the use of `.extend(s.chars())` where s is a
494+
/// `&str`.
495+
///
496+
/// **Why is this bad?** `.push_str(s)` is clearer and faster
497+
///
498+
/// **Known problems:** None.
499+
///
500+
/// **Example:**
501+
/// ```rust
502+
/// let abc = "abc";
503+
/// let mut s = String::new();
504+
/// s.extend(abc.chars());
505+
/// ```
506+
/// The correct use would be:
507+
/// ```rust
508+
/// let abc = "abc";
509+
/// let mut s = String::new();
510+
/// s.push_str(abc);
511+
/// ```
512+
513+
declare_lint! {
514+
pub STRING_EXTEND_CHARS,
515+
Warn,
516+
"using `x.extend(s.chars())` where s is a `&str`"
517+
}
518+
493519

494520
impl LintPass for Pass {
495521
fn get_lints(&self) -> LintArray {
@@ -514,7 +540,8 @@ impl LintPass for Pass {
514540
FILTER_MAP,
515541
ITER_NTH,
516542
ITER_SKIP_NEXT,
517-
GET_UNWRAP)
543+
GET_UNWRAP,
544+
STRING_EXTEND_CHARS)
518545
}
519546
}
520547

@@ -807,10 +834,33 @@ fn lint_vec_extend(cx: &LateContext, expr: &hir::Expr, args: &MethodArgs) {
807834
}
808835
}
809836

837+
fn lint_string_extend(cx: &LateContext, expr: &hir::Expr, args: &MethodArgs) {
838+
let arg = &args[1];
839+
if let Some(arglists) = method_chain_args(arg, &["chars"]) {
840+
let target = &arglists[0][0];
841+
let (self_ty, _) = walk_ptrs_ty_depth(cx.tcx.tables().expr_ty(target));
842+
if self_ty.sty == ty::TyStr {
843+
span_lint_and_then(
844+
cx,
845+
STRING_EXTEND_CHARS,
846+
expr.span,
847+
"calling `.extend(_.chars())`",
848+
|db| {
849+
db.span_suggestion(expr.span, "try this",
850+
format!("{}.push_str({})",
851+
snippet(cx, args[0].span, "_"),
852+
snippet(cx, target.span, "_")));
853+
});
854+
}
855+
}
856+
}
857+
810858
fn lint_extend(cx: &LateContext, expr: &hir::Expr, args: &MethodArgs) {
811859
let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.tables().expr_ty(&args[0]));
812860
if match_type(cx, obj_ty, &paths::VEC) {
813861
lint_vec_extend(cx, expr, args);
862+
} else if match_type(cx, obj_ty, &paths::STRING) {
863+
lint_string_extend(cx, expr, args);
814864
}
815865
}
816866

tests/compile-fail/methods.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@ impl IteratorFalsePositives {
180180
}
181181
}
182182

183+
#[derive(Copy, Clone)]
184+
struct HasChars;
185+
186+
impl HasChars {
187+
fn chars(self) -> std::str::Chars<'static> {
188+
"HasChars".chars()
189+
}
190+
}
191+
183192
/// Checks implementation of `FILTER_NEXT` lint
184193
fn filter_next() {
185194
let v = vec![3, 2, 1, 0, -1, -2, -3];
@@ -524,6 +533,30 @@ fn use_extend_from_slice() {
524533
//~| SUGGESTION v.extend_from_slice(&["But", "this"]);
525534
}
526535

536+
fn str_extend_chars() {
537+
let abc = "abc";
538+
let mut s = String::new();
539+
540+
s.push_str(abc);
541+
s.extend(abc.chars());
542+
//~^ERROR calling `.extend(_.chars())`
543+
//~|HELP try this
544+
//~|SUGGESTION s.push_str(abc)
545+
546+
s.push_str("abc");
547+
s.extend("abc".chars());
548+
//~^ERROR calling `.extend(_.chars())`
549+
//~|HELP try this
550+
//~|SUGGESTION s.push_str("abc")
551+
552+
s.extend(abc.chars().skip(1));
553+
s.extend("abc".chars().skip(1));
554+
s.extend(['a', 'b', 'c'].iter());
555+
556+
let f = HasChars;
557+
s.extend(f.chars());
558+
}
559+
527560
fn clone_on_copy() {
528561
42.clone(); //~ERROR using `clone` on a `Copy` type
529562
//~| HELP try removing the `clone` call

0 commit comments

Comments
 (0)