]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/unwrap_in_result.rs
Rollup merge of #91562 - dtolnay:asyncspace, r=Mark-Simulacrum
[rust.git] / src / tools / clippy / clippy_lints / src / unwrap_in_result.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::ty::is_type_diagnostic_item;
3 use clippy_utils::{method_chain_args, return_ty};
4 use if_chain::if_chain;
5 use rustc_hir as hir;
6 use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
7 use rustc_hir::{Expr, ImplItemKind};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_middle::hir::map::Map;
10 use rustc_middle::ty;
11 use rustc_session::{declare_lint_pass, declare_tool_lint};
12 use rustc_span::{sym, Span};
13
14 declare_clippy_lint! {
15     /// ### What it does
16     /// Checks for functions of type `Result` that contain `expect()` or `unwrap()`
17     ///
18     /// ### Why is this bad?
19     /// These functions promote recoverable errors to non-recoverable errors which may be undesirable in code bases which wish to avoid panics.
20     ///
21     /// ### Known problems
22     /// This can cause false positives in functions that handle both recoverable and non recoverable errors.
23     ///
24     /// ### Example
25     /// Before:
26     /// ```rust
27     /// fn divisible_by_3(i_str: String) -> Result<(), String> {
28     ///     let i = i_str
29     ///         .parse::<i32>()
30     ///         .expect("cannot divide the input by three");
31     ///
32     ///     if i % 3 != 0 {
33     ///         Err("Number is not divisible by 3")?
34     ///     }
35     ///
36     ///     Ok(())
37     /// }
38     /// ```
39     ///
40     /// After:
41     /// ```rust
42     /// fn divisible_by_3(i_str: String) -> Result<(), String> {
43     ///     let i = i_str
44     ///         .parse::<i32>()
45     ///         .map_err(|e| format!("cannot divide the input by three: {}", e))?;
46     ///
47     ///     if i % 3 != 0 {
48     ///         Err("Number is not divisible by 3")?
49     ///     }
50     ///
51     ///     Ok(())
52     /// }
53     /// ```
54     #[clippy::version = "1.48.0"]
55     pub UNWRAP_IN_RESULT,
56     restriction,
57     "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`"
58 }
59
60 declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]);
61
62 impl<'tcx> LateLintPass<'tcx> for UnwrapInResult {
63     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
64         if_chain! {
65             // first check if it's a method or function
66             if let hir::ImplItemKind::Fn(ref _signature, _) = impl_item.kind;
67             // checking if its return type is `result` or `option`
68             if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::Result)
69                 || is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::Option);
70             then {
71                 lint_impl_body(cx, impl_item.span, impl_item);
72             }
73         }
74     }
75 }
76
77 struct FindExpectUnwrap<'a, 'tcx> {
78     lcx: &'a LateContext<'tcx>,
79     typeck_results: &'tcx ty::TypeckResults<'tcx>,
80     result: Vec<Span>,
81 }
82
83 impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> {
84     type Map = Map<'tcx>;
85
86     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
87         // check for `expect`
88         if let Some(arglists) = method_chain_args(expr, &["expect"]) {
89             let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
90             if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
91                 || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
92             {
93                 self.result.push(expr.span);
94             }
95         }
96
97         // check for `unwrap`
98         if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
99             let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
100             if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
101                 || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
102             {
103                 self.result.push(expr.span);
104             }
105         }
106
107         // and check sub-expressions
108         intravisit::walk_expr(self, expr);
109     }
110
111     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
112         NestedVisitorMap::None
113     }
114 }
115
116 fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
117     if let ImplItemKind::Fn(_, body_id) = impl_item.kind {
118         let body = cx.tcx.hir().body(body_id);
119         let mut fpu = FindExpectUnwrap {
120             lcx: cx,
121             typeck_results: cx.tcx.typeck(impl_item.def_id),
122             result: Vec::new(),
123         };
124         fpu.visit_expr(&body.value);
125
126         // if we've found one, lint
127         if !fpu.result.is_empty() {
128             span_lint_and_then(
129                 cx,
130                 UNWRAP_IN_RESULT,
131                 impl_span,
132                 "used unwrap or expect in a function that returns result or option",
133                 move |diag| {
134                     diag.help("unwrap and expect should not be used in a function that returns result or option");
135                     diag.span_note(fpu.result, "potential non-recoverable error(s)");
136                 },
137             );
138         }
139     }
140 }