diff --git a/CHANGELOG.md b/CHANGELOG.md index 1323f973ccfd..1a37ff2e171d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4974,6 +4974,7 @@ Released 2018-09-13 [`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash [`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord [`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints +[`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns [`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast [`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map [`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 9ed6627b7413..dbd1a404150a 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -130,6 +130,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat * [option_option](https://rust-lang.github.io/rust-clippy/master/index.html#option_option) * [linkedlist](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist) * [rc_mutex](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex) +* [unnecessary_box_returns](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns) ### msrv diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 8ca91301472e..89076e5d294f 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -616,6 +616,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::unit_types::UNIT_CMP_INFO, crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO, crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO, + crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO, crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO, crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO, crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c9210bf73f89..117732c6efe9 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -300,6 +300,7 @@ mod uninit_vec; mod unit_return_expecting_ord; mod unit_types; mod unnamed_address; +mod unnecessary_box_returns; mod unnecessary_owned_empty_strings; mod unnecessary_self_imports; mod unnecessary_struct_initialization; @@ -940,6 +941,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute)); store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv()))); store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); + store.register_late_pass(move |_| { + Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new( + avoid_breaking_exported_api, + )) + }); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/unnecessary_box_returns.rs b/clippy_lints/src/unnecessary_box_returns.rs new file mode 100644 index 000000000000..912bcda630b8 --- /dev/null +++ b/clippy_lints/src/unnecessary_box_returns.rs @@ -0,0 +1,120 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{def_id::LocalDefId, 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` where `T` implements `Sized` + /// + /// ### 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` been dropped. + /// + /// ### Example + /// ```rust + /// fn foo() -> Box { + /// Box::new(String::from("Hello, world!")) + /// } + /// ``` + /// Use instead: + /// ```rust + /// 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, +} + +impl_lint_pass!(UnnecessaryBoxReturns => [UNNECESSARY_BOX_RETURNS]); + +impl UnnecessaryBoxReturns { + pub fn new(avoid_breaking_exported_api: bool) -> Self { + Self { + avoid_breaking_exported_api, + } + } + + 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 if T is unsized, so don't lint those + if boxed_ty.is_sized(cx.tcx, cx.param_env) { + 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 implmentation 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); + } +} diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 8ba252425a3d..5384ae01f926 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -249,7 +249,7 @@ define_Conf! { /// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"] /// ``` (arithmetic_side_effects_allowed_unary: rustc_data_structures::fx::FxHashSet = <_>::default()), - /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX. + /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS. /// /// Suppress lints whenever the suggested change would cause breakage for other crates. (avoid_breaking_exported_api: bool = true), diff --git a/tests/ui/unnecessary_box_returns.rs b/tests/ui/unnecessary_box_returns.rs new file mode 100644 index 000000000000..fe60d929759b --- /dev/null +++ b/tests/ui/unnecessary_box_returns.rs @@ -0,0 +1,60 @@ +#![warn(clippy::unnecessary_box_returns)] + +trait Bar { + // lint + fn baz(&self) -> Box; +} + +pub struct Foo {} + +impl Bar for Foo { + // don't lint: this is a problem with the trait, not the implementation + fn baz(&self) -> Box { + Box::new(42) + } +} + +impl Foo { + fn baz(&self) -> Box { + // lint + Box::new(13) + } +} + +// lint +fn bxed_usize() -> Box { + Box::new(5) +} + +// lint +fn _bxed_foo() -> Box { + Box::new(Foo {}) +} + +// don't lint: this is exported +pub fn bxed_foo() -> Box { + Box::new(Foo {}) +} + +// don't lint: str is unsized +fn bxed_str() -> Box { + "Hello, world!".to_string().into_boxed_str() +} + +// don't lint: function contains the word, "box" +fn boxed_usize() -> Box { + Box::new(7) +} + +// don't lint: this has an unspecified return type +fn default() {} + +// don't lint: this doesn't return a Box +fn string() -> String { + String::from("Hello, world") +} + +fn main() { + // don't lint: this is a closure + let a = || -> Box { Box::new(5) }; +} diff --git a/tests/ui/unnecessary_box_returns.stderr b/tests/ui/unnecessary_box_returns.stderr new file mode 100644 index 000000000000..b17512c10a17 --- /dev/null +++ b/tests/ui/unnecessary_box_returns.stderr @@ -0,0 +1,35 @@ +error: boxed return of the sized type `usize` + --> $DIR/unnecessary_box_returns.rs:5:22 + | +LL | fn baz(&self) -> Box; + | ^^^^^^^^^^ help: try: `usize` + | + = help: changing this also requires a change to the return expressions in this function + = note: `-D clippy::unnecessary-box-returns` implied by `-D warnings` + +error: boxed return of the sized type `usize` + --> $DIR/unnecessary_box_returns.rs:18:22 + | +LL | fn baz(&self) -> Box { + | ^^^^^^^^^^ help: try: `usize` + | + = help: changing this also requires a change to the return expressions in this function + +error: boxed return of the sized type `usize` + --> $DIR/unnecessary_box_returns.rs:25:20 + | +LL | fn bxed_usize() -> Box { + | ^^^^^^^^^^ help: try: `usize` + | + = help: changing this also requires a change to the return expressions in this function + +error: boxed return of the sized type `Foo` + --> $DIR/unnecessary_box_returns.rs:30:19 + | +LL | fn _bxed_foo() -> Box { + | ^^^^^^^^ help: try: `Foo` + | + = help: changing this also requires a change to the return expressions in this function + +error: aborting due to 4 previous errors +