Skip to content

Commit e29b3b5

Browse files
committed
Auto merge of #8579 - yoav-lavi:squashed-master, r=flip1995
`unnecessary_join` lint changelog: Adds a lint called ``[`unnecessary_join`]`` that detects cases of `.collect::<Vec<String>>.join("")` or `.collect::<Vec<_>>.join("")` on an iterator, suggesting `.collect::<String>()` instead Fixes: rust-lang/rust-clippy#8570 This is a reopen of rust-lang/rust-clippy#8573 changelog: add lint [`unnecessary_join`]
2 parents f07ee8a + b60a7fb commit e29b3b5

8 files changed

+172
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3508,6 +3508,7 @@ Released 2018-09-13
35083508
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
35093509
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
35103510
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
3511+
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
35113512
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
35123513
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
35133514
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ store.register_lints(&[
334334
methods::UNNECESSARY_FILTER_MAP,
335335
methods::UNNECESSARY_FIND_MAP,
336336
methods::UNNECESSARY_FOLD,
337+
methods::UNNECESSARY_JOIN,
337338
methods::UNNECESSARY_LAZY_EVALUATIONS,
338339
methods::UNNECESSARY_TO_OWNED,
339340
methods::UNWRAP_OR_ELSE_DEFAULT,

clippy_lints/src/lib.register_pedantic.rs

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
6363
LintId::of(methods::IMPLICIT_CLONE),
6464
LintId::of(methods::INEFFICIENT_TO_STRING),
6565
LintId::of(methods::MAP_UNWRAP_OR),
66+
LintId::of(methods::UNNECESSARY_JOIN),
6667
LintId::of(misc::FLOAT_CMP),
6768
LintId::of(misc::USED_UNDERSCORE_BINDING),
6869
LintId::of(mut_mut::MUT_MUT),

clippy_lints/src/methods/mod.rs

+36
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ mod uninit_assumed_init;
6060
mod unnecessary_filter_map;
6161
mod unnecessary_fold;
6262
mod unnecessary_iter_cloned;
63+
mod unnecessary_join;
6364
mod unnecessary_lazy_eval;
6465
mod unnecessary_to_owned;
6566
mod unwrap_or_else_default;
@@ -2049,6 +2050,35 @@ declare_clippy_lint! {
20492050
"unnecessary calls to `to_owned`-like functions"
20502051
}
20512052

2053+
declare_clippy_lint! {
2054+
/// ### What it does
2055+
/// Checks for use of `.collect::<Vec<String>>().join("")` on iterators.
2056+
///
2057+
/// ### Why is this bad?
2058+
/// `.collect::<String>()` is more concise and usually more performant
2059+
///
2060+
/// ### Example
2061+
/// ```rust
2062+
/// let vector = vec!["hello", "world"];
2063+
/// let output = vector.iter().map(|item| item.to_uppercase()).collect::<Vec<String>>().join("");
2064+
/// println!("{}", output);
2065+
/// ```
2066+
/// The correct use would be:
2067+
/// ```rust
2068+
/// let vector = vec!["hello", "world"];
2069+
/// let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
2070+
/// println!("{}", output);
2071+
/// ```
2072+
/// ### Known problems
2073+
/// While `.collect::<String>()` is more performant in most cases, there are cases where
2074+
/// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
2075+
/// will prevent loop unrolling and will result in a negative performance impact.
2076+
#[clippy::version = "1.61.0"]
2077+
pub UNNECESSARY_JOIN,
2078+
pedantic,
2079+
"using `.collect::<Vec<String>>().join(\"\")` on an iterator"
2080+
}
2081+
20522082
pub struct Methods {
20532083
avoid_breaking_exported_api: bool,
20542084
msrv: Option<RustcVersion>,
@@ -2134,6 +2164,7 @@ impl_lint_pass!(Methods => [
21342164
MANUAL_SPLIT_ONCE,
21352165
NEEDLESS_SPLITN,
21362166
UNNECESSARY_TO_OWNED,
2167+
UNNECESSARY_JOIN,
21372168
]);
21382169

21392170
/// Extracts a method call name, args, and `Span` of the method name.
@@ -2429,6 +2460,11 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
24292460
("is_file", []) => filetype_is_file::check(cx, expr, recv),
24302461
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
24312462
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
2463+
("join", [join_arg]) => {
2464+
if let Some(("collect", _, span)) = method_call(recv) {
2465+
unnecessary_join::check(cx, expr, recv, join_arg, span);
2466+
}
2467+
},
24322468
("last", args @ []) | ("skip", args @ [_]) => {
24332469
if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
24342470
if let ("cloned", []) = (name2, args2) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use clippy_utils::{diagnostics::span_lint_and_sugg, ty::is_type_diagnostic_item};
2+
use rustc_ast::ast::LitKind;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{Expr, ExprKind};
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty::{Ref, Slice};
7+
use rustc_span::{sym, Span};
8+
9+
use super::UNNECESSARY_JOIN;
10+
11+
pub(super) fn check<'tcx>(
12+
cx: &LateContext<'tcx>,
13+
expr: &'tcx Expr<'tcx>,
14+
join_self_arg: &'tcx Expr<'tcx>,
15+
join_arg: &'tcx Expr<'tcx>,
16+
span: Span,
17+
) {
18+
let applicability = Applicability::MachineApplicable;
19+
let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg);
20+
if_chain! {
21+
// the turbofish for collect is ::<Vec<String>>
22+
if let Ref(_, ref_type, _) = collect_output_adjusted_type.kind();
23+
if let Slice(slice) = ref_type.kind();
24+
if is_type_diagnostic_item(cx, *slice, sym::String);
25+
// the argument for join is ""
26+
if let ExprKind::Lit(spanned) = &join_arg.kind;
27+
if let LitKind::Str(symbol, _) = spanned.node;
28+
if symbol.is_empty();
29+
then {
30+
span_lint_and_sugg(
31+
cx,
32+
UNNECESSARY_JOIN,
33+
span.with_hi(expr.span.hi()),
34+
r#"called `.collect<Vec<String>>().join("")` on an iterator"#,
35+
"try using",
36+
"collect::<String>()".to_owned(),
37+
applicability,
38+
);
39+
}
40+
}
41+
}

tests/ui/unnecessary_join.fixed

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::unnecessary_join)]
4+
5+
fn main() {
6+
// should be linted
7+
let vector = vec!["hello", "world"];
8+
let output = vector
9+
.iter()
10+
.map(|item| item.to_uppercase())
11+
.collect::<String>();
12+
println!("{}", output);
13+
14+
// should be linted
15+
let vector = vec!["hello", "world"];
16+
let output = vector
17+
.iter()
18+
.map(|item| item.to_uppercase())
19+
.collect::<String>();
20+
println!("{}", output);
21+
22+
// should not be linted
23+
let vector = vec!["hello", "world"];
24+
let output = vector
25+
.iter()
26+
.map(|item| item.to_uppercase())
27+
.collect::<Vec<String>>()
28+
.join("\n");
29+
println!("{}", output);
30+
31+
// should not be linted
32+
let vector = vec!["hello", "world"];
33+
let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
34+
println!("{}", output);
35+
}

