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