Skip to content

Commit 530083c

Browse files
authored
Merge pull request #1349 from philipturnbull/extend-chars
Lint `.extend(s.chars())` (closes #792)
2 parents e2908df + 8705f3d commit 530083c

File tree

5 files changed

+114
-7
lines changed

5 files changed

+114
-7
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ All notable changes to this project will be documented in this file.
362362
[`str_to_string`]: https://github.com/Manishearth/rust-clippy/wiki#str_to_string
363363
[`string_add`]: https://github.com/Manishearth/rust-clippy/wiki#string_add
364364
[`string_add_assign`]: https://github.com/Manishearth/rust-clippy/wiki#string_add_assign
365+
[`string_extend_chars`]: https://github.com/Manishearth/rust-clippy/wiki#string_extend_chars
365366
[`string_lit_as_bytes`]: https://github.com/Manishearth/rust-clippy/wiki#string_lit_as_bytes
366367
[`string_to_string`]: https://github.com/Manishearth/rust-clippy/wiki#string_to_string
367368
[`stutter`]: https://github.com/Manishearth/rust-clippy/wiki#stutter

README.md

+2-1
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` or `String`
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

+1
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

+70-6
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,36 @@ 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` or `String`.
495+
///
496+
/// **Why is this bad?** `.push_str(s)` is clearer
497+
///
498+
/// **Known problems:** None.
499+
///
500+
/// **Example:**
501+
/// ```rust
502+
/// let abc = "abc";
503+
/// let def = String::from("def");
504+
/// let mut s = String::new();
505+
/// s.extend(abc.chars());
506+
/// s.extend(def.chars());
507+
/// ```
508+
/// The correct use would be:
509+
/// ```rust
510+
/// let abc = "abc";
511+
/// let def = String::from("def");
512+
/// let mut s = String::new();
513+
/// s.push_str(abc);
514+
/// s.push_str(&def));
515+
/// ```
516+
517+
declare_lint! {
518+
pub STRING_EXTEND_CHARS,
519+
Warn,
520+
"using `x.extend(s.chars())` where s is a `&str` or `String`"
521+
}
522+
493523

494524
impl LintPass for Pass {
495525
fn get_lints(&self) -> LintArray {
@@ -514,7 +544,8 @@ impl LintPass for Pass {
514544
FILTER_MAP,
515545
ITER_NTH,
516546
ITER_SKIP_NEXT,
517-
GET_UNWRAP)
547+
GET_UNWRAP,
548+
STRING_EXTEND_CHARS)
518549
}
519550
}
520551

@@ -794,11 +825,7 @@ fn lint_clone_on_copy(cx: &LateContext, expr: &hir::Expr, arg: &hir::Expr, arg_t
794825
}
795826
}
796827

797-
fn lint_extend(cx: &LateContext, expr: &hir::Expr, args: &MethodArgs) {
798-
let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.tables().expr_ty(&args[0]));
799-
if !match_type(cx, obj_ty, &paths::VEC) {
800-
return;
801-
}
828+
fn lint_vec_extend(cx: &LateContext, expr: &hir::Expr, args: &MethodArgs) {
802829
let arg_ty = cx.tcx.tables().expr_ty(&args[1]);
803830
if let Some(slice) = derefs_to_slice(cx, &args[1], arg_ty) {
804831
span_lint_and_then(cx, EXTEND_FROM_SLICE, expr.span, "use of `extend` to extend a Vec by a slice", |db| {
@@ -811,6 +838,43 @@ fn lint_extend(cx: &LateContext, expr: &hir::Expr, args: &MethodArgs) {
811838
}
812839
}
813840

841+
fn lint_string_extend(cx: &LateContext, expr: &hir::Expr, args: &MethodArgs) {
842+
let arg = &args[1];
843+
if let Some(arglists) = method_chain_args(arg, &["chars"]) {
844+
let target = &arglists[0][0];
845+
let (self_ty, _) = walk_ptrs_ty_depth(cx.tcx.tables().expr_ty(target));
846+
let ref_str = if self_ty.sty == ty::TyStr {
847+
""
848+
} else if match_type(cx, self_ty, &paths::STRING) {
849+
"&"
850+
} else {
851+
return;
852+
};
853+
854+
span_lint_and_then(
855+
cx,
856+
STRING_EXTEND_CHARS,
857+
expr.span,
858+
"calling `.extend(_.chars())`",
859+
|db| {
860+
db.span_suggestion(expr.span, "try this",
861+
format!("{}.push_str({}{})",
862+
snippet(cx, args[0].span, "_"),
863+
ref_str,
864+
snippet(cx, target.span, "_")));
865+
});
866+
}
867+
}
868+
869+
fn lint_extend(cx: &LateContext, expr: &hir::Expr, args: &MethodArgs) {
870+
let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.tables().expr_ty(&args[0]));
871+
if match_type(cx, obj_ty, &paths::VEC) {
872+
lint_vec_extend(cx, expr, args);
873+
} else if match_type(cx, obj_ty, &paths::STRING) {
874+
lint_string_extend(cx, expr, args);
875+
}
876+
}
877+
814878
fn lint_cstring_as_ptr(cx: &LateContext, expr: &hir::Expr, new: &hir::Expr, unwrap: &hir::Expr) {
815879
if_let_chain!{[
816880
let hir::ExprCall(ref fun, ref args) = new.node,

tests/compile-fail/methods.rs

+40
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,37 @@ 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 def = String::from("def");
539+
let mut s = String::new();
540+
541+
s.push_str(abc);
542+
s.extend(abc.chars());
543+
//~^ERROR calling `.extend(_.chars())`
544+
//~|HELP try this
545+
//~|SUGGESTION s.push_str(abc)
546+
547+
s.push_str("abc");
548+
s.extend("abc".chars());
549+
//~^ERROR calling `.extend(_.chars())`
550+
//~|HELP try this
551+
//~|SUGGESTION s.push_str("abc")
552+
553+
s.push_str(&def);
554+
s.extend(def.chars());
555+
//~^ERROR calling `.extend(_.chars())`
556+
//~|HELP try this
557+
//~|SUGGESTION s.push_str(&def)
558+
559+
s.extend(abc.chars().skip(1));
560+
s.extend("abc".chars().skip(1));
561+
s.extend(['a', 'b', 'c'].iter());
562+
563+
let f = HasChars;
564+
s.extend(f.chars());
565+
}
566+
527567
fn clone_on_copy() {
528568
42.clone(); //~ERROR using `clone` on a `Copy` type
529569
//~| HELP try removing the `clone` call

0 commit comments

Comments
 (0)