Skip to content

Commit 92cad4a

Browse files
committed
feat(unnecessary_fold): lint on folds with Add::add/Mul::mul
1 parent 93a4992 commit 92cad4a

File tree

4 files changed

+363
-69
lines changed

4 files changed

+363
-69
lines changed

clippy_lints/src/methods/unnecessary_fold.rs

Lines changed: 80 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes};
2+
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes};
33
use clippy_utils::source::snippet_with_applicability;
44
use clippy_utils::{peel_blocks, strip_pat_refs};
55
use rustc_ast::ast;
66
use rustc_data_structures::packed::Pu128;
77
use rustc_errors::Applicability;
88
use rustc_hir as hir;
99
use rustc_hir::PatKind;
10+
use rustc_hir::def::{DefKind, Res};
1011
use rustc_lint::LateContext;
1112
use rustc_middle::ty;
12-
use rustc_span::{Span, sym};
13+
use rustc_span::{Span, Symbol, sym};
1314

1415
use super::UNNECESSARY_FOLD;
1516

@@ -41,6 +42,18 @@ fn needs_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
4142
return false;
4243
}
4344

45+
// - the final expression in the body of a function with a simple return type
46+
if let hir::Node::Block(block) = parent
47+
&& let mut parents = cx.tcx.hir_parent_iter(block.hir_id).map(|(_, def_id)| def_id)
48+
&& let Some(hir::Node::Expr(_)) = parents.next()
49+
&& let Some(hir::Node::Item(enclosing_item)) = parents.next()
50+
&& let hir::ItemKind::Fn { sig, .. } = enclosing_item.kind
51+
&& let hir::FnRetTy::Return(fn_return_ty) = sig.decl.output
52+
&& matches!(fn_return_ty.kind, hir::TyKind::Path(..))
53+
{
54+
return false;
55+
}
56+
4457
// if it's neither of those, stay on the safe side and suggest turbofish,
4558
// even if it could work!
4659
true
@@ -60,7 +73,7 @@ fn check_fold_with_op(
6073
fold_span: Span,
6174
op: hir::BinOpKind,
6275
replacement: Replacement,
63-
) {
76+
) -> bool {
6477
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind
6578
// Extract the body of the closure passed to fold
6679
&& let closure_body = cx.tcx.hir_body(body)
@@ -93,7 +106,7 @@ fn check_fold_with_op(
93106
r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
94107
)
95108
} else {
96-
format!("{method}{turbofish}()", method = replacement.method_name,)
109+
format!("{method}{turbofish}()", method = replacement.method_name)
97110
};
98111

99112
span_lint_and_sugg(
@@ -105,6 +118,41 @@ fn check_fold_with_op(
105118
sugg,
106119
applicability,
107120
);
121+
return true;
122+
}
123+
false
124+
}
125+
126+
fn check_fold_with_method(
127+
cx: &LateContext<'_>,
128+
expr: &hir::Expr<'_>,
129+
acc: &hir::Expr<'_>,
130+
fold_span: Span,
131+
method: Symbol,
132+
replacement: Replacement,
133+
) {
134+
// Extract the name of the function passed to `fold`
135+
if let Res::Def(DefKind::AssocFn, fn_did) = acc.res_if_named(cx, method)
136+
// Check if the function belongs to the operator
137+
&& cx.tcx.is_diagnostic_item(method, fn_did)
138+
{
139+
let applicability = Applicability::MachineApplicable;
140+
141+
let turbofish = if replacement.has_generic_return {
142+
format!("::<{}>", cx.typeck_results().expr_ty(expr))
143+
} else {
144+
String::new()
145+
};
146+
147+
span_lint_and_sugg(
148+
cx,
149+
UNNECESSARY_FOLD,
150+
fold_span.with_hi(expr.span.hi()),
151+
"this `.fold` can be written more succinctly using another method",
152+
"try",
153+
format!("{method}{turbofish}()", method = replacement.method_name),
154+
applicability,
155+
);
108156
}
109157
}
110158

