|
| 1 | +use rustc_errors::Diagnostic; |
| 2 | +use rustc_hir as hir; |
| 3 | +use rustc_lint::{LateContext, LintContext}; |
| 4 | +use rustc_middle::lint::in_external_macro; |
| 5 | +use rustc_middle::ty::{self, Ty}; |
| 6 | +use rustc_span::{sym, Span}; |
| 7 | +use rustc_typeck::hir_ty_to_ty; |
| 8 | + |
| 9 | +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; |
| 10 | +use clippy_utils::trait_ref_of_method; |
| 11 | +use clippy_utils::ty::{approx_ty_size, is_type_diagnostic_item}; |
| 12 | + |
| 13 | +use super::{RESULT_LARGE_ERR, RESULT_UNIT_ERR}; |
| 14 | + |
| 15 | +/// The type of the `Err`-variant in a `std::result::Result` returned by the |
| 16 | +/// given `FnDecl` |
| 17 | +fn result_err_ty<'tcx>( |
| 18 | + cx: &LateContext<'tcx>, |
| 19 | + decl: &hir::FnDecl<'tcx>, |
| 20 | + item_span: Span, |
| 21 | +) -> Option<(&'tcx hir::Ty<'tcx>, Ty<'tcx>)> { |
| 22 | + if !in_external_macro(cx.sess(), item_span) |
| 23 | + && let hir::FnRetTy::Return(hir_ty) = decl.output |
| 24 | + && let ty = hir_ty_to_ty(cx.tcx, hir_ty) |
| 25 | + && is_type_diagnostic_item(cx, ty, sym::Result) |
| 26 | + && let ty::Adt(_, substs) = ty.kind() |
| 27 | + { |
| 28 | + let err_ty = substs.type_at(1); |
| 29 | + Some((hir_ty, err_ty)) |
| 30 | + } else { |
| 31 | + None |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64) { |
| 36 | + if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind |
| 37 | + && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.span) |
| 38 | + { |
| 39 | + if cx.access_levels.is_exported(item.def_id) { |
| 40 | + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); |
| 41 | + check_result_unit_err(cx, err_ty, fn_header_span); |
| 42 | + } |
| 43 | + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::ImplItem<'tcx>, large_err_threshold: u64) { |
| 48 | + // Don't lint if method is a trait's implementation, we can't do anything about those |
| 49 | + if let hir::ImplItemKind::Fn(ref sig, _) = item.kind |
| 50 | + && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.span) |
| 51 | + && trait_ref_of_method(cx, item.def_id).is_none() |
| 52 | + { |
| 53 | + if cx.access_levels.is_exported(item.def_id) { |
| 54 | + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); |
| 55 | + check_result_unit_err(cx, err_ty, fn_header_span); |
| 56 | + } |
| 57 | + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::TraitItem<'tcx>, large_err_threshold: u64) { |
| 62 | + if let hir::TraitItemKind::Fn(ref sig, _) = item.kind { |
| 63 | + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); |
| 64 | + if let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.span) { |
| 65 | + if cx.access_levels.is_exported(item.def_id) { |
| 66 | + check_result_unit_err(cx, err_ty, fn_header_span); |
| 67 | + } |
| 68 | + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); |
| 69 | + } |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: Span) { |
| 74 | + if err_ty.is_unit() { |
| 75 | + span_lint_and_help( |
| 76 | + cx, |
| 77 | + RESULT_UNIT_ERR, |
| 78 | + fn_header_span, |
| 79 | + "this returns a `Result<_, ()>`", |
| 80 | + None, |
| 81 | + "use a custom `Error` type instead", |
| 82 | + ); |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) { |
| 87 | + let ty_size = approx_ty_size(cx, err_ty); |
| 88 | + if ty_size >= large_err_threshold { |
| 89 | + span_lint_and_then( |
| 90 | + cx, |
| 91 | + RESULT_LARGE_ERR, |
| 92 | + hir_ty_span, |
| 93 | + "the `Err`-variant returned from this function is very large", |
| 94 | + |diag: &mut Diagnostic| { |
| 95 | + diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes")); |
| 96 | + diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`")); |
| 97 | + }, |
| 98 | + ); |
| 99 | + } |
| 100 | +} |
0 commit comments