1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::ty::is_type_diagnostic_item;
3 use clippy_utils::visitors::contains_unsafe_block;
4 use clippy_utils::{is_res_lang_ctor, path_res, path_to_local_id};
6 use rustc_hir::LangItem::OptionSome;
7 use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind};
8 use rustc_lint::LateContext;
9 use rustc_span::{sym, SyntaxContext};
11 use super::manual_utils::{check_with, SomeExpr};
12 use super::MANUAL_FILTER;
14 // Function called on the <expr> of `[&+]Some((ref | ref mut) x) => <expr>`
15 // Need to check if it's of the form `<expr>=if <cond> {<then_expr>} else {<else_expr>}`
16 // AND that only one `then/else_expr` resolves to `Some(x)` while the other resolves to `None`
17 // return the `cond` expression if so.
18 fn get_cond_expr<'tcx>(
19 cx: &LateContext<'tcx>,
23 ) -> Option<SomeExpr<'tcx>> {
25 if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr);
26 if let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind;
27 if let PatKind::Binding(_,target, ..) = pat.kind;
28 if let (then_visitor, else_visitor)
29 = (is_some_expr(cx, target, ctxt, then_expr),
30 is_some_expr(cx, target, ctxt, else_expr));
31 if then_visitor != else_visitor; // check that one expr resolves to `Some(x)`, the other to `None`
33 return Some(SomeExpr {
34 expr: peels_blocks_incl_unsafe(cond.peel_drop_temps()),
35 needs_unsafe_block: contains_unsafe_block(cx, expr),
36 needs_negated: !then_visitor // if the `then_expr` resolves to `None`, need to negate the cond
43 fn peels_blocks_incl_unsafe_opt<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
44 // we don't want to use `peel_blocks` here because we don't care if the block is unsafe, it's
45 // checked by `contains_unsafe_block`
46 if let ExprKind::Block(block, None) = expr.kind {
47 if block.stmts.is_empty() {
54 fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> {
55 peels_blocks_incl_unsafe_opt(expr).unwrap_or(expr)
58 // function called for each <expr> expression:
59 // Some(x) => if <cond> {
64 // Returns true if <expr> resolves to `Some(x)`, `false` otherwise
65 fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &Expr<'_>) -> bool {
66 if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) {
67 // there can be not statements in the block as they would be removed when switching to `.filter`
68 if let ExprKind::Call(callee, [arg]) = inner_expr.kind {
69 return ctxt == expr.span.ctxt()
70 && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome)
71 && path_to_local_id(arg, target);
77 // given the closure: `|<pattern>| <expr>`
78 // returns `|&<pattern>| <expr>`
79 fn add_ampersand_if_copy(body_str: String, has_copy_trait: bool) -> String {
81 let mut with_ampersand = body_str;
82 with_ampersand.insert(1, '&');
89 pub(super) fn check_match<'tcx>(
90 cx: &LateContext<'tcx>,
91 scrutinee: &'tcx Expr<'_>,
92 arms: &'tcx [Arm<'_>],
95 let ty = cx.typeck_results().expr_ty(expr);
96 if is_type_diagnostic_item(cx, ty, sym::Option)
97 && let [first_arm, second_arm] = arms
98 && first_arm.guard.is_none()
99 && second_arm.guard.is_none()
101 check(cx, expr, scrutinee, first_arm.pat, first_arm.body, Some(second_arm.pat), second_arm.body);
105 pub(super) fn check_if_let<'tcx>(
106 cx: &LateContext<'tcx>,
107 expr: &'tcx Expr<'_>,
108 let_pat: &'tcx Pat<'_>,
109 let_expr: &'tcx Expr<'_>,
110 then_expr: &'tcx Expr<'_>,
111 else_expr: &'tcx Expr<'_>,
113 check(cx, expr, let_expr, let_pat, then_expr, None, else_expr);
117 cx: &LateContext<'tcx>,
118 expr: &'tcx Expr<'_>,
119 scrutinee: &'tcx Expr<'_>,
120 then_pat: &'tcx Pat<'_>,
121 then_body: &'tcx Expr<'_>,
122 else_pat: Option<&'tcx Pat<'_>>,
123 else_body: &'tcx Expr<'_>,
125 if let Some(sugg_info) = check_with(
135 let body_str = add_ampersand_if_copy(sugg_info.body_str, sugg_info.scrutinee_impl_copy);
140 "manual implementation of `Option::filter`",
142 if sugg_info.needs_brackets {
144 "{{ {}{}.filter({body_str}) }}",
145 sugg_info.scrutinee_str, sugg_info.as_ref_str
148 format!("{}{}.filter({body_str})", sugg_info.scrutinee_str, sugg_info.as_ref_str)