1
1
use crate :: utils:: { snippet, span_lint_and_note, match_path_ast, paths} ;
2
2
use if_chain:: if_chain;
3
- use rustc_ast:: ast:: { BindingMode , Block , ExprKind , Mutability , PatKind , StmtKind } ;
3
+ use rustc_ast:: ast:: { Block , ExprKind , PatKind , StmtKind } ;
4
4
use rustc_lint:: { EarlyContext , EarlyLintPass } ;
5
5
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
6
6
use rustc_span:: symbol:: Symbol ;
@@ -50,8 +50,8 @@ impl EarlyLintPass for FieldReassignWithDefault {
50
50
if_chain ! {
51
51
// only take `let ...` statements
52
52
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;
55
55
// only when assigning `... = Default::default()`
56
56
if let Some ( ref expr) = local. init;
57
57
if let ExprKind :: Call ( ref fn_expr, _) = & expr. kind;
@@ -68,48 +68,81 @@ impl EarlyLintPass for FieldReassignWithDefault {
68
68
} )
69
69
. collect :: < Vec < _ > > ( ) ;
70
70
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
73
73
for ( stmt_idx, binding_name) in binding_statements_using_default {
74
74
// last statement of block cannot trigger the lint
75
75
if stmt_idx == block. stmts . len ( ) - 1 {
76
76
break ;
77
77
}
78
78
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) ) ;
100
111
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
+ }
109
116
}
110
117
}
111
118
}
112
119
}
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
+ }
113
146
}
114
147
}
115
148
}
0 commit comments