]> git.proxmox.com Git - rustc.git/blobdiff - src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / unnecessary_box_returns.rs
diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs b/src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs
new file mode 100644 (file)
index 0000000..912bcda
--- /dev/null
@@ -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<T>` 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<T>` been dropped.
+    ///
+    /// ### Example
+    /// ```rust
+    /// fn foo() -> Box<String> {
+    ///     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<T> 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);
+    }
+}