@@ -2,14 +2,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
22use clippy_utils:: source:: snippet;
33use clippy_utils:: ty:: is_copy;
44use 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 } ;
66use rustc_lint:: { LateContext , LateLintPass } ;
77use rustc_session:: declare_lint_pass;
88
99declare_clippy_lint ! {
1010 /// ### 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.
1314 ///
1415 /// ### Why is this bad?
1516 /// Readability suffers from unnecessary struct building.
@@ -29,9 +30,14 @@ declare_clippy_lint! {
2930 /// let b = a;
3031 /// ```
3132 ///
33+ /// The struct literal ``S { ..a }`` in the assignment to ``b`` could be replaced
34+ /// with just ``a``.
35+ ///
3236 /// ### Known Problems
3337 /// Has false positives when the base is a place expression that cannot be
3438 /// moved out of, see [#10547](https://github.com/rust-lang/rust-clippy/issues/10547).
39+ ///
40+ /// Empty structs are ignored by the lint.
3541 #[ clippy:: version = "1.70.0" ]
3642 pub UNNECESSARY_STRUCT_INITIALIZATION ,
3743 nursery,
@@ -41,42 +47,111 @@ declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]);
4147
4248impl LateLintPass < ' _ > for UnnecessaryStruct {
4349 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+ } ;
6053
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 ;
69123
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+ }
79144 }
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
80155 }
81156}
82157
@@ -89,3 +164,43 @@ fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
89164 true
90165 }
91166}
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