@@ -124,60 +172,40 @@ pub(super) fn check(
124172
if let hir::ExprKind::Lit(lit) = init.kind {
125173
match lit.node {
126174
ast::LitKind::Bool(false) => {
127-
check_fold_with_op(
128-
cx,
129-
expr,
130-
acc,
131-
fold_span,
132-
hir::BinOpKind::Or,
133-
Replacement {
134-
method_name: "any",
135-
has_args: true,
136-
has_generic_return: false,
137-
},
138-
);
175+
let replacement = Replacement {
176+
method_name: "any",
177+
has_args: true,
178+
has_generic_return: false,
179+
};
180+
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, replacement);
139181
},
140182
ast::LitKind::Bool(true) => {
141-
check_fold_with_op(
142-
cx,
143-
expr,
144-
acc,
145-
fold_span,
146-
hir::BinOpKind::And,
147-
Replacement {
148-
method_name: "all",
149-
has_args: true,
150-
has_generic_return: false,
151-
},
152-
);
183+
let replacement = Replacement {
184+
method_name: "all",
185+
has_args: true,
186+
has_generic_return: false,
187+
};
188+
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, replacement);
153189
},
154190
ast::LitKind::Int(Pu128(0), _) => {
155-
check_fold_with_op(
156-
cx,
157-
expr,
158-
acc,
159-
fold_span,
160-
hir::BinOpKind::Add,
161-
Replacement {
162-
method_name: "sum",
163-
has_args: false,
164-
has_generic_return: needs_turbofish(cx, expr),
165-
},
166-
);
191+
let replacement = Replacement {
192+
method_name: "sum",
193+
has_args: false,
194+
has_generic_return: needs_turbofish(cx, expr),
195+
};
196+
if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, replacement) {
197+
check_fold_with_method(cx, expr, acc, fold_span, sym::add, replacement);
198+
}
167199
},
168200
ast::LitKind::Int(Pu128(1), _) => {
169-
check_fold_with_op(
170-
cx,
171-
expr,
172-
acc,
173-
fold_span,
174-
hir::BinOpKind::Mul,
175-
Replacement {
176-
method_name: "product",
177-
has_args: false,
178-
has_generic_return: needs_turbofish(cx, expr),
179-
},
180-
);
201+
let replacement = Replacement {
202+
method_name: "product",
203+
has_args: false,
204+
has_generic_return: needs_turbofish(cx, expr),
205+
};
206+
if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, replacement) {
207+
check_fold_with_method(cx, expr, acc, fold_span, sym::mul, replacement);
208+
}
181209
},
182210
_ => (),
183211
}

