@@ -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,112 @@ 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
+
73
+ base. span
74
+ } ,
75
+ _ => return ,
76
+ } ;
77
+
78
+ span_lint_and_sugg (
79
+ cx,
80
+ UNNECESSARY_STRUCT_INITIALIZATION ,
81
+ expr. span ,
82
+ "unnecessary struct building" ,
83
+ "replace with" ,
84
+ snippet ( cx, sugg, ".." ) . into_owned ( ) ,
85
+ rustc_errors:: Applicability :: MachineApplicable ,
86
+ ) ;
87
+ }
88
+ }
89
+
90
+ fn base_is_suitable ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , base : & Expr < ' _ > ) -> bool {
91
+ if !check_references ( cx, expr, base) {
92
+ return false ;
93
+ }
94
+
95
+ // TODO: do not propose to replace *XX if XX is not Copy
96
+ if let ExprKind :: Unary ( UnOp :: Deref , target) = base. kind
97
+ && matches ! ( target. kind, ExprKind :: Path ( ..) )
98
+ && !is_copy ( cx, cx. typeck_results ( ) . expr_ty ( expr) )
99
+ {
100
+ // `*base` cannot be used instead of the struct in the general case if it is not Copy.
101
+ return false ;
102
+ }
103
+ true
104
+ }
105
+
106
+ /// Check whether all fields of a struct assignment match.
107
+ /// Returns a [Path] item that one can obtain a span from for the lint suggestion.
108
+ ///
109
+ /// Conditions that must be satisfied to trigger this variant of the lint:
110
+ ///
111
+ /// - source struct of the assignment must be of same type as the destination
112
+ /// - names of destination struct fields must match the field names of the source
113
+ ///
114
+ /// We don’t check here if all struct fields are assigned as the remainder may
115
+ /// be filled in from a base struct.
116
+ fn same_path_in_all_fields < ' tcx > (
117
+ cx : & LateContext < ' _ > ,
118
+ expr : & Expr < ' _ > ,
119
+ fields : & [ ExprField < ' tcx > ] ,
120
+ ) -> Option < & ' tcx Path < ' tcx > > {
121
+ let ty = cx. typeck_results ( ) . expr_ty ( expr) ;
69
122
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
- ) ;
123
+ let mut found = None ;
124
+
125
+ for f in fields. iter ( ) . filter ( |f| !f. is_shorthand ) {
126
+ // fields are assigned from expression
127
+ if let ExprKind :: Field ( src_expr, ident) = f. expr . kind
128
+ // expression type matches
129
+ && ty == cx. typeck_results ( ) . expr_ty ( src_expr)
130
+ // field name matches
131
+ && f. ident == ident
132
+ // assigned from a path expression
133
+ && let ExprKind :: Path ( QPath :: Resolved ( None , src_path) ) = src_expr. kind
134
+ {
135
+ let Some ( ( _, p) ) = found else {
136
+ // this is the first field assignment in the list
137
+ found = Some ( ( src_expr, src_path) ) ;
138
+ continue ;
139
+ } ;
140
+
141
+ if p. res == src_path. res {
142
+ // subsequent field assignment with same origin struct as before
143
+ continue ;
144
+ }
79
145
}
146
+ // source of field assignment doesn’t qualify
147
+ return None ;
148
+ }
149
+
150
+ if let Some ( ( src_expr, src_path) ) = found
151
+ && check_references ( cx, expr, src_expr)
152
+ {
153
+ Some ( src_path)
154
+ } else {
155
+ None
80
156
}
81
157
}
82
158
@@ -89,3 +165,43 @@ fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
89
165
true
90
166
}
91
167
}
168
+
169
+ fn check_references ( cx : & LateContext < ' _ > , expr_a : & Expr < ' _ > , expr_b : & Expr < ' _ > ) -> bool {
170
+ if let Some ( parent) = get_parent_expr ( cx, expr_a)
171
+ && let parent_ty = cx. typeck_results ( ) . expr_ty_adjusted ( parent)
172
+ && parent_ty. is_any_ptr ( )
173
+ {
174
+ if is_copy ( cx, cx. typeck_results ( ) . expr_ty ( expr_a) ) && path_to_local ( expr_b) . is_some ( ) {
175
+ // When the type implements `Copy`, a reference to the new struct works on the
176
+ // copy. Using the original would borrow it.
177
+ return false ;
178
+ }
179
+
180
+ if parent_ty. is_mutable_ptr ( ) && !is_mutable ( cx, expr_b) {
181
+ // The original can be used in a mutable reference context only if it is mutable.
182
+ return false ;
183
+ }
184
+ }
185
+
186
+ true
187
+ }
188
+
189
+ /// When some fields are assigned from a base struct and others individually
190
+ /// the lint applies only if the source of the field is the same as the base.
191
+ /// This is enforced here by comparing the path of the base expression;
192
+ /// needless to say the link only applies if it (or whatever expression it is
193
+ /// a reference of) actually has a path.
194
+ fn path_matches_base ( path : & Path < ' _ > , base : & Expr < ' _ > ) -> bool {
195
+ let base_path = match base. kind {
196
+ ExprKind :: Unary ( UnOp :: Deref , base_expr) => {
197
+ if let ExprKind :: Path ( QPath :: Resolved ( _, base_path) ) = base_expr. kind {
198
+ base_path
199
+ } else {
200
+ return false ;
201
+ }
202
+ } ,
203
+ ExprKind :: Path ( QPath :: Resolved ( _, base_path) ) => base_path,
204
+ _ => return false ,
205
+ } ;
206
+ path. res == base_path. res
207
+ }
0 commit comments