Skip to content

Commit 86eaeaa

Browse files
authored
Do not make suggestion machine-applicable if it may change semantics (rust-lang#16324)
When suggesting to replace an iterator method by `.all()` or `.any()`, make the suggestion at most `MaybeIncorrect` instead of `MachineApplicable` and warn about the fact that semantics may change because those two methods are short-circuiting. Fixes rust-lang#3351 ---- changelog: [`unnecessary_fold`]: warn about possible semantics change when suggesting the use of a short-circuiting operator
2 parents 343cde9 + a390881 commit 86eaeaa

File tree

2 files changed

+77
-38
lines changed

2 files changed

+77
-38
lines changed

clippy_lints/src/methods/unnecessary_fold.rs

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use clippy_utils::diagnostics::span_lint_and_sugg;
1+
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes};
33
use clippy_utils::source::snippet_with_applicability;
44
use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_blocks, strip_pat_refs};
55
use rustc_ast::ast;
66
use rustc_data_structures::packed::Pu128;
7-
use rustc_errors::Applicability;
7+
use rustc_errors::{Applicability, Diag};
88
use rustc_hir as hir;
99
use rustc_hir::PatKind;
1010
use rustc_hir::def::{DefKind, Res};
@@ -59,6 +59,34 @@ struct Replacement {
5959
method_name: &'static str,
6060
has_args: bool,
6161
has_generic_return: bool,
62+
is_short_circuiting: bool,
63+
}
64+
65+
impl Replacement {
66+
fn default_applicability(&self) -> Applicability {
67+
if self.is_short_circuiting {
68+
Applicability::MaybeIncorrect
69+
} else {
70+
Applicability::MachineApplicable
71+
}
72+
}
73+
74+
fn maybe_add_note(&self, diag: &mut Diag<'_, ()>) {
75+
if self.is_short_circuiting {
76+
diag.note(format!(
77+
"the `{}` method is short circuiting and may change the program semantics if the iterator has side effects",
78+
self.method_name
79+
));
80+
}
81+
}
82+
83+
fn maybe_turbofish(&self, ty: Ty<'_>) -> String {
84+
if self.has_generic_return {
85+
format!("::<{ty}>")
86+
} else {
87+
String::new()
88+
}
89+
}
6290
}
6391

