]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/functions/result.rs
Rollup merge of #104252 - faern:stabilize-const_socketaddr, r=JohnTitor
[rust.git] / src / tools / clippy / clippy_lints / src / functions / result.rs
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, Adt, Ty};
6 use rustc_span::{sym, Span};
7
8 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
9 use clippy_utils::trait_ref_of_method;
10 use clippy_utils::ty::{approx_ty_size, is_type_diagnostic_item, AdtVariantInfo};
11
12 use super::{RESULT_LARGE_ERR, RESULT_UNIT_ERR};
13
14 /// The type of the `Err`-variant in a `std::result::Result` returned by the
15 /// given `FnDecl`
16 fn result_err_ty<'tcx>(
17     cx: &LateContext<'tcx>,
18     decl: &hir::FnDecl<'tcx>,
19     id: hir::def_id::LocalDefId,
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 = cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).subst_identity().output())
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.owner_id.def_id, item.span)
38     {
39         if cx.effective_visibilities.is_exported(item.owner_id.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.owner_id.def_id, item.span)
51         && trait_ref_of_method(cx, item.owner_id.def_id).is_none()
52     {
53         if cx.effective_visibilities.is_exported(item.owner_id.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.owner_id.def_id, item.span) {
65             if cx.effective_visibilities.is_exported(item.owner_id.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     if_chain! {
88         if let Adt(adt, subst) = err_ty.kind();
89         if let Some(local_def_id) = err_ty.ty_adt_def().expect("already checked this is adt").did().as_local();
90         if let Some(hir::Node::Item(item)) = cx
91             .tcx
92             .hir()
93             .find_by_def_id(local_def_id);
94         if let hir::ItemKind::Enum(ref def, _) = item.kind;
95         then {
96             let variants_size = AdtVariantInfo::new(cx, *adt, subst);
97             if let Some((first_variant, variants)) = variants_size.split_first()
98                 && first_variant.size >= large_err_threshold
99             {
100                 span_lint_and_then(
101                     cx,
102                     RESULT_LARGE_ERR,
103                     hir_ty_span,
104                     "the `Err`-variant returned from this function is very large",
105                     |diag| {
106                         diag.span_label(
107                             def.variants[first_variant.ind].span,
108                             format!("the largest variant contains at least {} bytes", variants_size[0].size),
109                         );
110
111                         for variant in variants {
112                             if variant.size >= large_err_threshold {
113                                 let variant_def = &def.variants[variant.ind];
114                                 diag.span_label(
115                                     variant_def.span,
116                                     format!("the variant `{}` contains at least {} bytes", variant_def.ident, variant.size),
117                                 );
118                             }
119                         }
120
121                         diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
122                     }
123                 );
124             }
125         }
126         else {
127             let ty_size = approx_ty_size(cx, err_ty);
128             if ty_size >= large_err_threshold {
129                 span_lint_and_then(
130                     cx,
131                     RESULT_LARGE_ERR,
132                     hir_ty_span,
133                     "the `Err`-variant returned from this function is very large",
134                     |diag: &mut Diagnostic| {
135                         diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
136                         diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
137                     },
138                 );
139             }
140         }
141     }
142 }