• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use clippy_utils::{diagnostics::span_lint_and_sugg, get_parent_expr, path_to_local, source::snippet, ty::is_copy};
2 use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, UnOp};
3 use rustc_lint::{LateContext, LateLintPass};
4 use rustc_session::{declare_lint_pass, declare_tool_lint};
5 
6 declare_clippy_lint! {
7     /// ### What it does
8     /// Checks for initialization of a `struct` by copying a base without setting
9     /// any field.
10     ///
11     /// ### Why is this bad?
12     /// Readability suffers from unnecessary struct building.
13     ///
14     /// ### Example
15     /// ```rust
16     /// struct S { s: String }
17     ///
18     /// let a = S { s: String::from("Hello, world!") };
19     /// let b = S { ..a };
20     /// ```
21     /// Use instead:
22     /// ```rust
23     /// struct S { s: String }
24     ///
25     /// let a = S { s: String::from("Hello, world!") };
26     /// let b = a;
27     /// ```
28     ///
29     /// ### Known Problems
30     /// Has false positives when the base is a place expression that cannot be
31     /// moved out of, see [#10547](https://github.com/rust-lang/rust-clippy/issues/10547).
32     #[clippy::version = "1.70.0"]
33     pub UNNECESSARY_STRUCT_INITIALIZATION,
34     nursery,
35     "struct built from a base that can be written mode concisely"
36 }
37 declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]);
38 
39 impl LateLintPass<'_> for UnnecessaryStruct {
check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>)40     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
41         if let ExprKind::Struct(_, &[], Some(base)) = expr.kind {
42             if let Some(parent) = get_parent_expr(cx, expr) &&
43                 let parent_ty = cx.typeck_results().expr_ty_adjusted(parent) &&
44                 parent_ty.is_any_ptr()
45             {
46                 if is_copy(cx, cx.typeck_results().expr_ty(expr)) && path_to_local(base).is_some() {
47                     // When the type implements `Copy`, a reference to the new struct works on the
48                     // copy. Using the original would borrow it.
49                     return;
50                 }
51 
52                 if parent_ty.is_mutable_ptr() && !is_mutable(cx, base) {
53                     // The original can be used in a mutable reference context only if it is mutable.
54                     return;
55                 }
56             }
57 
58             // TODO: do not propose to replace *XX if XX is not Copy
59             if let ExprKind::Unary(UnOp::Deref, target) = base.kind &&
60                 matches!(target.kind, ExprKind::Path(..)) &&
61                 !is_copy(cx, cx.typeck_results().expr_ty(expr))
62             {
63                 // `*base` cannot be used instead of the struct in the general case if it is not Copy.
64                 return;
65             }
66 
67             span_lint_and_sugg(
68                 cx,
69                 UNNECESSARY_STRUCT_INITIALIZATION,
70                 expr.span,
71                 "unnecessary struct building",
72                 "replace with",
73                 snippet(cx, base.span, "..").into_owned(),
74                 rustc_errors::Applicability::MachineApplicable,
75             );
76         }
77     }
78 }
79 
is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool80 fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
81     if let Some(hir_id) = path_to_local(expr) &&
82         let Node::Pat(pat) = cx.tcx.hir().get(hir_id)
83     {
84         matches!(pat.kind, PatKind::Binding(BindingAnnotation::MUT, ..))
85     } else {
86         true
87     }
88 }
89