Skip to content

Commit 66a9705

Browse files
committed
Initial implementation of result_large_err
1 parent 4df6032 commit 66a9705

13 files changed

+401
-71
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4025,6 +4025,7 @@ Released 2018-09-13
40254025
[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
40264026
[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
40274027
[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used
4028+
[`result_large_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err
40284029
[`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option
40294030
[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn
40304031
[`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else

clippy_lints/src/functions/mod.rs

+51-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod must_use;
22
mod not_unsafe_ptr_arg_deref;
3-
mod result_unit_err;
3+
mod result;
44
mod too_many_arguments;
55
mod too_many_lines;
66

@@ -217,17 +217,62 @@ declare_clippy_lint! {
217217
"public function returning `Result` with an `Err` type of `()`"
218218
}
219219

220+
declare_clippy_lint! {
221+
/// ### What it does
222+
/// Checks for functions that return `Result` with an unusually large
223+
/// `Err`-variant.
224+
///
225+
/// ### Why is this bad?
226+
/// A `Result` is at least as large as the `Err`-variant. While we
227+
/// expect that variant to be seldomly used, the compiler needs to reserve
228+
/// and move that much memory every single time.
229+
///
230+
/// ### Known problems
231+
/// The size determined by Clippy is platform-dependent.
232+
///
233+
/// ### Examples
234+
/// ```rust
235+
/// pub enum ParseError {
236+
/// UnparsedBytes([u8; 512]),
237+
/// UnexpectedEof,
238+
/// }
239+
///
240+
/// // The `Result` has at least 512 bytes, even in the `Ok`-case
241+
/// pub fn parse() -> Result<(), ParseError> {
242+
/// Ok(())
243+
/// }
244+
/// ```
245+
/// should be
246+
/// ```
247+
/// pub enum ParseError {
248+
/// UnparsedBytes(Box<[u8; 512]>),
249+
/// UnexpectedEof,
250+
/// }
251+
///
252+
/// // The `Result` is slightly larger than a pointer
253+
/// pub fn parse() -> Result<(), ParseError> {
254+
/// Ok(())
255+
/// }
256+
/// ```
257+
#[clippy::version = "1.64.0"]
258+
pub RESULT_LARGE_ERR,
259+
perf,
260+
"function returning `Result` with large `Err` type"
261+
}
262+
220263
#[derive(Copy, Clone)]
221264
pub struct Functions {
222265
too_many_arguments_threshold: u64,
223266
too_many_lines_threshold: u64,
267+
large_error_threshold: u64,
224268
}
225269

226270
impl Functions {
227-
pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64) -> Self {
271+
pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64, large_error_threshold: u64) -> Self {
228272
Self {
229273
too_many_arguments_threshold,
230274
too_many_lines_threshold,
275+
large_error_threshold,
231276
}
232277
}
233278
}
@@ -240,6 +285,7 @@ impl_lint_pass!(Functions => [
240285
DOUBLE_MUST_USE,
241286
MUST_USE_CANDIDATE,
242287
RESULT_UNIT_ERR,
288+
RESULT_LARGE_ERR,
243289
]);
244290

245291
impl<'tcx> LateLintPass<'tcx> for Functions {
@@ -259,18 +305,18 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
259305

260306
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
261307
must_use::check_item(cx, item);
262-
result_unit_err::check_item(cx, item);
308+
result::check_item(cx, item, self.large_error_threshold);
263309
}
264310

265311
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
266312
must_use::check_impl_item(cx, item);
267-
result_unit_err::check_impl_item(cx, item);
313+
result::check_impl_item(cx, item, self.large_error_threshold);
268314
}
269315

270316
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
271317
too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold);
272318
not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
273319
must_use::check_trait_item(cx, item);
274-
result_unit_err::check_trait_item(cx, item);
320+
result::check_trait_item(cx, item, self.large_error_threshold);
275321
}
276322
}

clippy_lints/src/functions/result.rs

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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+
}

clippy_lints/src/functions/result_unit_err.rs

-66
This file was deleted.

