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