Skip to content

Commit 2dd452f

Browse files
committed
Auto merge of #10931 - y21:issue10000, r=Jarcho
[`unnecessary_fold`]: suggest turbofish if necessary Fixes #10000 This adds turbofish `::<T>` to the suggestion in `unnecessary_fold`. This is necessary because the `Sum` trait is generic, which breaks inference when changing `fold()` to `sum()`. changelog: [`unnecessary_fold`]: suggest turbofish if necessary
2 parents 823d9dd + d102e22 commit 2dd452f

File tree

4 files changed

+257
-58
lines changed

4 files changed

+257
-58
lines changed

clippy_lints/src/methods/unnecessary_fold.rs

Lines changed: 154 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,71 +7,120 @@ use rustc_errors::Applicability;
77
use rustc_hir as hir;
88
use rustc_hir::PatKind;
99
use rustc_lint::LateContext;
10+
use rustc_middle::ty;
1011
use rustc_span::{source_map::Span, sym};
1112

1213
use super::UNNECESSARY_FOLD;
1314

14-
pub(super) fn check(
15+
/// Do we need to suggest turbofish when suggesting a replacement method?
16+
/// Changing `fold` to `sum` needs it sometimes when the return type can't be
17+
/// inferred. This checks for some common cases where it can be safely omitted
18+
fn needs_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
19+
let parent = cx.tcx.hir().get_parent(expr.hir_id);
20+
21+
// some common cases where turbofish isn't needed:
22+
// - assigned to a local variable with a type annotation
23+
if let hir::Node::Local(local) = parent
24+
&& local.ty.is_some()
25+
{
26+
return false;
27+
}
28+
29+
// - part of a function call argument, can be inferred from the function signature (provided that
30+
// the parameter is not a generic type parameter)
31+
if let hir::Node::Expr(parent_expr) = parent
32+
&& let hir::ExprKind::Call(recv, args) = parent_expr.kind
33+
&& let hir::ExprKind::Path(ref qpath) = recv.kind
34+
&& let Some(fn_def_id) = cx.qpath_res(qpath, recv.hir_id).opt_def_id()
35+
&& let fn_sig = cx.tcx.fn_sig(fn_def_id).skip_binder().skip_binder()
36+
&& let Some(arg_pos) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
37+
&& let Some(ty) = fn_sig.inputs().get(arg_pos)
38+
&& !matches!(ty.kind(), ty::Param(_))
39+
{
40+
return false;
41+
}
42+
43+
// if it's neither of those, stay on the safe side and suggest turbofish,
44+
// even if it could work!
45+
true
46+
}
47+
48+
#[derive(Copy, Clone)]
49+
struct Replacement {
50+
method_name: &'static str,
51+
has_args: bool,
52+
has_generic_return: bool,
53+
}
54+
55+
fn check_fold_with_op(
1556
cx: &LateContext<'_>,
1657
expr: &hir::Expr<'_>,
17-
init: &hir::Expr<'_>,
1858
acc: &hir::Expr<'_>,
1959
fold_span: Span,
60+
op: hir::BinOpKind,
61+
replacement: Replacement,
2062
) {
21-
fn check_fold_with_op(
22-
cx: &LateContext<'_>,
23-
expr: &hir::Expr<'_>,
24-
acc: &hir::Expr<'_>,
25-
fold_span: Span,
26-
op: hir::BinOpKind,
27-
replacement_method_name: &str,
28-
replacement_has_args: bool,
29-
) {
30-
if_chain! {
31-
// Extract the body of the closure passed to fold
32-
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind;
33-
let closure_body = cx.tcx.hir().body(body);
34-
let closure_expr = peel_blocks(closure_body.value);
35-
36-
// Check if the closure body is of the form `acc <op> some_expr(x)`
37-
if let hir::ExprKind::Binary(ref bin_op, left_expr, right_expr) = closure_expr.kind;
38-
if bin_op.node == op;
39-
40-
// Extract the names of the two arguments to the closure
41-
if let [param_a, param_b] = closure_body.params;
42-
if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(param_a.pat).kind;
43-
if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(param_b.pat).kind;
44-
45-
if path_to_local_id(left_expr, first_arg_id);
46-
if replacement_has_args || path_to_local_id(right_expr, second_arg_id);
47-
48-
then {
49-
let mut applicability = Applicability::MachineApplicable;
50-
let sugg = if replacement_has_args {
51-
format!(
52-
"{replacement_method_name}(|{second_arg_ident}| {r})",
53-
r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
54-
)
55-
} else {
56-
format!(
57-
"{replacement_method_name}()",
58-
)
59-
};
60-
61-
span_lint_and_sugg(
62-
cx,
63-
UNNECESSARY_FOLD,
64-
fold_span.with_hi(expr.span.hi()),
65-
// TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
66-
"this `.fold` can be written more succinctly using another method",
67-
"try",
68-
sugg,
69-
applicability,
70-
);
71-
}
63+
if_chain! {
64+
// Extract the body of the closure passed to fold
65+
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind;
66+
let closure_body = cx.tcx.hir().body(body);
67+
let closure_expr = peel_blocks(closure_body.value);
68+
69+
// Check if the closure body is of the form `acc <op> some_expr(x)`
70+
if let hir::ExprKind::Binary(ref bin_op, left_expr, right_expr) = closure_expr.kind;
71+
if bin_op.node == op;
72+
73+
// Extract the names of the two arguments to the closure
74+
if let [param_a, param_b] = closure_body.params;
75+
if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(param_a.pat).kind;
76+
if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(param_b.pat).kind;
77+
78+
if path_to_local_id(left_expr, first_arg_id);
79+
if replacement.has_args || path_to_local_id(right_expr, second_arg_id);
80+
81+
then {
82+
let mut applicability = Applicability::MachineApplicable;
83+
84+
let turbofish = if replacement.has_generic_return {
85+
format!("::<{}>", cx.typeck_results().expr_ty_adjusted(right_expr).peel_refs())
86+
} else {
87+
String::new()
88+
};
89+
90+
let sugg = if replacement.has_args {
91+
format!(
92+
"{method}{turbofish}(|{second_arg_ident}| {r})",
93+
method = replacement.method_name,
94+
r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
95+
)
96+
} else {
97+
format!(
98+
"{method}{turbofish}()",
99+
method = replacement.method_name,
100+
)
101+
};
102+
103+
span_lint_and_sugg(
104+
cx,
105+
UNNECESSARY_FOLD,
106+
fold_span.with_hi(expr.span.hi()),
107+
// TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
108+
"this `.fold` can be written more succinctly using another method",
109+
"try",
110+
sugg,
111+
applicability,
112+
);
72113
}
73114
}
115+
}
74116

117+
pub(super) fn check(
118+
cx: &LateContext<'_>,
119+
expr: &hir::Expr<'_>,
120+
init: &hir::Expr<'_>,
121+
acc: &hir::Expr<'_>,
122+
fold_span: Span,
123+
) {
75124
// Check that this is a call to Iterator::fold rather than just some function called fold
76125
if !is_trait_method(cx, expr, sym::Iterator) {
77126
return;
@@ -80,11 +129,59 @@ pub(super) fn check(
80129
// Check if the first argument to .fold is a suitable literal
81130
if let hir::ExprKind::Lit(lit) = init.kind {
82131
match lit.node {
83-
ast::LitKind::Bool(false) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, "any", true),
84-
ast::LitKind::Bool(true) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, "all", true),
85-
ast::LitKind::Int(0, _) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, "sum", false),
132+
ast::LitKind::Bool(false) => {
133+
check_fold_with_op(
134+
cx,
135+
expr,
136+
acc,
137+
fold_span,
138+
hir::BinOpKind::Or,
139+
Replacement {
140+
has_args: true,
141+
has_generic_return: false,
142+
method_name: "any",
143+
},
144+
);
145+
},
146+
ast::LitKind::Bool(true) => {
147+
check_fold_with_op(
148+
cx,
149+
expr,
150+
acc,
151+
fold_span,
152+
hir::BinOpKind::And,
153+
Replacement {
154+
has_args: true,
155+
has_generic_return: false,
156+
method_name: "all",
157+
},
158+
);
159+
},
160+
ast::LitKind::Int(0, _) => check_fold_with_op(
161+
cx,
162+
expr,
163+
acc,
164+
fold_span,
165+
hir::BinOpKind::Add,
166+
Replacement {
167+
has_args: false,
168+
has_generic_return: needs_turbofish(cx, expr),
169+
method_name: "sum",
170+
},
171+
),
86172
ast::LitKind::Int(1, _) => {
87-
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, "product", false);
173+
check_fold_with_op(
174+
cx,
175+
expr,
176+
acc,
177+
fold_span,
178+
hir::BinOpKind::Mul,
179+
Replacement {
180+
has_args: false,
181+
has_generic_return: needs_turbofish(cx, expr),
182+
method_name: "product",
183+
},
184+
);
88185
},
89186
_ => (),
90187
}

tests/ui/unnecessary_fold.fixed

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,28 @@ fn unnecessary_fold_over_multiple_lines() {
4949
.any(|x| x > 2);
5050
}
5151

52+
fn issue10000() {
53+
use std::collections::HashMap;
54+
use std::hash::BuildHasher;
55+
56+
fn anything<T>(_: T) {}
57+
fn num(_: i32) {}
58+
fn smoketest_map<S: BuildHasher>(mut map: HashMap<i32, i32, S>) {
59+
map.insert(0, 0);
60+
assert_eq!(map.values().sum::<i32>(), 0);
61+
62+
// more cases:
63+
let _ = map.values().sum::<i32>();
64+
let _ = map.values().product::<i32>();
65+
let _: i32 = map.values().sum();
66+
let _: i32 = map.values().product();
67+
anything(map.values().sum::<i32>());
68+
anything(map.values().product::<i32>());
69+
num(map.values().sum());
70+
num(map.values().product());
71+
}
72+
73+
smoketest_map(HashMap::new());
74+
}
75+
5276
fn main() {}

tests/ui/unnecessary_fold.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,28 @@ fn unnecessary_fold_over_multiple_lines() {
4949
.fold(false, |acc, x| acc || x > 2);
5050
}
5151

52+
fn issue10000() {
53+
use std::collections::HashMap;
54+
use std::hash::BuildHasher;
55+
56+
fn anything<T>(_: T) {}
57+
fn num(_: i32) {}
58+
fn smoketest_map<S: BuildHasher>(mut map: HashMap<i32, i32, S>) {
59+
map.insert(0, 0);
60+
assert_eq!(map.values().fold(0, |x, y| x + y), 0);
61+
62+
// more cases:
63+
let _ = map.values().fold(0, |x, y| x + y);
64+
let _ = map.values().fold(1, |x, y| x * y);
65+
let _: i32 = map.values().fold(0, |x, y| x + y);
66+
let _: i32 = map.values().fold(1, |x, y| x * y);
67+
anything(map.values().fold(0, |x, y| x + y));
68+
anything(map.values().fold(1, |x, y| x * y));
69+
num(map.values().fold(0, |x, y| x + y));
70+
num(map.values().fold(1, |x, y| x * y));
71+
}
72+
73+
smoketest_map(HashMap::new());
74+
}
75+
5276
fn main() {}

tests/ui/unnecessary_fold.stderr

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,59 @@ error: this `.fold` can be written more succinctly using another method
3636
LL | .fold(false, |acc, x| acc || x > 2);
3737
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)`
3838

39-
error: aborting due to 6 previous errors
39+
error: this `.fold` can be written more succinctly using another method
40+
--> $DIR/unnecessary_fold.rs:60:33
41+
|
42+
LL | assert_eq!(map.values().fold(0, |x, y| x + y), 0);
43+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum::<i32>()`
44+
45+
error: this `.fold` can be written more succinctly using another method
46+
--> $DIR/unnecessary_fold.rs:63:30
47+
|
48+
LL | let _ = map.values().fold(0, |x, y| x + y);
49+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum::<i32>()`
50+
51+
error: this `.fold` can be written more succinctly using another method
52+
--> $DIR/unnecessary_fold.rs:64:30
53+
|
54+
LL | let _ = map.values().fold(1, |x, y| x * y);
55+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `product::<i32>()`
56+
57+
error: this `.fold` can be written more succinctly using another method
58+
--> $DIR/unnecessary_fold.rs:65:35
59+
|
60+
LL | let _: i32 = map.values().fold(0, |x, y| x + y);
61+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum()`
62+
63+
error: this `.fold` can be written more succinctly using another method
64+
--> $DIR/unnecessary_fold.rs:66:35
65+
|
66+
LL | let _: i32 = map.values().fold(1, |x, y| x * y);
67+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `product()`
68+
69+
error: this `.fold` can be written more succinctly using another method
70+
--> $DIR/unnecessary_fold.rs:67:31
71+
|
72+
LL | anything(map.values().fold(0, |x, y| x + y));
73+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum::<i32>()`
74+
75+
error: this `.fold` can be written more succinctly using another method
76+
--> $DIR/unnecessary_fold.rs:68:31
77+
|
78+
LL | anything(map.values().fold(1, |x, y| x * y));
79+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `product::<i32>()`
80+
81+
error: this `.fold` can be written more succinctly using another method
82+
--> $DIR/unnecessary_fold.rs:69:26
83+
|
84+
LL | num(map.values().fold(0, |x, y| x + y));
85+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum()`
86+
87+
error: this `.fold` can be written more succinctly using another method
88+
--> $DIR/unnecessary_fold.rs:70:26
89+
|
90+
LL | num(map.values().fold(1, |x, y| x * y));
91+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `product()`
92+
93+
error: aborting due to 15 previous errors
4094

0 commit comments

Comments
 (0)