Skip to content

Commit 85cf046

Browse files
committed
Add manual_filter lint for Option
Share much of its implementation with `manual_map` and should greatly benefit from its previous feedback.
1 parent 5652ccb commit 85cf046

13 files changed

+971
-69
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3833,6 +3833,7 @@ Released 2018-09-13
38333833
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
38343834
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
38353835
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
3836+
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
38363837
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
38373838
[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find
38383839
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
134134
LintId::of(match_result_ok::MATCH_RESULT_OK),
135135
LintId::of(matches::COLLAPSIBLE_MATCH),
136136
LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
137+
LintId::of(matches::MANUAL_FILTER),
137138
LintId::of(matches::MANUAL_MAP),
138139
LintId::of(matches::MANUAL_UNWRAP_OR),
139140
LintId::of(matches::MATCH_AS_REF),

clippy_lints/src/lib.register_complexity.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
2626
LintId::of(manual_strip::MANUAL_STRIP),
2727
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
2828
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
29+
LintId::of(matches::MANUAL_FILTER),
2930
LintId::of(matches::MANUAL_UNWRAP_OR),
3031
LintId::of(matches::MATCH_AS_REF),
3132
LintId::of(matches::MATCH_SINGLE_BINDING),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ store.register_lints(&[
254254
match_result_ok::MATCH_RESULT_OK,
255255
matches::COLLAPSIBLE_MATCH,
256256
matches::INFALLIBLE_DESTRUCTURING_MATCH,
257+
matches::MANUAL_FILTER,
257258
matches::MANUAL_MAP,
258259
matches::MANUAL_UNWRAP_OR,
259260
matches::MATCH_AS_REF,
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::ty::is_type_diagnostic_item;
3+
use clippy_utils::{is_lang_ctor, path_to_local_id};
4+
use rustc_hir::intravisit::{walk_expr, Visitor};
5+
use rustc_hir::LangItem::OptionSome;
6+
use rustc_hir::{Arm, Block, BlockCheckMode, Expr, ExprKind, HirId, Pat, PatKind, UnsafeSource};
7+
use rustc_lint::LateContext;
8+
use rustc_span::{sym, SyntaxContext};
9+
10+
use super::manual_map::{check_with, SomeExpr};
11+
use super::MANUAL_FILTER;
12+
13+
#[derive(Default)]
14+
struct NeedsUnsafeBlock(pub bool);
15+
16+
impl<'tcx> Visitor<'tcx> for NeedsUnsafeBlock {
17+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
18+
match expr.kind {
19+
ExprKind::Block(
20+
Block {
21+
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
22+
..
23+
},
24+
_,
25+
) => {
26+
self.0 = true;
27+
},
28+
_ => walk_expr(self, expr),
29+
}
30+
}
31+
}
32+
33+
// Function called on the `expr` of `[&+]Some((ref | ref mut) x) => <expr>`
34+
// Need to check if it's of the `if <cond> {<then_expr>} else {<else_expr>}`
35+
// AND that only one `then/else_expr` resolves to `Some(x)` while the other resolves to `None`
36+
// return `cond` if
37+
fn get_cond_expr<'tcx>(
38+
cx: &LateContext<'tcx>,
39+
pat: &Pat<'_>,
40+
expr: &'tcx Expr<'_>,
41+
ctxt: SyntaxContext,
42+
) -> Option<SomeExpr<'tcx>> {
43+
if_chain! {
44+
if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr);
45+
if let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind;
46+
if let PatKind::Binding(_,target, ..) = pat.kind;
47+
if let (then_visitor, else_visitor)
48+
= (handle_if_or_else_expr(cx, target, ctxt, then_expr),
49+
handle_if_or_else_expr(cx, target, ctxt, else_expr));
50+
if then_visitor != else_visitor; // check that one expr resolves to `Some(x)`, the other to `None`
51+
then {
52+
let mut needs_unsafe_block = NeedsUnsafeBlock::default();
53+
needs_unsafe_block.visit_expr(expr);
54+
return Some(SomeExpr {
55+
expr: peels_blocks_incl_unsafe(cond.peel_drop_temps()),
56+
needs_unsafe_block: needs_unsafe_block.0,
57+
needs_negated: !then_visitor // if the `then_expr` resolves to `None`, need to negate the cond
58+
})
59+
}
60+
};
61+
None
62+
}
63+
64+
fn peels_blocks_incl_unsafe_opt<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
65+
// we don't want to use `peel_blocks` here because we don't care if the block is unsafe, it's
66+
// checked by `NeedsUnsafeBlock`
67+
if let ExprKind::Block(block, None) = expr.kind {
68+
if block.stmts.is_empty() {
69+
return block.expr;
70+
}
71+
};
72+
None
73+
}
74+
75+
fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> {
76+
peels_blocks_incl_unsafe_opt(expr).unwrap_or(expr)
77+
}
78+
79+
// function called for each <ifelse> expression:
80+
// Some(x) => if <cond> {
81+
// <ifelse>
82+
// } else {
83+
// <ifelse>
84+
// }
85+
// Returns true if <ifelse> resolves to `Some(x)`, `false` otherwise
86+
fn handle_if_or_else_expr<'tcx>(
87+
cx: &LateContext<'_>,
88+
target: HirId,
89+
ctxt: SyntaxContext,
90+
if_or_else_expr: &'tcx Expr<'_>,
91+
) -> bool {
92+
if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(if_or_else_expr) {
93+
// there can be not statements in the block as they would be removed when switching to `.filter`
94+
if let ExprKind::Call(
95+
Expr {
96+
kind: ExprKind::Path(ref qpath),
97+
..
98+
},
99+
[arg],
100+
) = inner_expr.kind
101+
{
102+
return ctxt == if_or_else_expr.span.ctxt()
103+
&& is_lang_ctor(cx, qpath, OptionSome)
104+
&& path_to_local_id(arg, target);
105+
}
106+
};
107+
false
108+
}
109+
110+
// given the closure: `|<pattern>| <expr>`
111+
// returns `|&<pattern>| <expr>`
112+
fn add_ampersand_if_copy(body_str: String, has_copy_trait: bool) -> String {
113+
if has_copy_trait {
114+
let mut with_ampersand = body_str;
115+
with_ampersand.insert(1, '&');
116+
with_ampersand
117+
} else {
118+
body_str
119+
}
120+
}
121+
122+
pub(super) fn check_match<'tcx>(
123+
cx: &LateContext<'tcx>,
124+
scrutinee: &'tcx Expr<'_>,
125+
arms: &'tcx [Arm<'_>],
126+
expr: &'tcx Expr<'_>,
127+
) {
128+
let ty = cx.typeck_results().expr_ty(expr);
129+
if_chain! {
130+
if is_type_diagnostic_item(cx, ty, sym::Option);
131+
if arms.len() == 2;
132+
if arms[0].guard.is_none();
133+
if arms[1].guard.is_none();
134+
then {
135+
check(cx, expr, scrutinee, arms[0].pat, arms[0].body, Some(arms[1].pat), arms[1].body)
136+
}
137+
}
138+
}
139+
140+
pub(super) fn check_if_let<'tcx>(
141+
cx: &LateContext<'tcx>,
142+
expr: &'tcx Expr<'_>,
143+
let_pat: &'tcx Pat<'_>,
144+
let_expr: &'tcx Expr<'_>,
145+
then_expr: &'tcx Expr<'_>,
146+
else_expr: &'tcx Expr<'_>,
147+
) {
148+
check(cx, expr, let_expr, let_pat, then_expr, None, else_expr);
149+
}
150+
151+
fn check<'tcx>(
152+
cx: &LateContext<'tcx>,
153+
expr: &'tcx Expr<'_>,
154+
scrutinee: &'tcx Expr<'_>,
155+
then_pat: &'tcx Pat<'_>,
156+
then_body: &'tcx Expr<'_>,
157+
else_pat: Option<&'tcx Pat<'_>>,
158+
else_body: &'tcx Expr<'_>,
159+
) {
160+
if let Some(sugg_info) = check_with(
161+
cx,
162+
expr,
163+
scrutinee,
164+
then_pat,
165+
then_body,
166+
else_pat,
167+
else_body,
168+
get_cond_expr,
169+
) {
170+
let body_str = add_ampersand_if_copy(sugg_info.body_str, sugg_info.scrutinee_impl_copy);
171+
span_lint_and_sugg(
172+
cx,
173+
MANUAL_FILTER,
174+
expr.span,
175+
"manual implementation of `Option::filter`",
176+
"try this",
177+
if sugg_info.needs_brackets {
178+
format!(
179+
"{{ {}{}.filter({}) }}",
180+
sugg_info.scrutinee_str, sugg_info.as_ref_str, body_str
181+
)
182+
} else {
183+
format!(
184+
"{}{}.filter({})",
185+
sugg_info.scrutinee_str, sugg_info.as_ref_str, body_str
186+
)
187+
},
188+
sugg_info.app,
189+
);
190+
}
191+
}

0 commit comments

Comments
 (0)