Skip to content

Commit 354172a

Browse files
committed
New lint manual_try_fold
1 parent c7bf05c commit 354172a

File tree

7 files changed

+195
-2
lines changed

7 files changed

+195
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4959,6 +4959,7 @@ Released 2018-09-13
49594959
[`manual_string_new`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new
49604960
[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
49614961
[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
4962+
[`manual_try_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold
49624963
[`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or
49634964
[`manual_while_let_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_while_let_some
49644965
[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
364364
crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO,
365365
crate::methods::MANUAL_SPLIT_ONCE_INFO,
366366
crate::methods::MANUAL_STR_REPEAT_INFO,
367+
crate::methods::MANUAL_TRY_FOLD_INFO,
367368
crate::methods::MAP_CLONE_INFO,
368369
crate::methods::MAP_COLLECT_RESULT_UNIT_INFO,
369370
crate::methods::MAP_ERR_IGNORE_INFO,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use clippy_utils::{
2+
diagnostics::span_lint_and_sugg,
3+
is_from_proc_macro,
4+
msrvs::{Msrv, ITERATOR_TRY_FOLD},
5+
source::snippet_opt,
6+
ty::implements_trait,
7+
};
8+
use rustc_errors::Applicability;
9+
use rustc_hir::{
10+
def::{DefKind, Res},
11+
Expr, ExprKind,
12+
};
13+
use rustc_lint::{LateContext, LintContext};
14+
use rustc_middle::lint::in_external_macro;
15+
use rustc_span::Span;
16+
17+
use super::MANUAL_TRY_FOLD;
18+
19+
pub(super) fn check<'tcx>(
20+
cx: &LateContext<'tcx>,
21+
expr: &Expr<'tcx>,
22+
init: &Expr<'_>,
23+
acc: &Expr<'_>,
24+
fold_span: Span,
25+
msrv: &Msrv,
26+
) {
27+
if !in_external_macro(cx.sess(), fold_span)
28+
&& msrv.meets(ITERATOR_TRY_FOLD)
29+
&& let init_ty = cx.typeck_results().expr_ty(init)
30+
&& let Some(try_trait) = cx.tcx.lang_items().try_trait()
31+
&& implements_trait(cx, init_ty, try_trait, &[])
32+
&& let ExprKind::Call(path, [first, rest @ ..]) = init.kind
33+
&& let ExprKind::Path(qpath) = path.kind
34+
&& let Res::Def(DefKind::Ctor(_, _), _) = cx.qpath_res(&qpath, path.hir_id)
35+
&& let ExprKind::Closure(closure) = acc.kind
36+
&& let Some(args_snip) = closure.fn_arg_span.and_then(|fn_arg_span| snippet_opt(cx, fn_arg_span))
37+
&& !is_from_proc_macro(cx, expr)
38+
{
39+
let init_snip = rest
40+
.is_empty()
41+
.then_some(first.span)
42+
.and_then(|span| snippet_opt(cx, span))
43+
.unwrap_or("...".to_owned());
44+
45+
span_lint_and_sugg(
46+
cx,
47+
MANUAL_TRY_FOLD,
48+
fold_span,
49+
"you seem to be using `Iterator::fold` on a type that implements `Try`",
50+
"use `try_fold` instead",
51+
format!("try_fold({init_snip}, {args_snip}, ...)", ),
52+
Applicability::HasPlaceholders,
53+
);
54+
}
55+
}

clippy_lints/src/methods/mod.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ mod manual_next_back;
5050
mod manual_ok_or;
5151
mod manual_saturating_arithmetic;
5252
mod manual_str_repeat;
53+
mod manual_try_fold;
5354
mod map_clone;
5455
mod map_collect_result_unit;
5556
mod map_err_ignore;
@@ -3286,6 +3287,30 @@ declare_clippy_lint! {
32863287
"calling `.drain(..).collect()` to move all elements into a new collection"
32873288
}
32883289

3290+
declare_clippy_lint! {
3291+
/// ### What it does
3292+
/// Checks for usage of `Iterator::fold` with a type that implements `Try`.
3293+
///
3294+
/// ### Why is this bad?
3295+
/// This is better represented with `try_fold`, but this has one major difference: It will
3296+
/// short-circuit on failure. *This is almost always what you want*. This can also open the door
3297+
/// for additional optimizations as well, as rustc can guarantee the function is never
3298+
/// called on `None`, `Err`, etc., alleviating otherwise necessary checks.
3299+
///
3300+
/// ### Example
3301+
/// ```rust
3302+
/// vec![1, 2, 3].iter().fold(Some(0i32), |sum, i| sum?.checked_add(*i));
3303+
/// ```
3304+
/// Use instead:
3305+
/// ```rust
3306+
/// vec![1, 2, 3].iter().try_fold(0i32, |sum, i| sum.checked_add(*i));
3307+
/// ```
3308+
#[clippy::version = "1.72.0"]
3309+
pub MANUAL_TRY_FOLD,
3310+
perf,
3311+
"checks for usage of `Iterator::fold` with a type that implements `Try`"
3312+
}
3313+
32893314
pub struct Methods {
32903315
avoid_breaking_exported_api: bool,
32913316
msrv: Msrv,
@@ -3416,7 +3441,8 @@ impl_lint_pass!(Methods => [
34163441
CLEAR_WITH_DRAIN,
34173442
MANUAL_NEXT_BACK,
34183443
UNNECESSARY_LITERAL_UNWRAP,
3419-
DRAIN_COLLECT
3444+
DRAIN_COLLECT,
3445+
MANUAL_TRY_FOLD,
34203446
]);
34213447

34223448
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3709,7 +3735,10 @@ impl Methods {
37093735
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, true),
37103736
_ => {},
37113737
},
3712-
("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span),
3738+
("fold", [init, acc]) => {
3739+
manual_try_fold::check(cx, expr, init, acc, call_span, &self.msrv);
3740+
unnecessary_fold::check(cx, expr, init, acc, span);
3741+
},
37133742
("for_each", [_]) => {
37143743
if let Some(("inspect", _, [_], span2, _)) = method_call(recv) {
37153744
inspect_for_each::check(cx, expr, span2);

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ msrv_aliases! {
4444
1,34,0 { TRY_FROM }
4545
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
4646
1,28,0 { FROM_BOOL }
47+
1,27,0 { ITERATOR_TRY_FOLD }
4748
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
4849
1,24,0 { IS_ASCII_DIGIT }
4950
1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }

tests/ui/manual_try_fold.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//@aux-build:proc_macros.rs
2+
#![allow(clippy::unnecessary_fold, unused)]
3+
#![warn(clippy::manual_try_fold)]
4+
#![feature(try_trait_v2)]
5+
6+
use std::ops::ControlFlow;
7+
use std::ops::FromResidual;
8+
use std::ops::Try;
9+
10+
#[macro_use]
11+
extern crate proc_macros;
12+
13+
// Test custom `Try` with more than 1 argument
14+
struct NotOption(i32, i32);
15+
16+
impl<R> FromResidual<R> for NotOption {
17+
fn from_residual(_: R) -> Self {
18+
todo!()
19+
}
20+
}
21+
22+
impl Try for NotOption {
23+
type Output = ();
24+
type Residual = ();
25+
26+
fn from_output(_: Self::Output) -> Self {
27+
todo!()
28+
}
29+
30+
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
31+
todo!()
32+
}
33+
}
34+
35+
// Test custom `Try` with only 1 argument
36+
#[derive(Default)]
37+
struct NotOptionButWorse(i32);
38+
39+
impl<R> FromResidual<R> for NotOptionButWorse {
40+
fn from_residual(_: R) -> Self {
41+
todo!()
42+
}
43+
}
44+
45+
impl Try for NotOptionButWorse {
46+
type Output = ();
47+
type Residual = ();
48+
49+
fn from_output(_: Self::Output) -> Self {
50+
todo!()
51+
}
52+
53+
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
54+
todo!()
55+
}
56+
}
57+
58+
fn main() {
59+
[1, 2, 3]
60+
.iter()
61+
.fold(Some(0i32), |sum, i| sum?.checked_add(*i))
62+
.unwrap();
63+
[1, 2, 3]
64+
.iter()
65+
.fold(NotOption(0i32, 0i32), |sum, i| NotOption(0i32, 0i32));
66+
[1, 2, 3]
67+
.iter()
68+
.fold(NotOptionButWorse(0i32), |sum, i| NotOptionButWorse(0i32));
69+
// Do not lint
70+
[1, 2, 3].iter().try_fold(0i32, |sum, i| sum.checked_add(*i)).unwrap();
71+
[1, 2, 3].iter().fold(0i32, |sum, i| sum + i);
72+
[1, 2, 3]
73+
.iter()
74+
.fold(NotOptionButWorse::default(), |sum, i| NotOptionButWorse::default());
75+
external! {
76+
[1, 2, 3].iter().fold(Some(0i32), |sum, i| sum?.checked_add(*i)).unwrap();
77+
[1, 2, 3].iter().try_fold(0i32, |sum, i| sum.checked_add(*i)).unwrap();
78+
}
79+
with_span! {
80+
span
81+
[1, 2, 3].iter().fold(Some(0i32), |sum, i| sum?.checked_add(*i)).unwrap();
82+
[1, 2, 3].iter().try_fold(0i32, |sum, i| sum.checked_add(*i)).unwrap();
83+
}
84+
}

tests/ui/manual_try_fold.stderr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: you seem to be using `Iterator::fold` on a type that implements `Try`
2+
--> $DIR/manual_try_fold.rs:61:10
3+
|
4+
LL | .fold(Some(0i32), |sum, i| sum?.checked_add(*i))
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `try_fold` instead: `try_fold(0i32, |sum, i|, ...)`
6+
|
7+
= note: `-D clippy::manual-try-fold` implied by `-D warnings`
8+
9+
error: you seem to be using `Iterator::fold` on a type that implements `Try`
10+
--> $DIR/manual_try_fold.rs:65:10
11+
|
12+
LL | .fold(NotOption(0i32, 0i32), |sum, i| NotOption(0i32, 0i32));
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `try_fold` instead: `try_fold(..., |sum, i|, ...)`
14+
15+
error: you seem to be using `Iterator::fold` on a type that implements `Try`
16+
--> $DIR/manual_try_fold.rs:68:10
17+
|
18+
LL | .fold(NotOptionButWorse(0i32), |sum, i| NotOptionButWorse(0i32));
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `try_fold` instead: `try_fold(0i32, |sum, i|, ...)`
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)