@@ -6,7 +6,7 @@ use rustc_errors::Applicability;
66use rustc_hir:: def:: Res ;
77use rustc_hir:: { Block , Expr , ExprKind , PatKind , QPath , Stmt , StmtKind } ;
88use rustc_lint:: { LateContext , LateLintPass } ;
9- use rustc_middle:: ty:: { self , Adt , Ty } ;
9+ use rustc_middle:: ty;
1010use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
1111use rustc_span:: symbol:: { Ident , Symbol } ;
1212use rustc_span:: Span ;
@@ -103,34 +103,50 @@ impl LateLintPass<'_> for Default {
103103 }
104104
105105 fn check_block < ' tcx > ( & mut self , cx : & LateContext < ' tcx > , block : & Block < ' tcx > ) {
106- // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
107- // `default` method of the `Default` trait, and store statement index in current block being
108- // checked and the name of the bound variable
109- let binding_statements_using_default = enumerate_bindings_using_default ( cx, block) ;
110-
111106 // start from the `let mut _ = _::default();` and look at all the following
112107 // statements, see if they re-assign the fields of the binding
113- for ( stmt_idx, binding_name, binding_type, span) in binding_statements_using_default {
114- // the last statement of a block cannot trigger the lint
115- if stmt_idx == block. stmts . len ( ) - 1 {
116- break ;
117- }
108+ let stmts_head = match block. stmts {
109+ // Skip the last statement since there cannot possibly be any following statements that re-assign fields.
110+ [ head @ .., _] if !head. is_empty ( ) => head,
111+ _ => return ,
112+ } ;
113+ for ( stmt_idx, stmt) in stmts_head. iter ( ) . enumerate ( ) {
114+ // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
115+ // `default` method of the `Default` trait, and store statement index in current block being
116+ // checked and the name of the bound variable
117+ let ( local, variant, binding_name, binding_type, span) = if_chain ! {
118+ // only take `let ...` statements
119+ if let StmtKind :: Local ( local) = stmt. kind;
120+ if let Some ( expr) = local. init;
121+ // only take bindings to identifiers
122+ if let PatKind :: Binding ( _, binding_id, ident, _) = local. pat. kind;
123+ // only when assigning `... = Default::default()`
124+ if is_expr_default( expr, cx) ;
125+ let binding_type = cx. typeck_results( ) . node_type( binding_id) ;
126+ if let Some ( adt) = binding_type. ty_adt_def( ) ;
127+ if adt. is_struct( ) ;
128+ let variant = adt. non_enum_variant( ) ;
129+ if adt. did. is_local( ) || !variant. is_field_list_non_exhaustive( ) ;
130+ let module_did = cx. tcx. parent_module( stmt. hir_id) . to_def_id( ) ;
131+ if variant
132+ . fields
133+ . iter( )
134+ . all( |field| field. vis. is_accessible_from( module_did, cx. tcx) ) ;
135+ then {
136+ ( local, variant, ident. name, binding_type, expr. span)
137+ } else {
138+ continue ;
139+ }
140+ } ;
118141
119142 // find all "later statement"'s where the fields of the binding set as
120143 // Default::default() get reassigned, unless the reassignment refers to the original binding
121144 let mut first_assign = None ;
122145 let mut assigned_fields = Vec :: new ( ) ;
123146 let mut cancel_lint = false ;
124147 for consecutive_statement in & block. stmts [ stmt_idx + 1 ..] {
125- // interrupt if the statement is a let binding (`Local`) that shadows the original
126- // binding
127- if stmt_shadows_binding ( consecutive_statement, binding_name) {
128- break ;
129- }
130148 // find out if and which field was set by this `consecutive_statement`
131- else if let Some ( ( field_ident, assign_rhs) ) =
132- field_reassigned_by_stmt ( consecutive_statement, binding_name)
133- {
149+ if let Some ( ( field_ident, assign_rhs) ) = field_reassigned_by_stmt ( consecutive_statement, binding_name) {
134150 // interrupt and cancel lint if assign_rhs references the original binding
135151 if contains_name ( binding_name, assign_rhs) {
136152 cancel_lint = true ;
@@ -152,7 +168,7 @@ impl LateLintPass<'_> for Default {
152168 first_assign = Some ( consecutive_statement) ;
153169 }
154170 }
155- // interrupt also if no field was assigned, since we only want to look at consecutive statements
171+ // interrupt if no field was assigned, since we only want to look at consecutive statements
156172 else {
157173 break ;
158174 }
@@ -161,55 +177,45 @@ impl LateLintPass<'_> for Default {
161177 // if there are incorrectly assigned fields, do a span_lint_and_note to suggest
162178 // construction using `Ty { fields, ..Default::default() }`
163179 if !assigned_fields. is_empty ( ) && !cancel_lint {
164- // take the original assignment as span
165- let stmt = & block. stmts [ stmt_idx] ;
166-
167- if let StmtKind :: Local ( preceding_local) = & stmt. kind {
168- // filter out fields like `= Default::default()`, because the FRU already covers them
169- let assigned_fields = assigned_fields
170- . into_iter ( )
171- . filter ( |( _, rhs) | !is_expr_default ( rhs, cx) )
172- . collect :: < Vec < ( Symbol , & Expr < ' _ > ) > > ( ) ;
180+ // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
181+ let ext_with_default = !variant
182+ . fields
183+ . iter ( )
184+ . all ( |field| assigned_fields. iter ( ) . any ( |( a, _) | a == & field. ident . name ) ) ;
173185
174- // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
175- let ext_with_default = !fields_of_type ( binding_type)
176- . iter ( )
177- . all ( |field| assigned_fields. iter ( ) . any ( |( a, _) | a == & field. name ) ) ;
186+ let field_list = assigned_fields
187+ . into_iter ( )
188+ . map ( |( field, rhs) | {
189+ // extract and store the assigned value for help message
190+ let value_snippet = snippet ( cx, rhs. span , ".." ) ;
191+ format ! ( "{}: {}" , field, value_snippet)
192+ } )
193+ . collect :: < Vec < String > > ( )
194+ . join ( ", " ) ;
178195
179- let field_list = assigned_fields
180- . into_iter ( )
181- . map ( |( field, rhs) | {
182- // extract and store the assigned value for help message
183- let value_snippet = snippet ( cx, rhs. span , ".." ) ;
184- format ! ( "{}: {}" , field, value_snippet)
185- } )
186- . collect :: < Vec < String > > ( )
187- . join ( ", " ) ;
188-
189- let sugg = if ext_with_default {
190- if field_list. is_empty ( ) {
191- format ! ( "{}::default()" , binding_type)
192- } else {
193- format ! ( "{} {{ {}, ..Default::default() }}" , binding_type, field_list)
194- }
196+ let sugg = if ext_with_default {
197+ if field_list. is_empty ( ) {
198+ format ! ( "{}::default()" , binding_type)
195199 } else {
196- format ! ( "{} {{ {} }}" , binding_type, field_list)
197- } ;
200+ format ! ( "{} {{ {}, ..Default::default() }}" , binding_type, field_list)
201+ }
202+ } else {
203+ format ! ( "{} {{ {} }}" , binding_type, field_list)
204+ } ;
198205
199- // span lint once per statement that binds default
200- span_lint_and_note (
201- cx,
202- FIELD_REASSIGN_WITH_DEFAULT ,
203- first_assign. unwrap ( ) . span ,
204- "field assignment outside of initializer for an instance created with Default::default()" ,
205- Some ( preceding_local. span ) ,
206- & format ! (
207- "consider initializing the variable with `{}` and removing relevant reassignments" ,
208- sugg
209- ) ,
210- ) ;
211- self . reassigned_linted . insert ( span) ;
212- }
206+ // span lint once per statement that binds default
207+ span_lint_and_note (
208+ cx,
209+ FIELD_REASSIGN_WITH_DEFAULT ,
210+ first_assign. unwrap ( ) . span ,
211+ "field assignment outside of initializer for an instance created with Default::default()" ,
212+ Some ( local. span ) ,
213+ & format ! (
214+ "consider initializing the variable with `{}` and removing relevant reassignments" ,
215+ sugg
216+ ) ,
217+ ) ;
218+ self . reassigned_linted . insert ( span) ;
213219 }
214220 }
215221 }
@@ -230,47 +236,6 @@ fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool
230236 }
231237}
232238
233- /// Returns the block indices, identifiers and types of bindings set as `Default::default()`, except
234- /// for when the pattern type is a tuple.
235- fn enumerate_bindings_using_default < ' tcx > (
236- cx : & LateContext < ' tcx > ,
237- block : & Block < ' tcx > ,
238- ) -> Vec < ( usize , Symbol , Ty < ' tcx > , Span ) > {
239- block
240- . stmts
241- . iter ( )
242- . enumerate ( )
243- . filter_map ( |( idx, stmt) | {
244- if_chain ! {
245- // only take `let ...` statements
246- if let StmtKind :: Local ( ref local) = stmt. kind;
247- // only take bindings to identifiers
248- if let PatKind :: Binding ( _, _, ident, _) = local. pat. kind;
249- // that are not tuples
250- let ty = cx. typeck_results( ) . pat_ty( local. pat) ;
251- if !matches!( ty. kind( ) , ty:: Tuple ( _) ) ;
252- // only when assigning `... = Default::default()`
253- if let Some ( ref expr) = local. init;
254- if is_expr_default( expr, cx) ;
255- then {
256- Some ( ( idx, ident. name, ty, expr. span) )
257- } else {
258- None
259- }
260- }
261- } )
262- . collect ( )
263- }
264-
265- fn stmt_shadows_binding ( this : & Stmt < ' _ > , shadowed : Symbol ) -> bool {
266- if let StmtKind :: Local ( local) = & this. kind {
267- if let PatKind :: Binding ( _, _, ident, _) = local. pat . kind {
268- return ident. name == shadowed;
269- }
270- }
271- false
272- }
273-
274239/// Returns the reassigned field and the assigning expression (right-hand side of assign).
275240fn field_reassigned_by_stmt < ' tcx > ( this : & Stmt < ' tcx > , binding_name : Symbol ) -> Option < ( Ident , & ' tcx Expr < ' tcx > ) > {
276241 if_chain ! {
@@ -290,14 +255,3 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
290255 }
291256 }
292257}
293-
294- /// Returns the vec of fields for a struct and an empty vec for non-struct ADTs.
295- fn fields_of_type ( ty : Ty < ' _ > ) -> Vec < Ident > {
296- if let Adt ( adt, _) = ty. kind ( ) {
297- if adt. is_struct ( ) {
298- let variant = & adt. non_enum_variant ( ) ;
299- return variant. fields . iter ( ) . map ( |f| f. ident ) . collect ( ) ;
300- }
301- }
302- vec ! [ ]
303- }
0 commit comments