6492
fn check_fold_with_op(
@@ -86,32 +114,29 @@ fn check_fold_with_op(
86114
&& left_expr.res_local_id() == Some(first_arg_id)
87115
&& (replacement.has_args || right_expr.res_local_id() == Some(second_arg_id))
88116
{
89-
let mut applicability = Applicability::MachineApplicable;
90-
91-
let turbofish = if replacement.has_generic_return {
92-
format!("::<{}>", cx.typeck_results().expr_ty_adjusted(right_expr).peel_refs())
93-
} else {
94-
String::new()
95-
};
96-
97-
let sugg = if replacement.has_args {
98-
format!(
99-
"{method}{turbofish}(|{second_arg_ident}| {r})",
100-
method = replacement.method_name,
101-
r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
102-
)
103-
} else {
104-
format!("{method}{turbofish}()", method = replacement.method_name)
105-
};
106-
107-
span_lint_and_sugg(
117+
let span = fold_span.with_hi(expr.span.hi());
118+
span_lint_and_then(
108119
cx,
109120
UNNECESSARY_FOLD,
110-
fold_span.with_hi(expr.span.hi()),
121+
span,
111122
"this `.fold` can be written more succinctly using another method",
112-
"try",
113-
sugg,
114-
applicability,
123+
|diag| {
124+
let mut applicability = replacement.default_applicability();
125+
let turbofish =
126+
replacement.maybe_turbofish(cx.typeck_results().expr_ty_adjusted(right_expr).peel_refs());
127+
let sugg = if replacement.has_args {
128+
format!(
129+
"{method}{turbofish}(|{second_arg_ident}| {r})",
130+
method = replacement.method_name,
131+
r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
132+
)
133+
} else {
134+
format!("{method}{turbofish}()", method = replacement.method_name)
135+
};
136+
137+
diag.span_suggestion(span, "try", sugg, applicability);
138+
replacement.maybe_add_note(diag);
139+
},
115140
);
116141
return true;
117142
}
@@ -131,22 +156,25 @@ fn check_fold_with_method(
131156
// Check if the function belongs to the operator
132157
&& cx.tcx.is_diagnostic_item(method, fn_did)
133158
{
134-
let applicability = Applicability::MachineApplicable;
135-
136-
let turbofish = if replacement.has_generic_return {
137-
format!("::<{}>", cx.typeck_results().expr_ty(expr))
138-
} else {
139-
String::new()
140-
};
141-
142-
span_lint_and_sugg(
159+
let span = fold_span.with_hi(expr.span.hi());
160+
span_lint_and_then(
143161
cx,
144162
UNNECESSARY_FOLD,
145-
fold_span.with_hi(expr.span.hi()),
163+
span,
146164
"this `.fold` can be written more succinctly using another method",
147-
"try",
148-
format!("{method}{turbofish}()", method = replacement.method_name),
149-
applicability,
165+
|diag| {
166+
diag.span_suggestion(
167+
span,
168+
"try",
169+
format!(
170+
"{method}{turbofish}()",
171+
method = replacement.method_name,
172+
turbofish = replacement.maybe_turbofish(cx.typeck_results().expr_ty(expr))
173+
),
174+
replacement.default_applicability(),
175+
);
176+
replacement.maybe_add_note(diag);
177+
},
150178
);
151179
}
152180
}
@@ -171,6 +199,7 @@ pub(super) fn check<'tcx>(
171199
method_name: "any",
172200
has_args: true,
173201
has_generic_return: false,
202+
is_short_circuiting: true,
174203
};
175204
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, replacement);
176205
},
@@ -179,6 +208,7 @@ pub(super) fn check<'tcx>(
179208
method_name: "all",
180209
has_args: true,
181210
has_generic_return: false,
211+
is_short_circuiting: true,
182212
};
183213
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, replacement);
184214
},
@@ -187,6 +217,7 @@ pub(super) fn check<'tcx>(
187217
method_name: "sum",
188218
has_args: false,
189219
has_generic_return: needs_turbofish(cx, expr),
220+
is_short_circuiting: false,
190221
};
191222
if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, replacement) {
192223
check_fold_with_method(cx, expr, acc, fold_span, sym::add, replacement);
@@ -197,6 +228,7 @@ pub(super) fn check<'tcx>(
197228
method_name: "product",
198229
has_args: false,
199230
has_generic_return: needs_turbofish(cx, expr),
231+
is_short_circuiting: false,
200232
};
201233
if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, replacement) {
202234
check_fold_with_method(cx, expr, acc, fold_span, sym::mul, replacement);

tests/ui/unnecessary_fold.stderr

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ error: this `.fold` can be written more succinctly using another method
44
LL | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)`
66
|
7+
= note: the `any` method is short circuiting and may change the program semantics if the iterator has side effects
78
= note: `-D clippy::unnecessary-fold` implied by `-D warnings`
89
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_fold)]`
910

@@ -21,6 +22,8 @@ error: this `.fold` can be written more succinctly using another method
2122
|
2223
LL | let _ = (0..3).fold(true, |acc, x| acc && x > 2);
2324
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `all(|x| x > 2)`
25+
|
26+
= note: the `all` method is short circuiting and may change the program semantics if the iterator has side effects
2427

2528
error: this `.fold` can be written more succinctly using another method
2629
--> tests/ui/unnecessary_fold.rs:24:25
@@ -63,12 +66,16 @@ error: this `.fold` can be written more succinctly using another method
6366
|
6467
LL | let _: bool = (0..3).map(|x| 2 * x).fold(false, |acc, x| acc || x > 2);
6568
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)`
69+
|
70+
= note: the `any` method is short circuiting and may change the program semantics if the iterator has side effects
6671

6772
error: this `.fold` can be written more succinctly using another method
6873
--> tests/ui/unnecessary_fold.rs:110:10
6974
|
7075
LL | .fold(false, |acc, x| acc || x > 2);
7176
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)`
77+
|
78+
= note: the `any` method is short circuiting and may change the program semantics if the iterator has side effects
7279

7380
error: this `.fold` can be written more succinctly using another method
7481
--> tests/ui/unnecessary_fold.rs:123:33

0 commit comments

Comments
 (0)