6 mod redundant_allocation;
10 use clippy_utils::diagnostics::span_lint;
12 use rustc_hir::intravisit::{walk_ty, FnKind, NestedVisitorMap, Visitor};
14 Body, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local,
15 MutTy, QPath, TraitFn, TraitItem, TraitItemKind, TyKind,
17 use rustc_lint::{LateContext, LateLintPass};
18 use rustc_middle::hir::map::Map;
19 use rustc_session::{declare_tool_lint, impl_lint_pass};
20 use rustc_span::source_map::Span;
21 use rustc_target::spec::abi::Abi;
23 declare_clippy_lint! {
24 /// **What it does:** Checks for use of `Box<Vec<_>>` anywhere in the code.
25 /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information.
27 /// **Why is this bad?** `Vec` already keeps its contents in a separate area on
28 /// the heap. So if you `Box` it, you just add another level of indirection
29 /// without any benefit whatsoever.
31 /// **Known problems:** None.
36 /// values: Box<Vec<Foo>>,
49 "usage of `Box<Vec<T>>`, vector elements are already on the heap"
52 declare_clippy_lint! {
53 /// **What it does:** Checks for use of `Vec<Box<T>>` where T: Sized anywhere in the code.
54 /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information.
56 /// **Why is this bad?** `Vec` already keeps its contents in a separate area on
57 /// the heap. So if you `Box` its contents, you just add another level of indirection.
59 /// **Known problems:** Vec<Box<T: Sized>> makes sense if T is a large type (see [#3530](https://github.com/rust-lang/rust-clippy/issues/3530),
65 /// values: Vec<Box<i32>>,
78 "usage of `Vec<Box<T>>` where T: Sized, vector elements are already on the heap"
81 declare_clippy_lint! {
82 /// **What it does:** Checks for use of `Option<Option<_>>` in function signatures and type
85 /// **Why is this bad?** `Option<_>` represents an optional value. `Option<Option<_>>`
86 /// represents an optional optional value which is logically the same thing as an optional
87 /// value but has an unneeded extra level of wrapping.
89 /// If you have a case where `Some(Some(_))`, `Some(None)` and `None` are distinct cases,
90 /// consider a custom `enum` instead, with clear names for each case.
92 /// **Known problems:** None.
96 /// fn get_data() -> Option<Option<u32>> {
104 /// pub enum Contents {
105 /// Data(Vec<u8>), // Was Some(Some(Vec<u8>))
106 /// NotYetFetched, // Was Some(None)
107 /// None, // Was None
110 /// fn get_data() -> Contents {
116 "usage of `Option<Option<T>>`"
119 declare_clippy_lint! {
120 /// **What it does:** Checks for usage of any `LinkedList`, suggesting to use a
121 /// `Vec` or a `VecDeque` (formerly called `RingBuf`).
123 /// **Why is this bad?** Gankro says:
125 /// > The TL;DR of `LinkedList` is that it's built on a massive amount of
126 /// pointers and indirection.
127 /// > It wastes memory, it has terrible cache locality, and is all-around slow.
129 /// > "only" amortized for push/pop, should be faster in the general case for
130 /// almost every possible
131 /// > workload, and isn't even amortized at all if you can predict the capacity
134 /// > `LinkedList`s are only really good if you're doing a lot of merging or
135 /// splitting of lists.
136 /// > This is because they can just mangle some pointers instead of actually
137 /// copying the data. Even
138 /// > if you're doing a lot of insertion in the middle of the list, `RingBuf`
139 /// can still be better
140 /// > because of how expensive it is to seek to the middle of a `LinkedList`.
142 /// **Known problems:** False positives – the instances where using a
143 /// `LinkedList` makes sense are few and far between, but they can still happen.
147 /// # use std::collections::LinkedList;
148 /// let x: LinkedList<usize> = LinkedList::new();
152 "usage of LinkedList, usually a vector is faster, or a more specialized data structure like a `VecDeque`"
155 declare_clippy_lint! {
156 /// **What it does:** Checks for use of `&Box<T>` anywhere in the code.
157 /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information.
159 /// **Why is this bad?** Any `&Box<T>` can also be a `&T`, which is more
162 /// **Known problems:** None.
166 /// fn foo(bar: &Box<T>) { ... }
172 /// fn foo(bar: &T) { ... }
176 "a borrow of a boxed type"
179 declare_clippy_lint! {
180 /// **What it does:** Checks for use of redundant allocations anywhere in the code.
182 /// **Why is this bad?** Expressions such as `Rc<&T>`, `Rc<Rc<T>>`, `Rc<Box<T>>`, `Box<&T>`
183 /// add an unnecessary level of indirection.
185 /// **Known problems:** None.
189 /// # use std::rc::Rc;
190 /// fn foo(bar: Rc<&usize>) {}
196 /// fn foo(bar: &usize) {}
198 pub REDUNDANT_ALLOCATION,
200 "redundant allocation"
203 declare_clippy_lint! {
204 /// **What it does:** Checks for `Rc<T>` and `Arc<T>` when `T` is a mutable buffer type such as `String` or `Vec`.
206 /// **Why is this bad?** Expressions such as `Rc<String>` usually have no advantage over `Rc<str>`, since
207 /// it is larger and involves an extra level of indirection, and doesn't implement `Borrow<str>`.
209 /// While mutating a buffer type would still be possible with `Rc::get_mut()`, it only
210 /// works if there are no additional references yet, which usually defeats the purpose of
211 /// enclosing it in a shared ownership type. Instead, additionally wrapping the inner
212 /// type with an interior mutable container (such as `RefCell` or `Mutex`) would normally
215 /// **Known problems:** This pattern can be desirable to avoid the overhead of a `RefCell` or `Mutex` for
216 /// cases where mutation only happens before there are any additional references.
220 /// # use std::rc::Rc;
221 /// fn foo(interned: Rc<String>) { ... }
227 /// fn foo(interned: Rc<str>) { ... }
231 "shared ownership of a buffer type"
235 vec_box_size_threshold: u64,
238 impl_lint_pass!(Types => [BOX_VEC, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION, RC_BUFFER]);
240 impl<'tcx> LateLintPass<'tcx> for Types {
241 fn check_fn(&mut self, cx: &LateContext<'_>, _: FnKind<'_>, decl: &FnDecl<'_>, _: &Body<'_>, _: Span, id: HirId) {
242 // Skip trait implementations; see issue #605.
243 if let Some(hir::Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_item(id)) {
244 if let ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = item.kind {
249 self.check_fn_decl(cx, decl);
252 fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) {
253 self.check_ty(cx, &field.ty, false);
256 fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
258 TraitItemKind::Const(ref ty, _) | TraitItemKind::Type(_, Some(ref ty)) => self.check_ty(cx, ty, false),
259 TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, &sig.decl),
260 TraitItemKind::Type(..) => (),
264 fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
265 if let Some(ref ty) = local.ty {
266 self.check_ty(cx, ty, true);
272 pub fn new(vec_box_size_threshold: u64) -> Self {
273 Self { vec_box_size_threshold }
276 fn check_fn_decl(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>) {
277 for input in decl.inputs {
278 self.check_ty(cx, input, false);
281 if let FnRetTy::Return(ref ty) = decl.output {
282 self.check_ty(cx, ty, false);
286 /// Recursively check for `TypePass` lints in the given type. Stop at the first
289 /// The parameter `is_local` distinguishes the context of the type.
290 fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, is_local: bool) {
291 if hir_ty.span.from_expansion() {
295 TyKind::Path(ref qpath) if !is_local => {
296 let hir_id = hir_ty.hir_id;
297 let res = cx.qpath_res(qpath, hir_id);
298 if let Some(def_id) = res.opt_def_id() {
299 let mut triggered = false;
300 triggered |= box_vec::check(cx, hir_ty, qpath, def_id);
301 triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id);
302 triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id);
303 triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold);
304 triggered |= option_option::check(cx, hir_ty, qpath, def_id);
305 triggered |= linked_list::check(cx, hir_ty, def_id);
312 QPath::Resolved(Some(ref ty), ref p) => {
313 self.check_ty(cx, ty, is_local);
314 for ty in p.segments.iter().flat_map(|seg| {
317 .map_or_else(|| [].iter(), |params| params.args.iter())
318 .filter_map(|arg| match arg {
319 GenericArg::Type(ty) => Some(ty),
323 self.check_ty(cx, ty, is_local);
326 QPath::Resolved(None, ref p) => {
327 for ty in p.segments.iter().flat_map(|seg| {
330 .map_or_else(|| [].iter(), |params| params.args.iter())
331 .filter_map(|arg| match arg {
332 GenericArg::Type(ty) => Some(ty),
336 self.check_ty(cx, ty, is_local);
339 QPath::TypeRelative(ref ty, ref seg) => {
340 self.check_ty(cx, ty, is_local);
341 if let Some(ref params) = seg.args {
342 for ty in params.args.iter().filter_map(|arg| match arg {
343 GenericArg::Type(ty) => Some(ty),
346 self.check_ty(cx, ty, is_local);
350 QPath::LangItem(..) => {},
353 TyKind::Rptr(ref lt, ref mut_ty) => {
354 if !borrowed_box::check(cx, hir_ty, lt, mut_ty) {
355 self.check_ty(cx, &mut_ty.ty, is_local);
358 TyKind::Slice(ref ty) | TyKind::Array(ref ty, _) | TyKind::Ptr(MutTy { ref ty, .. }) => {
359 self.check_ty(cx, ty, is_local)
361 TyKind::Tup(tys) => {
363 self.check_ty(cx, ty, is_local);
371 declare_clippy_lint! {
372 /// **What it does:** Checks for types used in structs, parameters and `let`
373 /// declarations above a certain complexity threshold.
375 /// **Why is this bad?** Too complex types make the code less readable. Consider
376 /// using a `type` definition to simplify them.
378 /// **Known problems:** None.
382 /// # use std::rc::Rc;
384 /// inner: Rc<Vec<Vec<Box<(u32, u32, u32, u32)>>>>,
389 "usage of very complex types that might be better factored into `type` definitions"
392 pub struct TypeComplexity {
396 impl TypeComplexity {
398 pub fn new(threshold: u64) -> Self {
403 impl_lint_pass!(TypeComplexity => [TYPE_COMPLEXITY]);
405 impl<'tcx> LateLintPass<'tcx> for TypeComplexity {
408 cx: &LateContext<'tcx>,
410 decl: &'tcx FnDecl<'_>,
415 self.check_fndecl(cx, decl);
418 fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'_>) {
419 // enum variants are also struct fields now
420 self.check_type(cx, &field.ty);
423 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
425 ItemKind::Static(ref ty, _, _) | ItemKind::Const(ref ty, _) => self.check_type(cx, ty),
426 // functions, enums, structs, impls and traits are covered
431 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
433 TraitItemKind::Const(ref ty, _) | TraitItemKind::Type(_, Some(ref ty)) => self.check_type(cx, ty),
434 TraitItemKind::Fn(FnSig { ref decl, .. }, TraitFn::Required(_)) => self.check_fndecl(cx, decl),
435 // methods with default impl are covered by check_fn
436 TraitItemKind::Type(..) | TraitItemKind::Fn(_, TraitFn::Provided(_)) => (),
440 fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
442 ImplItemKind::Const(ref ty, _) | ImplItemKind::TyAlias(ref ty) => self.check_type(cx, ty),
443 // methods are covered by check_fn
444 ImplItemKind::Fn(..) => (),
448 fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
449 if let Some(ref ty) = local.ty {
450 self.check_type(cx, ty);
455 impl<'tcx> TypeComplexity {
456 fn check_fndecl(&self, cx: &LateContext<'tcx>, decl: &'tcx FnDecl<'_>) {
457 for arg in decl.inputs {
458 self.check_type(cx, arg);
460 if let FnRetTy::Return(ref ty) = decl.output {
461 self.check_type(cx, ty);
465 fn check_type(&self, cx: &LateContext<'_>, ty: &hir::Ty<'_>) {
466 if ty.span.from_expansion() {
470 let mut visitor = TypeComplexityVisitor { score: 0, nest: 1 };
471 visitor.visit_ty(ty);
475 if score > self.threshold {
480 "very complex type used. Consider factoring parts into `type` definitions",
486 /// Walks a type and assigns a complexity score to it.
487 struct TypeComplexityVisitor {
488 /// total complexity score of the type
490 /// current nesting level
494 impl<'tcx> Visitor<'tcx> for TypeComplexityVisitor {
495 type Map = Map<'tcx>;
497 fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) {
498 let (add_score, sub_nest) = match ty.kind {
499 // _, &x and *x have only small overhead; don't mess with nesting level
500 TyKind::Infer | TyKind::Ptr(..) | TyKind::Rptr(..) => (1, 0),
502 // the "normal" components of a type: named types, arrays/tuples
503 TyKind::Path(..) | TyKind::Slice(..) | TyKind::Tup(..) | TyKind::Array(..) => (10 * self.nest, 1),
505 // function types bring a lot of overhead
506 TyKind::BareFn(ref bare) if bare.abi == Abi::Rust => (50 * self.nest, 1),
508 TyKind::TraitObject(ref param_bounds, _, _) => {
509 let has_lifetime_parameters = param_bounds.iter().any(|bound| {
511 .bound_generic_params
513 .any(|gen| matches!(gen.kind, GenericParamKind::Lifetime { .. }))
515 if has_lifetime_parameters {
516 // complex trait bounds like A<'a, 'b>
519 // simple trait bounds like A + B
526 self.score += add_score;
527 self.nest += sub_nest;
529 self.nest -= sub_nest;
531 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
532 NestedVisitorMap::None