about summary refs log tree commit diff
path: root/clippy_lints/src/unnecessary_struct_initialization.rs
blob: 333ea0c82df6513a67dd6804c898b4e26a63d5da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_copy;
use clippy_utils::{get_parent_expr, path_to_local};
use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;

declare_clippy_lint! {
    /// ### What it does
    /// Checks for initialization of a `struct` by copying a base without setting
    /// any field.
    ///
    /// ### Why is this bad?
    /// Readability suffers from unnecessary struct building.
    ///
    /// ### Example
    /// ```no_run
    /// struct S { s: String }
    ///
    /// let a = S { s: String::from("Hello, world!") };
    /// let b = S { ..a };
    /// ```
    /// Use instead:
    /// ```no_run
    /// struct S { s: String }
    ///
    /// let a = S { s: String::from("Hello, world!") };
    /// let b = a;
    /// ```
    ///
    /// ### Known Problems
    /// Has false positives when the base is a place expression that cannot be
    /// moved out of, see [#10547](https://github.com/rust-lang/rust-clippy/issues/10547).
    #[clippy::version = "1.70.0"]
    pub UNNECESSARY_STRUCT_INITIALIZATION,
    nursery,
    "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_node(hir_id)
    {
        matches!(pat.kind, PatKind::Binding(BindingAnnotation::MUT, ..))
    } else {
        true
    }
}