about summary refs log tree commit diff
path: root/clippy_lints/src/unnecessary_struct_initialization.rs
diff options
context:
space:
mode:
authorPhilipp Krones <hello@philkrones.com>2023-03-24 14:04:35 +0100
committerPhilipp Krones <hello@philkrones.com>2023-03-24 14:26:19 +0100
commit8df896c076fd993bad58878ee8a6ed29d8e586ba (patch)
treec0edd67687a954bb38d66e77dae3dbd0db3909c5 /clippy_lints/src/unnecessary_struct_initialization.rs
parent58eb9964cc627470cdd9fdcdef872a45615227fe (diff)
downloadrust-8df896c076fd993bad58878ee8a6ed29d8e586ba.tar.gz
rust-8df896c076fd993bad58878ee8a6ed29d8e586ba.zip
Merge commit 'd5e2a7aca55ed49fc943b7a07a8eba05ab5a0079' into clippyup
Diffstat (limited to 'clippy_lints/src/unnecessary_struct_initialization.rs')
-rw-r--r--clippy_lints/src/unnecessary_struct_initialization.rs84
1 files changed, 84 insertions, 0 deletions
diff --git a/clippy_lints/src/unnecessary_struct_initialization.rs b/clippy_lints/src/unnecessary_struct_initialization.rs
new file mode 100644
index 00000000000..af0b4b1592f
--- /dev/null
+++ b/clippy_lints/src/unnecessary_struct_initialization.rs
@@ -0,0 +1,84 @@
+use clippy_utils::{diagnostics::span_lint_and_sugg, get_parent_expr, path_to_local, source::snippet, ty::is_copy};
+use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for initialization of a `struct` by copying a base without setting
+    /// any field.
+    ///
+    /// ### Why is this bad?
+    /// Readibility suffers from unnecessary struct building.
+    ///
+    /// ### Example
+    /// ```rust
+    /// struct S { s: String }
+    ///
+    /// let a = S { s: String::from("Hello, world!") };
+    /// let b = S { ..a };
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// struct S { s: String }
+    ///
+    /// let a = S { s: String::from("Hello, world!") };
+    /// let b = a;
+    /// ```
+    #[clippy::version = "1.70.0"]
+    pub UNNECESSARY_STRUCT_INITIALIZATION,
+    complexity,
+    "struct built from a base that can be written mode concisely"
+}
+declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]);
+
+impl LateLintPass<'_> for UnnecessaryStruct {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+        if let ExprKind::Struct(_, &[], Some(base)) = expr.kind {
+            if let Some(parent) = get_parent_expr(cx, expr) &&
+                let parent_ty = cx.typeck_results().expr_ty_adjusted(parent) &&
+                parent_ty.is_any_ptr()
+            {
+                if is_copy(cx, cx.typeck_results().expr_ty(expr)) && path_to_local(base).is_some() {
+                    // When the type implements `Copy`, a reference to the new struct works on the
+                    // copy. Using the original would borrow it.
+                    return;
+                }
+
+                if parent_ty.is_mutable_ptr() && !is_mutable(cx, base) {
+                    // The original can be used in a mutable reference context only if it is mutable.
+                    return;
+                }
+            }
+
+            // TODO: do not propose to replace *XX if XX is not Copy
+            if let ExprKind::Unary(UnOp::Deref, target) = base.kind &&
+                matches!(target.kind, ExprKind::Path(..)) &&
+                !is_copy(cx, cx.typeck_results().expr_ty(expr))
+            {
+                // `*base` cannot be used instead of the struct in the general case if it is not Copy.
+                return;
+            }
+
+            span_lint_and_sugg(
+                cx,
+                UNNECESSARY_STRUCT_INITIALIZATION,
+                expr.span,
+                "unnecessary struct building",
+                "replace with",
+                snippet(cx, base.span, "..").into_owned(),
+                rustc_errors::Applicability::MachineApplicable,
+            );
+        }
+    }
+}
+
+fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    if let Some(hir_id) = path_to_local(expr) &&
+        let Node::Pat(pat) = cx.tcx.hir().get(hir_id)
+    {
+        matches!(pat.kind, PatKind::Binding(BindingAnnotation::MUT, ..))
+    } else {
+        true
+    }
+}