|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_then;
|
2 |
| -use clippy_utils::ty::is_type_diagnostic_item; |
3 |
| -use clippy_utils::visitors::for_each_expr; |
4 |
| -use clippy_utils::{method_chain_args, return_ty}; |
5 |
| -use core::ops::ControlFlow; |
6 |
| -use rustc_hir as hir; |
7 |
| -use rustc_hir::ImplItemKind; |
| 2 | +use clippy_utils::{return_ty, sym}; |
| 3 | +use rustc_hir::{ |
| 4 | + Body, BodyOwnerKind, Expr, ExprKind, FnSig, ImplItem, ImplItemKind, Item, ItemKind, OwnerId, PathSegment, QPath, |
| 5 | +}; |
8 | 6 | use rustc_lint::{LateContext, LateLintPass};
|
9 |
| -use rustc_session::declare_lint_pass; |
10 |
| -use rustc_span::{Span, sym}; |
| 7 | +use rustc_middle::ty::Ty; |
| 8 | +use rustc_session::impl_lint_pass; |
| 9 | +use rustc_span::{Ident, Span, Symbol}; |
11 | 10 |
|
12 | 11 | declare_clippy_lint! {
|
13 | 12 | /// ### What it does
|
@@ -57,62 +56,166 @@ declare_clippy_lint! {
|
57 | 56 | "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`"
|
58 | 57 | }
|
59 | 58 |
|
60 |
| -declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]); |
| 59 | +impl_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]); |
61 | 60 |
|
62 |
| -impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { |
63 |
| - fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { |
64 |
| - if let ImplItemKind::Fn(ref _signature, _) = impl_item.kind |
65 |
| - // first check if it's a method or function |
66 |
| - // checking if its return type is `result` or `option` |
67 |
| - && (is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Result) |
68 |
| - || is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Option)) |
69 |
| - { |
70 |
| - lint_impl_body(cx, impl_item.span, impl_item); |
| 61 | +#[derive(Clone, Copy, Eq, PartialEq)] |
| 62 | +enum OptionOrResult { |
| 63 | + Option, |
| 64 | + Result, |
| 65 | +} |
| 66 | + |
| 67 | +impl OptionOrResult { |
| 68 | + fn with_article(self) -> &'static str { |
| 69 | + if matches!(self, Self::Option) { |
| 70 | + "an `Option`" |
| 71 | + } else { |
| 72 | + "a `Result`" |
71 | 73 | }
|
72 | 74 | }
|
73 | 75 | }
|
74 | 76 |
|
75 |
| -fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) { |
76 |
| - if let ImplItemKind::Fn(_, body_id) = impl_item.kind { |
77 |
| - let body = cx.tcx.hir_body(body_id); |
78 |
| - let typeck = cx.tcx.typeck(impl_item.owner_id.def_id); |
79 |
| - let mut result = Vec::new(); |
80 |
| - let _: Option<!> = for_each_expr(cx, body.value, |e| { |
81 |
| - // check for `expect` |
82 |
| - if let Some(arglists) = method_chain_args(e, &[sym::expect]) { |
83 |
| - let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs(); |
84 |
| - if is_type_diagnostic_item(cx, receiver_ty, sym::Option) |
85 |
| - || is_type_diagnostic_item(cx, receiver_ty, sym::Result) |
86 |
| - { |
87 |
| - result.push(e.span); |
88 |
| - } |
89 |
| - } |
90 |
| - |
91 |
| - // check for `unwrap` |
92 |
| - if let Some(arglists) = method_chain_args(e, &[sym::unwrap]) { |
93 |
| - let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs(); |
94 |
| - if is_type_diagnostic_item(cx, receiver_ty, sym::Option) |
95 |
| - || is_type_diagnostic_item(cx, receiver_ty, sym::Result) |
96 |
| - { |
97 |
| - result.push(e.span); |
98 |
| - } |
99 |
| - } |
100 |
| - |
101 |
| - ControlFlow::Continue(()) |
| 77 | +struct OptionOrResultFn { |
| 78 | + kind: OptionOrResult, |
| 79 | + return_ty_span: Option<Span>, |
| 80 | +} |
| 81 | + |
| 82 | +#[derive(Default)] |
| 83 | +pub struct UnwrapInResult { |
| 84 | + fn_stack: Vec<Option<OptionOrResultFn>>, |
| 85 | + current_fn: Option<OptionOrResultFn>, |
| 86 | +} |
| 87 | + |
| 88 | +impl UnwrapInResult { |
| 89 | + fn enter_item(&mut self, cx: &LateContext<'_>, fn_def_id: OwnerId, sig: &FnSig<'_>) { |
| 90 | + self.fn_stack.push(self.current_fn.take()); |
| 91 | + self.current_fn = is_option_or_result(cx, return_ty(cx, fn_def_id)).map(|kind| OptionOrResultFn { |
| 92 | + kind, |
| 93 | + return_ty_span: Some(sig.decl.output.span()), |
102 | 94 | });
|
| 95 | + } |
103 | 96 |
|
104 |
| - // if we've found one, lint |
105 |
| - if !result.is_empty() { |
| 97 | + fn leave_item(&mut self) { |
| 98 | + self.current_fn = self.fn_stack.pop().unwrap(); |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { |
| 103 | + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { |
| 104 | + if let ImplItemKind::Fn(sig, _) = &impl_item.kind { |
| 105 | + self.enter_item(cx, impl_item.owner_id, sig); |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + fn check_impl_item_post(&mut self, _: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'tcx>) { |
| 110 | + if let ImplItemKind::Fn(..) = impl_item.kind { |
| 111 | + self.leave_item(); |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { |
| 116 | + if let ItemKind::Fn { |
| 117 | + has_body: true, sig, .. |
| 118 | + } = &item.kind |
| 119 | + { |
| 120 | + self.enter_item(cx, item.owner_id, sig); |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + fn check_item_post(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { |
| 125 | + if let ItemKind::Fn { has_body: true, .. } = item.kind { |
| 126 | + self.leave_item(); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { |
| 131 | + if expr.span.from_expansion() { |
| 132 | + return; |
| 133 | + } |
| 134 | + |
| 135 | + if let Some(OptionOrResultFn { |
| 136 | + kind, |
| 137 | + ref mut return_ty_span, |
| 138 | + }) = self.current_fn |
| 139 | + && let Some((oor, fn_name)) = is_unwrap_or_expect_call(cx, expr) |
| 140 | + && oor == kind |
| 141 | + { |
106 | 142 | span_lint_and_then(
|
107 | 143 | cx,
|
108 | 144 | UNWRAP_IN_RESULT,
|
109 |
| - impl_span, |
110 |
| - "used unwrap or expect in a function that returns result or option", |
111 |
| - move |diag| { |
112 |
| - diag.help("unwrap and expect should not be used in a function that returns result or option"); |
113 |
| - diag.span_note(result, "potential non-recoverable error(s)"); |
| 145 | + expr.span, |
| 146 | + format!("`{fn_name}` used in a function that returns {}", kind.with_article()), |
| 147 | + |diag| { |
| 148 | + // Issue the note and help only once per function |
| 149 | + if let Some(span) = return_ty_span.take() { |
| 150 | + diag.span_note(span, "in this function signature"); |
| 151 | + let complement = if kind == OptionOrResult::Result { |
| 152 | + " or calling the `.map_err()` method" |
| 153 | + } else { |
| 154 | + "" |
| 155 | + }; |
| 156 | + diag.help(format!("consider using the `?` operator{complement}")); |
| 157 | + } |
114 | 158 | },
|
115 | 159 | );
|
116 | 160 | }
|
117 | 161 | }
|
| 162 | + |
| 163 | + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { |
| 164 | + let body_def_id = cx.tcx.hir_body_owner_def_id(body.id()); |
| 165 | + if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) { |
| 166 | + // When entering a body which is not a function, mask the potential surrounding |
| 167 | + // function to not apply the lint. |
| 168 | + self.fn_stack.push(self.current_fn.take()); |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { |
| 173 | + let body_def_id = cx.tcx.hir_body_owner_def_id(body.id()); |
| 174 | + if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) { |
| 175 | + // Unmask the potential surrounding function. |
| 176 | + self.current_fn = self.fn_stack.pop().unwrap(); |
| 177 | + } |
| 178 | + } |
| 179 | +} |
| 180 | + |
| 181 | +fn is_option_or_result(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<OptionOrResult> { |
| 182 | + match ty.ty_adt_def().and_then(|def| cx.tcx.get_diagnostic_name(def.did())) { |
| 183 | + Some(sym::Option) => Some(OptionOrResult::Option), |
| 184 | + Some(sym::Result) => Some(OptionOrResult::Result), |
| 185 | + _ => None, |
| 186 | + } |
| 187 | +} |
| 188 | + |
| 189 | +fn is_unwrap_or_expect_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(OptionOrResult, Symbol)> { |
| 190 | + if let ExprKind::Call(func, _) = expr.kind |
| 191 | + && let ExprKind::Path(QPath::TypeRelative( |
| 192 | + hir_ty, |
| 193 | + PathSegment { |
| 194 | + ident: |
| 195 | + Ident { |
| 196 | + name: name @ (sym::unwrap | sym::expect), |
| 197 | + .. |
| 198 | + }, |
| 199 | + .. |
| 200 | + }, |
| 201 | + )) = func.kind |
| 202 | + { |
| 203 | + is_option_or_result(cx, cx.typeck_results().node_type(hir_ty.hir_id)).map(|oor| (oor, *name)) |
| 204 | + } else if let ExprKind::MethodCall( |
| 205 | + PathSegment { |
| 206 | + ident: Ident { |
| 207 | + name: name @ (sym::unwrap | sym::expect), |
| 208 | + .. |
| 209 | + }, |
| 210 | + .. |
| 211 | + }, |
| 212 | + recv, |
| 213 | + _, |
| 214 | + _, |
| 215 | + ) = expr.kind |
| 216 | + { |
| 217 | + is_option_or_result(cx, cx.typeck_results().expr_ty_adjusted(recv)).map(|oor| (oor, *name)) |
| 218 | + } else { |
| 219 | + None |
| 220 | + } |
118 | 221 | }
|
0 commit comments