]>
Commit | Line | Data |
---|---|---|
353b0b11 FG |
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 { | |
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 | ||
80 | 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 | } |