]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/manual_ok_or.rs
Rollup merge of #82215 - TaKO8Ki:replace-if-let-while-let, r=varkor
[rust.git] / clippy_lints / src / manual_ok_or.rs
1 use crate::utils::{
2     indent_of, is_type_diagnostic_item, match_qpath, path_to_local_id, paths, reindent_multiline, snippet_opt,
3     span_lint_and_sugg,
4 };
5 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir::{Expr, ExprKind, PatKind};
8 use rustc_lint::LintContext;
9 use rustc_lint::{LateContext, LateLintPass};
10 use rustc_middle::lint::in_external_macro;
11 use rustc_session::{declare_lint_pass, declare_tool_lint};
12 use rustc_span::symbol::sym;
13
14 declare_clippy_lint! {
15     /// **What it does:**
16     /// Finds patterns that reimplement `Option::ok_or`.
17     ///
18     /// **Why is this bad?**
19     /// Concise code helps focusing on behavior instead of boilerplate.
20     ///
21     /// **Known problems:** None.
22     ///
23     /// **Examples:**
24     /// ```rust
25     /// let foo: Option<i32> = None;
26     /// foo.map_or(Err("error"), |v| Ok(v));
27     /// ```
28     ///
29     /// Use instead:
30     /// ```rust
31     /// let foo: Option<i32> = None;
32     /// foo.ok_or("error");
33     /// ```
34     pub MANUAL_OK_OR,
35     pedantic,
36     "finds patterns that can be encoded more concisely with `Option::ok_or`"
37 }
38
39 declare_lint_pass!(ManualOkOr => [MANUAL_OK_OR]);
40
41 impl LateLintPass<'_> for ManualOkOr {
42     fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) {
43         if in_external_macro(cx.sess(), scrutinee.span) {
44             return;
45         }
46
47         if_chain! {
48             if let ExprKind::MethodCall(method_segment, _, args, _) = scrutinee.kind;
49             if method_segment.ident.name == sym!(map_or);
50             if args.len() == 3;
51             let method_receiver = &args[0];
52             let ty = cx.typeck_results().expr_ty(method_receiver);
53             if is_type_diagnostic_item(cx, ty, sym::option_type);
54             let or_expr = &args[1];
55             if is_ok_wrapping(cx, &args[2]);
56             if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind;
57             if match_qpath(err_path, &paths::RESULT_ERR);
58             if let Some(method_receiver_snippet) = snippet_opt(cx, method_receiver.span);
59             if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span);
60             if let Some(indent) = indent_of(cx, scrutinee.span);
61             then {
62                 let reindented_err_arg_snippet =
63                     reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4));
64                 span_lint_and_sugg(
65                     cx,
66                     MANUAL_OK_OR,
67                     scrutinee.span,
68                     "this pattern reimplements `Option::ok_or`",
69                     "replace with",
70                     format!(
71                         "{}.ok_or({})",
72                         method_receiver_snippet,
73                         reindented_err_arg_snippet
74                     ),
75                     Applicability::MachineApplicable,
76                 );
77             }
78         }
79     }
80 }
81
82 fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool {
83     if let ExprKind::Path(ref qpath) = map_expr.kind {
84         if match_qpath(qpath, &paths::RESULT_OK) {
85             return true;
86         }
87     }
88     if_chain! {
89         if let ExprKind::Closure(_, _, body_id, ..) = map_expr.kind;
90         let body = cx.tcx.hir().body(body_id);
91         if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind;
92         if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind;
93         if match_qpath(ok_path, &paths::RESULT_OK);
94         then { path_to_local_id(ok_arg, param_id) } else { false }
95     }
96 }