| use clippy_utils::diagnostics::span_lint_and_then; |
| use clippy_utils::ty::approx_ty_size; |
| use rustc_errors::Applicability; |
| use rustc_hir::def_id::LocalDefId; |
| use rustc_hir::{FnDecl, FnRetTy, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| use rustc_span::Symbol; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// |
| /// Checks for a return type containing a `Box<T>` where `T` implements `Sized` |
| /// |
| /// The lint ignores `Box<T>` where `T` is larger than `unnecessary_box_size`, |
| /// as returning a large `T` directly may be detrimental to performance. |
| /// |
| /// ### Why is this bad? |
| /// |
| /// It's better to just return `T` in these cases. The caller may not need |
| /// the value to be boxed, and it's expensive to free the memory once the |
| /// `Box<T>` been dropped. |
| /// |
| /// ### Example |
| /// ```no_run |
| /// fn foo() -> Box<String> { |
| /// Box::new(String::from("Hello, world!")) |
| /// } |
| /// ``` |
| /// Use instead: |
| /// ```no_run |
| /// fn foo() -> String { |
| /// String::from("Hello, world!") |
| /// } |
| /// ``` |
| #[clippy::version = "1.70.0"] |
| pub UNNECESSARY_BOX_RETURNS, |
| pedantic, |
| "Needlessly returning a Box" |
| } |
| |
| pub struct UnnecessaryBoxReturns { |
| avoid_breaking_exported_api: bool, |
| maximum_size: u64, |
| } |
| |
| impl_lint_pass!(UnnecessaryBoxReturns => [UNNECESSARY_BOX_RETURNS]); |
| |
| impl UnnecessaryBoxReturns { |
| pub fn new(avoid_breaking_exported_api: bool, maximum_size: u64) -> Self { |
| Self { |
| avoid_breaking_exported_api, |
| maximum_size, |
| } |
| } |
| |
| fn check_fn_item(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) { |
| // we don't want to tell someone to break an exported function if they ask us not to |
| if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { |
| return; |
| } |
| |
| // functions which contain the word "box" are exempt from this lint |
| if name.as_str().contains("box") { |
| return; |
| } |
| |
| let FnRetTy::Return(return_ty_hir) = &decl.output else { |
| return; |
| }; |
| |
| let return_ty = cx |
| .tcx |
| .erase_late_bound_regions(cx.tcx.fn_sig(def_id).skip_binder()) |
| .output(); |
| |
| if !return_ty.is_box() { |
| return; |
| } |
| |
| let boxed_ty = return_ty.boxed_ty(); |
| |
| // It's sometimes useful to return Box<T> if T is unsized, so don't lint those. |
| // Also, don't lint if we know that T is very large, in which case returning |
| // a Box<T> may be beneficial. |
| if boxed_ty.is_sized(cx.tcx, cx.param_env) && approx_ty_size(cx, boxed_ty) <= self.maximum_size { |
| span_lint_and_then( |
| cx, |
| UNNECESSARY_BOX_RETURNS, |
| return_ty_hir.span, |
| format!("boxed return of the sized type `{boxed_ty}`").as_str(), |
| |diagnostic| { |
| diagnostic.span_suggestion( |
| return_ty_hir.span, |
| "try", |
| boxed_ty.to_string(), |
| // the return value and function callers also needs to |
| // be changed, so this can't be MachineApplicable |
| Applicability::Unspecified, |
| ); |
| diagnostic.help("changing this also requires a change to the return expressions in this function"); |
| }, |
| ); |
| } |
| } |
| } |
| |
| impl LateLintPass<'_> for UnnecessaryBoxReturns { |
| fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { |
| let TraitItemKind::Fn(signature, _) = &item.kind else { |
| return; |
| }; |
| self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name); |
| } |
| |
| fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'_>) { |
| // Ignore implementations of traits, because the lint should be on the |
| // trait, not on the implementation of it. |
| let Node::Item(parent) = cx.tcx.hir().get_parent(item.hir_id()) else { |
| return; |
| }; |
| let ItemKind::Impl(parent) = parent.kind else { return }; |
| if parent.of_trait.is_some() { |
| return; |
| } |
| |
| let ImplItemKind::Fn(signature, ..) = &item.kind else { |
| return; |
| }; |
| self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name); |
| } |
| |
| fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { |
| let ItemKind::Fn(signature, ..) = &item.kind else { |
| return; |
| }; |
| self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name); |
| } |
| } |