|
| 1 | +use clippy_utils::diagnostics::span_lint_and_sugg; |
| 2 | +use clippy_utils::source::snippet_with_applicability; |
| 3 | +use clippy_utils::ty::is_type_diagnostic_item; |
| 4 | +use clippy_utils::{get_parent_expr, 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; |
| 10 | +use rustc_middle::ty::{self, Ty}; |
| 11 | +use rustc_span::{hygiene, sym}; |
| 12 | + |
| 13 | +use super::TRY_ERR; |
| 14 | + |
| 15 | +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutinee: &'tcx Expr<'_>) { |
| 16 | + // Looks for a structure like this: |
| 17 | + // match ::std::ops::Try::into_result(Err(5)) { |
| 18 | + // ::std::result::Result::Err(err) => |
| 19 | + // #[allow(unreachable_code)] |
| 20 | + // return ::std::ops::Try::from_error(::std::convert::From::from(err)), |
| 21 | + // ::std::result::Result::Ok(val) => |
| 22 | + // #[allow(unreachable_code)] |
| 23 | + // val, |
| 24 | + // }; |
| 25 | + if_chain! { |
| 26 | + if let ExprKind::Call(match_fun, try_args) = scrutinee.kind; |
| 27 | + if let ExprKind::Path(ref match_fun_path) = match_fun.kind; |
| 28 | + if matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, ..)); |
| 29 | + if let Some(try_arg) = try_args.get(0); |
| 30 | + if let ExprKind::Call(err_fun, err_args) = try_arg.kind; |
| 31 | + if let Some(err_arg) = err_args.get(0); |
| 32 | + if let ExprKind::Path(ref err_fun_path) = err_fun.kind; |
| 33 | + if is_lang_ctor(cx, err_fun_path, ResultErr); |
| 34 | + if let Some(return_ty) = find_return_type(cx, &expr.kind); |
| 35 | + then { |
| 36 | + let prefix; |
| 37 | + let suffix; |
| 38 | + let err_ty; |
| 39 | + |
| 40 | + if let Some(ty) = result_error_type(cx, return_ty) { |
| 41 | + prefix = "Err("; |
| 42 | + suffix = ")"; |
| 43 | + err_ty = ty; |
| 44 | + } else if let Some(ty) = poll_result_error_type(cx, return_ty) { |
| 45 | + prefix = "Poll::Ready(Err("; |
| 46 | + suffix = "))"; |
| 47 | + err_ty = ty; |
| 48 | + } else if let Some(ty) = poll_option_result_error_type(cx, return_ty) { |
| 49 | + prefix = "Poll::Ready(Some(Err("; |
| 50 | + suffix = ")))"; |
| 51 | + err_ty = ty; |
| 52 | + } else { |
| 53 | + return; |
| 54 | + }; |
| 55 | + |
| 56 | + let expr_err_ty = cx.typeck_results().expr_ty(err_arg); |
| 57 | + let span = hygiene::walk_chain(err_arg.span, try_arg.span.ctxt()); |
| 58 | + let mut applicability = Applicability::MachineApplicable; |
| 59 | + let origin_snippet = snippet_with_applicability(cx, span, "_", &mut applicability); |
| 60 | + let ret_prefix = if get_parent_expr(cx, expr).map_or(false, |e| matches!(e.kind, ExprKind::Ret(_))) { |
| 61 | + "" // already returns |
| 62 | + } else { |
| 63 | + "return " |
| 64 | + }; |
| 65 | + let suggestion = if err_ty == expr_err_ty { |
| 66 | + format!("{}{}{}{}", ret_prefix, prefix, origin_snippet, suffix) |
| 67 | + } else { |
| 68 | + format!("{}{}{}.into(){}", ret_prefix, prefix, origin_snippet, suffix) |
| 69 | + }; |
| 70 | + |
| 71 | + span_lint_and_sugg( |
| 72 | + cx, |
| 73 | + TRY_ERR, |
| 74 | + expr.span, |
| 75 | + "returning an `Err(_)` with the `?` operator", |
| 76 | + "try this", |
| 77 | + suggestion, |
| 78 | + applicability, |
| 79 | + ); |
| 80 | + } |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +/// Finds function return type by examining return expressions in match arms. |
| 85 | +fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> Option<Ty<'tcx>> { |
| 86 | + if let ExprKind::Match(_, arms, MatchSource::TryDesugar) = expr { |
| 87 | + for arm in arms.iter() { |
| 88 | + if let ExprKind::Ret(Some(ret)) = arm.body.kind { |
| 89 | + return Some(cx.typeck_results().expr_ty(ret)); |
| 90 | + } |
| 91 | + } |
| 92 | + } |
| 93 | + None |
| 94 | +} |
| 95 | + |
| 96 | +/// Extracts the error type from Result<T, E>. |
| 97 | +fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { |
| 98 | + if_chain! { |
| 99 | + if let ty::Adt(_, subst) = ty.kind(); |
| 100 | + if is_type_diagnostic_item(cx, ty, sym::Result); |
| 101 | + then { |
| 102 | + Some(subst.type_at(1)) |
| 103 | + } else { |
| 104 | + None |
| 105 | + } |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +/// Extracts the error type from Poll<Result<T, E>>. |
| 110 | +fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { |
| 111 | + if_chain! { |
| 112 | + if let ty::Adt(def, subst) = ty.kind(); |
| 113 | + if match_def_path(cx, def.did(), &paths::POLL); |
| 114 | + let ready_ty = subst.type_at(0); |
| 115 | + |
| 116 | + if let ty::Adt(ready_def, ready_subst) = ready_ty.kind(); |
| 117 | + if cx.tcx.is_diagnostic_item(sym::Result, ready_def.did()); |
| 118 | + then { |
| 119 | + Some(ready_subst.type_at(1)) |
| 120 | + } else { |
| 121 | + None |
| 122 | + } |
| 123 | + } |
| 124 | +} |
| 125 | + |
| 126 | +/// Extracts the error type from Poll<Option<Result<T, E>>>. |
| 127 | +fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { |
| 128 | + if_chain! { |
| 129 | + if let ty::Adt(def, subst) = ty.kind(); |
| 130 | + if match_def_path(cx, def.did(), &paths::POLL); |
| 131 | + let ready_ty = subst.type_at(0); |
| 132 | + |
| 133 | + if let ty::Adt(ready_def, ready_subst) = ready_ty.kind(); |
| 134 | + if cx.tcx.is_diagnostic_item(sym::Option, ready_def.did()); |
| 135 | + let some_ty = ready_subst.type_at(0); |
| 136 | + |
| 137 | + if let ty::Adt(some_def, some_subst) = some_ty.kind(); |
| 138 | + if cx.tcx.is_diagnostic_item(sym::Result, some_def.did()); |
| 139 | + then { |
| 140 | + Some(some_subst.type_at(1)) |
| 141 | + } else { |
| 142 | + None |
| 143 | + } |
| 144 | + } |
| 145 | +} |
0 commit comments