clippy_lints/src/lib.register_all.rs

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
8080
LintId::of(functions::DOUBLE_MUST_USE),
8181
LintId::of(functions::MUST_USE_UNIT),
8282
LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
83+
LintId::of(functions::RESULT_LARGE_ERR),
8384
LintId::of(functions::RESULT_UNIT_ERR),
8485
LintId::of(functions::TOO_MANY_ARGUMENTS),
8586
LintId::of(if_let_mutex::IF_LET_MUTEX),

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ store.register_lints(&[
171171
functions::MUST_USE_CANDIDATE,
172172
functions::MUST_USE_UNIT,
173173
functions::NOT_UNSAFE_PTR_ARG_DEREF,
174+
functions::RESULT_LARGE_ERR,
174175
functions::RESULT_UNIT_ERR,
175176
functions::TOO_MANY_ARGUMENTS,
176177
functions::TOO_MANY_LINES,

clippy_lints/src/lib.register_perf.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
77
LintId::of(escape::BOXED_LOCAL),
88
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
99
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
10+
LintId::of(functions::RESULT_LARGE_ERR),
1011
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
1112
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
1213
LintId::of(loops::MANUAL_MEMCPY),

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -668,10 +668,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
668668
store.register_late_pass(move || Box::new(disallowed_names::DisallowedNames::new(disallowed_names.clone())));
669669
let too_many_arguments_threshold = conf.too_many_arguments_threshold;
670670
let too_many_lines_threshold = conf.too_many_lines_threshold;
671+
let large_error_threshold = conf.large_error_threshold;
671672
store.register_late_pass(move || {
672673
Box::new(functions::Functions::new(
673674
too_many_arguments_threshold,
674675
too_many_lines_threshold,
676+
large_error_threshold,
675677
))
676678
});
677679
let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();

clippy_lints/src/utils/conf.rs

+4
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,10 @@ define_Conf! {
379379
///
380380
/// Whether `dbg!` should be allowed in test functions
381381
(allow_dbg_in_tests: bool = false),
382+
/// Lint: RESULT_LARGE_ERR
383+
///
384+
/// The maximum size of the `Err`-variant in a `Result` returned from a function
385+
(large_error_threshold: u64 = 128),
382386
}
383387

384388
/// Search for the configuration file.

clippy_utils/src/ty.rs

+50
Original file line numberDiff line numberDiff line change
@@ -831,3 +831,53 @@ pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tc
831831
})
832832
.unwrap_or(false)
833833
}
834+
835+
/// Comes up with an "at least" guesstimate for the type's size, not taking into
836+
/// account the layout of type parameters.
837+
pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {
838+
use rustc_middle::ty::layout::LayoutOf;
839+
if !is_normalizable(cx, cx.param_env, ty) {
840+
return 0;
841+
}
842+
match (cx.layout_of(ty).map(|layout| layout.size.bytes()), ty.kind()) {
843+
(Ok(size), _) => size,
844+
(Err(_), ty::Tuple(list)) => list.as_substs().types().map(|t| approx_ty_size(cx, t)).sum(),
845+
(Err(_), ty::Array(t, n)) => {
846+
n.try_eval_usize(cx.tcx, cx.param_env).unwrap_or_default() * approx_ty_size(cx, *t)
847+
},
848+
(Err(_), ty::Adt(def, subst)) if def.is_struct() => def
849+
.variants()
850+
.iter()
851+
.map(|v| {
852+
v.fields
853+
.iter()
854+
.map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst)))
855+
.sum::<u64>()
856+
})
857+
.sum(),
858+
(Err(_), ty::Adt(def, subst)) if def.is_enum() => def
859+
.variants()
860+
.iter()
861+
.map(|v| {
862+
v.fields
863+
.iter()
864+
.map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst)))
865+
.sum::<u64>()
866+
})
867+
.max()
868+
.unwrap_or_default(),
869+
(Err(_), ty::Adt(def, subst)) if def.is_union() => def
870+
.variants()
871+
.iter()
872+
.map(|v| {
873+
v.fields
874+
.iter()
875+
.map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst)))
876+
.max()
877+
.unwrap_or_default()
878+
})
879+
.max()
880+
.unwrap_or_default(),
881+
(Err(_), _) => 0,
882+
}
883+
}

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
1919
enforced-import-renames
2020
enum-variant-name-threshold
2121
enum-variant-size-threshold
22+
large-error-threshold
2223
literal-representation-threshold
2324
max-fn-params-bools
2425
max-include-file-size

0 commit comments

Comments
 (0)