tests/ui/unnecessary_fold.fixed

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,35 @@ fn is_any(acc: bool, x: usize) -> bool {
66

77
/// Calls which should trigger the `UNNECESSARY_FOLD` lint
88
fn unnecessary_fold() {
9+
use std::ops::{Add, Mul};
10+
911
// Can be replaced by .any
1012
let _ = (0..3).any(|x| x > 2);
1113
//~^ unnecessary_fold
14+
1215
// Can be replaced by .any (checking suggestion)
1316
let _ = (0..3).fold(false, is_any);
1417
//~^ redundant_closure
18+
1519
// Can be replaced by .all
1620
let _ = (0..3).all(|x| x > 2);
1721
//~^ unnecessary_fold
22+
1823
// Can be replaced by .sum
1924
let _: i32 = (0..3).sum();
2025
//~^ unnecessary_fold
26+
let _: i32 = (0..3).sum();
27+
//~^ unnecessary_fold
28+
let _: i32 = (0..3).sum();
29+
//~^ unnecessary_fold
30+
2131
// Can be replaced by .product
2232
let _: i32 = (0..3).product();
2333
//~^ unnecessary_fold
34+
let _: i32 = (0..3).product();
35+
//~^ unnecessary_fold
36+
let _: i32 = (0..3).product();
37+
//~^ unnecessary_fold
2438
}
2539

2640
/// Should trigger the `UNNECESSARY_FOLD` lint, with an error span including exactly `.fold(...)`
@@ -37,6 +51,43 @@ fn unnecessary_fold_should_ignore() {
3751
let _ = (0..3).fold(0, |acc, x| acc * x);
3852
let _ = (0..3).fold(0, |acc, x| 1 + acc + x);
3953

54+
struct Adder;
55+
impl Adder {
56+
fn add(lhs: i32, rhs: i32) -> i32 {
57+
unimplemented!()
58+
}
59+
fn mul(lhs: i32, rhs: i32) -> i32 {
60+
unimplemented!()
61+
}
62+
}
63+
// `add`/`mul` are inherent methods
64+
let _: i32 = (0..3).fold(0, Adder::add);
65+
let _: i32 = (0..3).fold(1, Adder::mul);
66+
67+
trait FakeAdd<Rhs = Self> {
68+
type Output;
69+
fn add(self, other: Rhs) -> Self::Output;
70+
}
71+
impl FakeAdd for i32 {
72+
type Output = Self;
73+
fn add(self, other: i32) -> Self::Output {
74+
self + other
75+
}
76+
}
77+
trait FakeMul<Rhs = Self> {
78+
type Output;
79+
fn mul(self, other: Rhs) -> Self::Output;
80+
}
81+
impl FakeMul for i32 {
82+
type Output = Self;
83+
fn mul(self, other: i32) -> Self::Output {
84+
self * other
85+
}
86+
}
87+
// `add`/`mul` come from an unrelated trait
88+
let _: i32 = (0..3).fold(0, FakeAdd::add);
89+
let _: i32 = (0..3).fold(1, FakeMul::mul);
90+
4091
// We only match against an accumulator on the left
4192
// hand side. We could lint for .sum and .product when
4293
// it's on the right, but don't for now (and this wouldn't
@@ -63,6 +114,7 @@ fn unnecessary_fold_over_multiple_lines() {
63114
fn issue10000() {
64115
use std::collections::HashMap;
65116
use std::hash::BuildHasher;
117+
use std::ops::{Add, Mul};
66118

67119
fn anything<T>(_: T) {}
68120
fn num(_: i32) {}
@@ -74,23 +126,56 @@ fn issue10000() {
74126
// more cases:
75127
let _ = map.values().sum::<i32>();
76128
//~^ unnecessary_fold
129+
let _ = map.values().sum::<i32>();
130+
//~^ unnecessary_fold
77131
let _ = map.values().product::<i32>();
78132
//~^ unnecessary_fold
133+
let _ = map.values().product::<i32>();
134+
//~^ unnecessary_fold
135+
let _: i32 = map.values().sum();
136+
//~^ unnecessary_fold
79137
let _: i32 = map.values().sum();
80138
//~^ unnecessary_fold
81139
let _: i32 = map.values().product();
82140
//~^ unnecessary_fold
141+
let _: i32 = map.values().product();
142+
//~^ unnecessary_fold
83143
anything(map.values().sum::<i32>());
84144
//~^ unnecessary_fold
145+
anything(map.values().sum::<i32>());
146+
//~^ unnecessary_fold
147+
anything(map.values().product::<i32>());
148+
//~^ unnecessary_fold
85149
anything(map.values().product::<i32>());
86150
//~^ unnecessary_fold
87151
num(map.values().sum());
88152
//~^ unnecessary_fold
153+
num(map.values().sum());
154+
//~^ unnecessary_fold
155+
num(map.values().product());
156+
//~^ unnecessary_fold
89157
num(map.values().product());
90158
//~^ unnecessary_fold
91159
}
92160

93161
smoketest_map(HashMap::new());
162+
163+
fn add_turbofish_not_necessary() -> i32 {
164+
(0..3).sum()
165+
//~^ unnecessary_fold
166+
}
167+
fn mul_turbofish_not_necessary() -> i32 {
168+
(0..3).product()
169+
//~^ unnecessary_fold
170+
}
171+
fn add_turbofish_necessary() -> impl Add {
172+
(0..3).sum::<i32>()
173+
//~^ unnecessary_fold
174+
}
175+
fn mul_turbofish_necessary() -> impl Mul {
176+
(0..3).product::<i32>()
177+
//~^ unnecessary_fold
178+
}
94179
}
95180

96181
fn main() {}

0 commit comments

Comments
 (0)