tests/ui/unnecessary_join.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::unnecessary_join)]
4+
5+
fn main() {
6+
// should be linted
7+
let vector = vec!["hello", "world"];
8+
let output = vector
9+
.iter()
10+
.map(|item| item.to_uppercase())
11+
.collect::<Vec<String>>()
12+
.join("");
13+
println!("{}", output);
14+
15+
// should be linted
16+
let vector = vec!["hello", "world"];
17+
let output = vector
18+
.iter()
19+
.map(|item| item.to_uppercase())
20+
.collect::<Vec<_>>()
21+
.join("");
22+
println!("{}", output);
23+
24+
// should not be linted
25+
let vector = vec!["hello", "world"];
26+
let output = vector
27+
.iter()
28+
.map(|item| item.to_uppercase())
29+
.collect::<Vec<String>>()
30+
.join("\n");
31+
println!("{}", output);
32+
33+
// should not be linted
34+
let vector = vec!["hello", "world"];
35+
let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
36+
println!("{}", output);
37+
}

tests/ui/unnecessary_join.stderr

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: called `.collect<Vec<String>>().join("")` on an iterator
2+
--> $DIR/unnecessary_join.rs:11:10
3+
|
4+
LL | .collect::<Vec<String>>()
5+
| __________^
6+
LL | | .join("");
7+
| |_________________^ help: try using: `collect::<String>()`
8+
|
9+
= note: `-D clippy::unnecessary-join` implied by `-D warnings`
10+
11+
error: called `.collect<Vec<String>>().join("")` on an iterator
12+
--> $DIR/unnecessary_join.rs:20:10
13+
|
14+
LL | .collect::<Vec<_>>()
15+
| __________^
16+
LL | | .join("");
17+
| |_________________^ help: try using: `collect::<String>()`
18+
19+
error: aborting due to 2 previous errors
20+

0 commit comments

Comments
 (0)