wowlab_tidy/languages/rust/rules/performance/
unnecessary_collect.rs1use ra_ap_syntax::ast;
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "collect then iter",
9 code: "fn f() { (0..10).collect::<Vec<i32>>().iter().count(); }",
10 pass: false,
11 },
12 Example {
13 label: "collect then into_iter",
14 code: "fn f() { (0..10).collect::<Vec<i32>>().into_iter().count(); }",
15 pass: false,
16 },
17 Example {
18 label: "separate collect",
19 code: "fn f() { let v: Vec<i32> = (0..10).collect(); v.iter().count(); }",
20 pass: true,
21 },
22 Example {
23 label: "collect without iter",
24 code: "fn f() { let v: Vec<i32> = (0..10).collect(); }",
25 pass: true,
26 },
27 Example {
28 label: "collect iter in test module",
29 code: "#[cfg(test)]\nmod tests {\n fn f() { (0..10).collect::<Vec<i32>>().iter().count(); }\n}",
30 pass: true,
31 },
32];
33
34crate::ast_rule!(
35 unnecessary_collect,
36 "Flag `.collect().iter()` — remove the intermediate collection.",
37 "Collecting into a Vec just to iterate it again wastes an allocation. Chain the iterators directly.",
38);
39
40fn check_unnecessary_collect(ctx: &AstCtx<'_>) -> Vec<Violation> {
41 ctx.nodes::<ast::MethodCallExpr>()
42 .filter(|call| !ctx.is_in_test(call))
43 .filter_map(|call| {
44 let method = call.name_ref()?.text().to_string();
45
46 if method != "iter" && method != "into_iter" {
47 return None;
48 }
49
50 let ast::Expr::MethodCallExpr(receiver) = call.receiver()? else {
51 return None;
52 };
53
54 receiver
55 .name_ref()
56 .is_some_and(|name| name.text() == "collect")
57 .then(|| {
58 ctx.violation(
59 &call,
60 format!(
61 ".collect().{method}() is redundant — remove the intermediate collect"
62 ),
63 )
64 })
65 })
66 .collect()
67}
68
69crate::tidy_ast_test!(check_unnecessary_collect, {
70 crate::example_tests!(EXAMPLES, check_unnecessary_collect);
71});