@@ -2,14 +2,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
2
2
use clippy_utils:: source:: snippet;
3
3
use clippy_utils:: ty:: is_copy;
4
4
use clippy_utils:: { get_parent_expr, path_to_local} ;
5
- use rustc_hir:: { BindingMode , Expr , ExprKind , Node , PatKind , UnOp } ;
5
+ use rustc_hir:: { BindingMode , Expr , ExprField , ExprKind , Node , PatKind , Path , QPath , UnOp } ;
6
6
use rustc_lint:: { LateContext , LateLintPass } ;
7
7
use rustc_session:: declare_lint_pass;
8
8
9
9
declare_clippy_lint ! {
10
10
/// ### What it does
11
- /// Checks for initialization of a `struct` by copying a base without setting
12
- /// any field.
11
+ /// Checks for initialization of an identical `struct` from another instance
12
+ /// of the type, either by copying a base without setting any field or by
13
+ /// moving all fields individually.
13
14
///
14
15
/// ### Why is this bad?
15
16
/// Readability suffers from unnecessary struct building.
@@ -29,9 +30,14 @@ declare_clippy_lint! {
29
30
/// let b = a;
30
31
/// ```
31
32
///
33
+ /// The struct literal ``S { ..a }`` in the assignment to ``b`` could be replaced
34
+ /// with just ``a``.
35
+ ///
32
36
/// ### Known Problems
33
37
/// Has false positives when the base is a place expression that cannot be
34
38
/// moved out of, see [#10547](https://github.com/rust-lang/rust-clippy/issues/10547).
39
+ ///
40
+ /// Empty structs are ignored by the lint.
35
41
#[ clippy:: version = "1.70.0" ]
36
42
pub UNNECESSARY_STRUCT_INITIALIZATION ,
37
43
nursery,
@@ -41,42 +47,111 @@ declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]);
41
47
42
48
impl LateLintPass < ' _ > for UnnecessaryStruct {
43
49
fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
44
- if let ExprKind :: Struct ( _, & [ ] , Some ( base) ) = expr. kind {
45
- if let Some ( parent) = get_parent_expr ( cx, expr)
46
- && let parent_ty = cx. typeck_results ( ) . expr_ty_adjusted ( parent)
47
- && parent_ty. is_any_ptr ( )
48
- {
49
- if is_copy ( cx, cx. typeck_results ( ) . expr_ty ( expr) ) && path_to_local ( base) . is_some ( ) {
50
- // When the type implements `Copy`, a reference to the new struct works on the
51
- // copy. Using the original would borrow it.
52
- return ;
53
- }
54
-
55
- if parent_ty. is_mutable_ptr ( ) && !is_mutable ( cx, base) {
56
- // The original can be used in a mutable reference context only if it is mutable.
57
- return ;
58
- }
59
- }
50
+ let ExprKind :: Struct ( _, fields, base) = expr. kind else {
51
+ return ;
52
+ } ;
60
53
61
- // TODO: do not propose to replace *XX if XX is not Copy
62
- if let ExprKind :: Unary ( UnOp :: Deref , target) = base. kind
63
- && matches ! ( target. kind, ExprKind :: Path ( ..) )
64
- && !is_copy ( cx, cx. typeck_results ( ) . expr_ty ( expr) )
65
- {
66
- // `*base` cannot be used instead of the struct in the general case if it is not Copy.
67
- return ;
68
- }
54
+ if expr. span . from_expansion ( ) {
55
+ // Prevent lint from hitting inside macro code
56
+ return ;
57
+ }
58
+
59
+ let field_path = same_path_in_all_fields ( cx, expr, fields) ;
60
+
61
+ let sugg = match ( field_path, base) {
62
+ ( Some ( & path) , None ) => {
63
+ // all fields match, no base given
64
+ path. span
65
+ } ,
66
+ ( Some ( path) , Some ( base) ) if base_is_suitable ( cx, expr, base) && path_matches_base ( path, base) => {
67
+ // all fields match, has base: ensure that the path of the base matches
68
+ base. span
69
+ } ,
70
+ ( None , Some ( base) ) if fields. is_empty ( ) && base_is_suitable ( cx, expr, base) => {
71
+ // just the base, no explicit fields
72
+ base. span
73
+ } ,
74
+ _ => return ,
75
+ } ;
76
+
77
+ span_lint_and_sugg (
78
+ cx,
79
+ UNNECESSARY_STRUCT_INITIALIZATION ,
80
+ expr. span ,
81
+ "unnecessary struct building" ,
82
+ "replace with" ,
83
+ snippet ( cx, sugg, ".." ) . into_owned ( ) ,
84
+ rustc_errors:: Applicability :: MachineApplicable ,
85
+ ) ;
86
+ }
87
+ }
88
+
89
+ fn base_is_suitable ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , base : & Expr < ' _ > ) -> bool {
90
+ if !check_references ( cx, expr, base) {
91
+ return false ;
92
+ }
93
+
94
+ // TODO: do not propose to replace *XX if XX is not Copy
95
+ if let ExprKind :: Unary ( UnOp :: Deref , target) = base. kind
96
+ && matches ! ( target. kind, ExprKind :: Path ( ..) )
97
+ && !is_copy ( cx, cx. typeck_results ( ) . expr_ty ( expr) )
98
+ {
99
+ // `*base` cannot be used instead of the struct in the general case if it is not Copy.
100
+ return false ;
101
+ }
102
+ true
103
+ }
104
+
105
+ /// Check whether all fields of a struct assignment match.
106
+ /// Returns a [Path] item that one can obtain a span from for the lint suggestion.
107
+ ///
108
+ /// Conditions that must be satisfied to trigger this variant of the lint:
109
+ ///
110
+ /// - source struct of the assignment must be of same type as the destination
111
+ /// - names of destination struct fields must match the field names of the source
112
+ ///
113
+ /// We don’t check here if all struct fields are assigned as the remainder may
114
+ /// be filled in from a base struct.
115
+ fn same_path_in_all_fields < ' tcx > (
116
+ cx : & LateContext < ' _ > ,
117
+ expr : & Expr < ' _ > ,
118
+ fields : & [ ExprField < ' tcx > ] ,
119
+ ) -> Option < & ' tcx Path < ' tcx > > {
120
+ let ty = cx. typeck_results ( ) . expr_ty ( expr) ;
121
+
122
+ let mut found = None ;
69
123
70
- span_lint_and_sugg (
71
- cx,
72
- UNNECESSARY_STRUCT_INITIALIZATION ,
73
- expr. span ,
74
- "unnecessary struct building" ,
75
- "replace with" ,
76
- snippet ( cx, base. span , ".." ) . into_owned ( ) ,
77
- rustc_errors:: Applicability :: MachineApplicable ,
78
- ) ;
124
+ for f in fields {
125
+ // fields are assigned from expression
126
+ if let ExprKind :: Field ( src_expr, ident) = f. expr . kind
127
+ // expression type matches
128
+ && ty == cx. typeck_results ( ) . expr_ty ( src_expr)
129
+ // field name matches
130
+ && f. ident == ident
131
+ // assigned from a path expression
132
+ && let ExprKind :: Path ( QPath :: Resolved ( None , src_path) ) = src_expr. kind
133
+ {
134
+ let Some ( ( _, p) ) = found else {
135
+ // this is the first field assignment in the list
136
+ found = Some ( ( src_expr, src_path) ) ;
137
+ continue ;
138
+ } ;
139
+
140
+ if p. res == src_path. res {
141
+ // subsequent field assignment with same origin struct as before
142
+ continue ;
143
+ }
79
144
}
145
+ // source of field assignment doesn’t qualify
146
+ return None ;
147
+ }
148
+
149
+ if let Some ( ( src_expr, src_path) ) = found
150
+ && check_references ( cx, expr, src_expr)
151
+ {
152
+ Some ( src_path)
153
+ } else {
154
+ None
80
155
}
81
156
}
82
157
@@ -89,3 +164,43 @@ fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
89
164
true
90
165
}
91
166
}
167
+
168
+ fn check_references ( cx : & LateContext < ' _ > , expr_a : & Expr < ' _ > , expr_b : & Expr < ' _ > ) -> bool {
169
+ if let Some ( parent) = get_parent_expr ( cx, expr_a)
170
+ && let parent_ty = cx. typeck_results ( ) . expr_ty_adjusted ( parent)
171
+ && parent_ty. is_any_ptr ( )
172
+ {
173
+ if is_copy ( cx, cx. typeck_results ( ) . expr_ty ( expr_a) ) && path_to_local ( expr_b) . is_some ( ) {
174
+ // When the type implements `Copy`, a reference to the new struct works on the
175
+ // copy. Using the original would borrow it.
176
+ return false ;
177
+ }
178
+
179
+ if parent_ty. is_mutable_ptr ( ) && !is_mutable ( cx, expr_b) {
180
+ // The original can be used in a mutable reference context only if it is mutable.
181
+ return false ;
182
+ }
183
+ }
184
+
185
+ true
186
+ }
187
+
188
+ /// When some fields are assigned from a base struct and others individually
189
+ /// the lint applies only if the source of the field is the same as the base.
190
+ /// This is enforced here by comparing the path of the base expression;
191
+ /// needless to say the lint only applies if it (or whatever expression it is
192
+ /// a reference of) actually has a path.
193
+ fn path_matches_base ( path : & Path < ' _ > , base : & Expr < ' _ > ) -> bool {
194
+ let base_path = match base. kind {
195
+ ExprKind :: Unary ( UnOp :: Deref , base_expr) => {
196
+ if let ExprKind :: Path ( QPath :: Resolved ( _, base_path) ) = base_expr. kind {
197
+ base_path
198
+ } else {
199
+ return false ;
200
+ }
201
+ } ,
202
+ ExprKind :: Path ( QPath :: Resolved ( _, base_path) ) => base_path,
203
+ _ => return false ,
204
+ } ;
205
+ path. res == base_path. res
206
+ }
0 commit comments