11use crate :: utils:: { snippet, span_lint_and_note, match_path_ast, paths} ;
22use if_chain:: if_chain;
3- use rustc_ast:: ast:: { BindingMode , Block , ExprKind , Mutability , PatKind , StmtKind } ;
3+ use rustc_ast:: ast:: { Block , ExprKind , PatKind , StmtKind } ;
44use rustc_lint:: { EarlyContext , EarlyLintPass } ;
55use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
66use rustc_span:: symbol:: Symbol ;
@@ -50,8 +50,8 @@ impl EarlyLintPass for FieldReassignWithDefault {
5050 if_chain ! {
5151 // only take `let ...` statements
5252 if let StmtKind :: Local ( ref local) = stmt. kind;
53- // only take `... mut binding ...`
54- if let PatKind :: Ident ( BindingMode :: ByValue ( Mutability :: Mut ) , binding, _) = local. pat. kind;
53+ // only take bindings to identifiers
54+ if let PatKind :: Ident ( _ , binding, _) = local. pat. kind;
5555 // only when assigning `... = Default::default()`
5656 if let Some ( ref expr) = local. init;
5757 if let ExprKind :: Call ( ref fn_expr, _) = & expr. kind;
@@ -68,48 +68,81 @@ impl EarlyLintPass for FieldReassignWithDefault {
6868 } )
6969 . collect :: < Vec < _ > > ( ) ;
7070
71- // look at all the following statements for the binding statements and see if they reassign
72- // the fields of the binding
71+ // start from the `let mut binding = Default::default();` and look at all the following
72+ // statements, see if they re-assign the fields of the binding
7373 for ( stmt_idx, binding_name) in binding_statements_using_default {
7474 // last statement of block cannot trigger the lint
7575 if stmt_idx == block. stmts . len ( ) - 1 {
7676 break ;
7777 }
7878
79- // find "later statement"'s where the fields of the binding set by Default::default()
80- // get reassigned
81- let later_stmt = & block. stmts [ stmt_idx + 1 ] ;
82- if_chain ! {
83- // only take assignments
84- if let StmtKind :: Semi ( ref later_expr) = later_stmt. kind;
85- if let ExprKind :: Assign ( ref later_lhs, ref later_rhs, _) = later_expr. kind;
86- // only take assignments to fields where the field refers to the same binding as the previous statement
87- if let ExprKind :: Field ( ref binding_name_candidate, later_field_ident) = later_lhs. kind;
88- if let ExprKind :: Path ( _, ref path ) = binding_name_candidate. kind;
89- if let Some ( second_binding_name) = path. segments. last( ) ;
90- if second_binding_name. ident. name == binding_name;
91- then {
92- // take the preceding statement as span
93- let stmt = & block. stmts[ stmt_idx] ;
94- if let StmtKind :: Local ( preceding_local) = & stmt. kind {
95- // reorganize the latter assigment statement into `field` and `value` for the lint
96- let field = later_field_ident. name. as_str( ) ;
97- let value_snippet = snippet( cx, later_rhs. span, ".." ) ;
98- if let Some ( ty) = & preceding_local. ty {
99- let ty_snippet = snippet( cx, ty. span, "_" ) ;
79+ // find all "later statement"'s where the fields of the binding set as
80+ // Default::default() get reassigned
81+ let mut first_assign = None ;
82+ let mut assigned_fields = vec ! [ ] ;
83+ for post_defassign_stmt in & block. stmts [ stmt_idx + 1 ..] {
84+ // interrupt if the statement is a let binding (`Local`) that shadows the original
85+ // binding
86+ if let StmtKind :: Local ( local) = & post_defassign_stmt. kind {
87+ if let PatKind :: Ident ( _, id, _) = local. pat . kind {
88+ if id. name == binding_name {
89+ break ;
90+ }
91+ }
92+ }
93+ // statement kinds other than `StmtKind::Local` are valid, because they cannot
94+ // shadow a binding
95+ else {
96+ if_chain ! {
97+ // only take assignments
98+ if let StmtKind :: Semi ( ref later_expr) = post_defassign_stmt. kind;
99+ if let ExprKind :: Assign ( ref assign_lhs, ref assign_rhs, _) = later_expr. kind;
100+ // only take assignments to fields where the left-hand side field is a field of
101+ // the same binding as the previous statement
102+ if let ExprKind :: Field ( ref binding, later_field_ident) = assign_lhs. kind;
103+ if let ExprKind :: Path ( _, ref path ) = binding. kind;
104+ if let Some ( second_binding_name) = path. segments. last( ) ;
105+ if second_binding_name. ident. name == binding_name;
106+ then {
107+ // extract and store the name of the field and the assigned value for help message
108+ let field = later_field_ident. name;
109+ let value_snippet = snippet( cx, assign_rhs. span, ".." ) ;
110+ assigned_fields. push( ( field, value_snippet) ) ;
100111
101- span_lint_and_note(
102- cx,
103- FIELD_REASSIGN_WITH_DEFAULT ,
104- later_stmt. span,
105- "field assignment outside of initializer for an instance created with Default::default()" ,
106- Some ( preceding_local. span) ,
107- & format!( "consider initializing the variable immutably with `{} {{ {}: {}, ..Default::default() }}`" , ty_snippet, field, value_snippet) ,
108- ) ;
112+ // also set first instance of error for help message
113+ if first_assign. is_none( ) {
114+ first_assign = Some ( post_defassign_stmt) ;
115+ }
109116 }
110117 }
111118 }
112119 }
120+
121+ // if there are incorrectly assigned fields, do a span_lint_and_note to suggest
122+ // immutable construction using `Ty { fields, ..Default::default() }`
123+ if !assigned_fields. is_empty ( ) {
124+ // take the preceding statement as span
125+ let stmt = & block. stmts [ stmt_idx] ;
126+ if let StmtKind :: Local ( preceding_local) = & stmt. kind {
127+ if let Some ( ty) = & preceding_local. ty {
128+ let ty_snippet = snippet ( cx, ty. span , "_" ) ;
129+
130+ let field_list = assigned_fields. into_iter ( ) . map ( |( field, value) | format ! ( "{}: {}" , field, value) ) . collect :: < Vec < String > > ( ) . join ( ", " ) ;
131+
132+ let sugg = format ! ( "{} {{ {}, ..Default::default() }}" , ty_snippet, field_list) ;
133+
134+ // span lint once per statement that binds default
135+ span_lint_and_note (
136+ cx,
137+ FIELD_REASSIGN_WITH_DEFAULT ,
138+ first_assign. unwrap_or_else ( || unreachable ! ( ) ) . span ,
139+ "field assignment outside of initializer for an instance created with Default::default()" ,
140+ Some ( preceding_local. span ) ,
141+ & format ! ( "consider initializing the variable immutably with `{}`" , sugg) ,
142+ ) ;
143+ }
144+ }
145+ }
113146 }
114147 }
115148}
0 commit comments