1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
2 use clippy_utils
::source
::{snippet, snippet_with_macro_callsite}
;
3 use clippy_utils
::ty
::is_type_diagnostic_item
;
4 use clippy_utils
::{differing_macro_contexts, get_parent_expr, in_macro, is_lang_ctor, match_def_path, paths}
;
5 use if_chain
::if_chain
;
6 use rustc_errors
::Applicability
;
7 use rustc_hir
::LangItem
::ResultErr
;
8 use rustc_hir
::{Expr, ExprKind, LangItem, MatchSource, QPath}
;
9 use rustc_lint
::{LateContext, LateLintPass}
;
10 use rustc_middle
::lint
::in_external_macro
;
11 use rustc_middle
::ty
::{self, Ty}
;
12 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
15 declare_clippy_lint
! {
16 /// **What it does:** Checks for usages of `Err(x)?`.
18 /// **Why is this bad?** The `?` operator is designed to allow calls that
19 /// can fail to be easily chained. For example, `foo()?.bar()` or
20 /// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will
21 /// always return), it is more clear to write `return Err(x)`.
23 /// **Known problems:** None.
27 /// fn foo(fail: bool) -> Result<i32, String> {
37 /// fn foo(fail: bool) -> Result<i32, String> {
39 /// return Err("failed".into());
46 "return errors explicitly rather than hiding them behind a `?`"
49 declare_lint_pass
!(TryErr
=> [TRY_ERR
]);
51 impl<'tcx
> LateLintPass
<'tcx
> for TryErr
{
52 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
53 // Looks for a structure like this:
54 // match ::std::ops::Try::into_result(Err(5)) {
55 // ::std::result::Result::Err(err) =>
56 // #[allow(unreachable_code)]
57 // return ::std::ops::Try::from_error(::std::convert::From::from(err)),
58 // ::std::result::Result::Ok(val) =>
59 // #[allow(unreachable_code)]
63 if !in_external_macro(cx
.tcx
.sess
, expr
.span
);
64 if let ExprKind
::Match(match_arg
, _
, MatchSource
::TryDesugar
) = expr
.kind
;
65 if let ExprKind
::Call(match_fun
, try_args
) = match_arg
.kind
;
66 if let ExprKind
::Path(ref match_fun_path
) = match_fun
.kind
;
67 if matches
!(match_fun_path
, QPath
::LangItem(LangItem
::TryIntoResult
, _
));
68 if let Some(try_arg
) = try_args
.get(0);
69 if let ExprKind
::Call(err_fun
, err_args
) = try_arg
.kind
;
70 if let Some(err_arg
) = err_args
.get(0);
71 if let ExprKind
::Path(ref err_fun_path
) = err_fun
.kind
;
72 if is_lang_ctor(cx
, err_fun_path
, ResultErr
);
73 if let Some(return_ty
) = find_return_type(cx
, &expr
.kind
);
79 if let Some(ty
) = result_error_type(cx
, return_ty
) {
83 } else if let Some(ty
) = poll_result_error_type(cx
, return_ty
) {
84 prefix
= "Poll::Ready(Err(";
87 } else if let Some(ty
) = poll_option_result_error_type(cx
, return_ty
) {
88 prefix
= "Poll::Ready(Some(Err(";
95 let expr_err_ty
= cx
.typeck_results().expr_ty(err_arg
);
96 let differing_contexts
= differing_macro_contexts(expr
.span
, err_arg
.span
);
98 let origin_snippet
= if in_macro(expr
.span
) && in_macro(err_arg
.span
) && differing_contexts
{
99 snippet(cx
, err_arg
.span
.ctxt().outer_expn_data().call_site
, "_")
100 } else if err_arg
.span
.from_expansion() && !in_macro(expr
.span
) {
101 snippet_with_macro_callsite(cx
, err_arg
.span
, "_")
103 snippet(cx
, err_arg
.span
, "_")
105 let ret_prefix
= if get_parent_expr(cx
, expr
).map_or(false, |e
| matches
!(e
.kind
, ExprKind
::Ret(_
))) {
106 "" // already returns
110 let suggestion
= if err_ty
== expr_err_ty
{
111 format
!("{}{}{}{}", ret_prefix
, prefix
, origin_snippet
, suffix
)
113 format
!("{}{}{}.into(){}", ret_prefix
, prefix
, origin_snippet
, suffix
)
120 "returning an `Err(_)` with the `?` operator",
123 Applicability
::MachineApplicable
130 /// Finds function return type by examining return expressions in match arms.
131 fn find_return_type
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &'tcx ExprKind
<'_
>) -> Option
<Ty
<'tcx
>> {
132 if let ExprKind
::Match(_
, arms
, MatchSource
::TryDesugar
) = expr
{
133 for arm
in arms
.iter() {
134 if let ExprKind
::Ret(Some(ret
)) = arm
.body
.kind
{
135 return Some(cx
.typeck_results().expr_ty(ret
));
142 /// Extracts the error type from Result<T, E>.
143 fn result_error_type
<'tcx
>(cx
: &LateContext
<'tcx
>, ty
: Ty
<'tcx
>) -> Option
<Ty
<'tcx
>> {
145 if let ty
::Adt(_
, subst
) = ty
.kind();
146 if is_type_diagnostic_item(cx
, ty
, sym
::result_type
);
148 Some(subst
.type_at(1))
155 /// Extracts the error type from Poll<Result<T, E>>.
156 fn poll_result_error_type
<'tcx
>(cx
: &LateContext
<'tcx
>, ty
: Ty
<'tcx
>) -> Option
<Ty
<'tcx
>> {
158 if let ty
::Adt(def
, subst
) = ty
.kind();
159 if match_def_path(cx
, def
.did
, &paths
::POLL
);
160 let ready_ty
= subst
.type_at(0);
162 if let ty
::Adt(ready_def
, ready_subst
) = ready_ty
.kind();
163 if cx
.tcx
.is_diagnostic_item(sym
::result_type
, ready_def
.did
);
165 Some(ready_subst
.type_at(1))
172 /// Extracts the error type from Poll<Option<Result<T, E>>>.
173 fn poll_option_result_error_type
<'tcx
>(cx
: &LateContext
<'tcx
>, ty
: Ty
<'tcx
>) -> Option
<Ty
<'tcx
>> {
175 if let ty
::Adt(def
, subst
) = ty
.kind();
176 if match_def_path(cx
, def
.did
, &paths
::POLL
);
177 let ready_ty
= subst
.type_at(0);
179 if let ty
::Adt(ready_def
, ready_subst
) = ready_ty
.kind();
180 if cx
.tcx
.is_diagnostic_item(sym
::option_type
, ready_def
.did
);
181 let some_ty
= ready_subst
.type_at(0);
183 if let ty
::Adt(some_def
, some_subst
) = some_ty
.kind();
184 if cx
.tcx
.is_diagnostic_item(sym
::result_type
, some_def
.did
);
186 Some(some_subst
.type_at(1))