11use crate :: utils:: { match_def_path, paths, qpath_res, snippet, span_lint_and_note} ;
22use if_chain:: if_chain;
33use rustc_hir:: def:: Res ;
4- use rustc_hir:: { Block , ExprKind , PatKind , QPath , StmtKind } ;
5- use rustc_span:: symbol:: Symbol ;
4+ use rustc_hir:: { Block , Expr , ExprKind , PatKind , QPath , Stmt , StmtKind } ;
5+ use rustc_span:: symbol:: { Ident , Symbol } ;
66
77use rustc_lint:: { LateContext , LateLintPass } ;
88use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
@@ -11,7 +11,7 @@ declare_clippy_lint! {
1111 /// **What it does:** Checks for immediate reassignment of fields initialized
1212 /// with Default::default().
1313 ///
14- /// **Why is this bad?**It's more idiomatic to use [functional update syntax](https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax).
14+ /// **Why is this bad?**It's more idiomatic to use the [functional update syntax](https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax).
1515 ///
1616 /// **Known problems:** None.
1717 ///
@@ -39,36 +39,13 @@ declare_lint_pass!(FieldReassignWithDefault => [FIELD_REASSIGN_WITH_DEFAULT]);
3939
4040impl LateLintPass < ' _ > for FieldReassignWithDefault {
4141 fn check_block ( & mut self , cx : & LateContext < ' _ > , block : & Block < ' _ > ) {
42- // store statement index and name of binding for all statements like
43- // `let mut binding = Default::default();`
44- let binding_statements_using_default: Vec < ( usize , Symbol ) > = block
45- . stmts
46- . iter ( )
47- . enumerate ( )
48- . filter_map ( |( idx, stmt) | {
49- if_chain ! {
50- // only take `let ...` statements
51- if let StmtKind :: Local ( ref local) = stmt. kind;
52- // only take bindings to identifiers
53- if let PatKind :: Binding ( _, _, ident, _) = local. pat. kind;
54- // only when assigning `... = Default::default()`
55- if let Some ( ref expr) = local. init;
56- if let ExprKind :: Call ( ref fn_expr, _) = & expr. kind;
57- if let ExprKind :: Path ( qpath) = & fn_expr. kind;
58- if let Res :: Def ( _, def_id) = qpath_res( cx, qpath, fn_expr. hir_id) ;
59- // right hand side of assignment is `Default::default`
60- if match_def_path( cx, def_id, & paths:: DEFAULT_TRAIT_METHOD ) ;
61- then {
62- Some ( ( idx, ident. name) )
63- }
64- else {
65- None
66- }
67- }
68- } )
69- . collect :: < Vec < _ > > ( ) ;
7042
71- // start from the `let mut binding = Default::default();` and look at all the following
43+ // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
44+ // `default` method of the `Default` trait. and store statement index in current block being
45+ // checked and the name of the bound variable
46+ let binding_statements_using_default: Vec < ( usize , Symbol ) > = enumerate_bindings_using_default ( cx, block) ;
47+
48+ // start from the `let mut _ = _::default();` and look at all the following
7249 // statements, see if they re-assign the fields of the binding
7350 for ( stmt_idx, binding_name) in binding_statements_using_default {
7451 // last statement of block cannot trigger the lint
@@ -80,40 +57,24 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
8057 // Default::default() get reassigned
8158 let mut first_assign = None ;
8259 let mut assigned_fields = vec ! [ ] ;
83- for post_defassign_stmt in & block. stmts [ stmt_idx + 1 ..] {
60+ for consequtive_statement in & block. stmts [ stmt_idx + 1 ..] {
8461 // interrupt if the statement is a let binding (`Local`) that shadows the original
8562 // binding
86- if let StmtKind :: Local ( local) = & post_defassign_stmt. kind {
87- if let PatKind :: Binding ( _, _, ident, _) = local. pat . kind {
88- if ident. name == binding_name {
89- break ;
90- }
91- }
63+ if stmt_shadows_binding ( consequtive_statement, & binding_name) {
64+ break ;
9265 }
9366 // statement kinds other than `StmtKind::Local` are valid, because they cannot
9467 // shadow a binding
9568 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 qpath) = binding. kind;
104- if let QPath :: Resolved ( _, path) = qpath;
105- if let Some ( second_binding_name) = path. segments. last( ) ;
106- if second_binding_name. ident. name == binding_name;
107- then {
108- // extract and store the name of the field and the assigned value for help message
109- let field = later_field_ident. name;
110- let value_snippet = snippet( cx, assign_rhs. span, ".." ) ;
111- assigned_fields. push( ( field, value_snippet) ) ;
69+ // find out if and which field was set by this `consequtive_statement`
70+ if let Some ( ( field_ident, assign_rhs) ) = field_reassigned_by_stmt ( consequtive_statement, & binding_name) {
71+ // extract and store the assigned value for help message
72+ let value_snippet = snippet ( cx, assign_rhs. span , ".." ) ;
73+ assigned_fields. push ( ( field_ident. name , value_snippet) ) ;
11274
113- // also set first instance of error for help message
114- if first_assign. is_none( ) {
115- first_assign = Some ( post_defassign_stmt) ;
116- }
75+ // also set first instance of error for help message
76+ if first_assign. is_none ( ) {
77+ first_assign = Some ( consequtive_statement) ;
11778 }
11879 }
11980 }
@@ -122,7 +83,7 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
12283 // if there are incorrectly assigned fields, do a span_lint_and_note to suggest
12384 // immutable construction using `Ty { fields, ..Default::default() }`
12485 if !assigned_fields. is_empty ( ) {
125- // take the preceding statement as span
86+ // take the original assignment as span
12687 let stmt = & block. stmts [ stmt_idx] ;
12788 if let StmtKind :: Local ( preceding_local) = & stmt. kind {
12889 if let Some ( ty) = & preceding_local. ty {
@@ -151,3 +112,61 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
151112 }
152113 }
153114}
115+
116+ fn enumerate_bindings_using_default ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) -> Vec < ( usize , Symbol ) > {
117+ block
118+ . stmts
119+ . iter ( )
120+ . enumerate ( )
121+ . filter_map ( |( idx, stmt) | {
122+ if_chain ! {
123+ // only take `let ...` statements
124+ if let StmtKind :: Local ( ref local) = stmt. kind;
125+ // only take bindings to identifiers
126+ if let PatKind :: Binding ( _, _, ident, _) = local. pat. kind;
127+ // only when assigning `... = Default::default()`
128+ if let Some ( ref expr) = local. init;
129+ if let ExprKind :: Call ( ref fn_expr, _) = & expr. kind;
130+ if let ExprKind :: Path ( qpath) = & fn_expr. kind;
131+ if let Res :: Def ( _, def_id) = qpath_res( cx, qpath, fn_expr. hir_id) ;
132+ // right hand side of assignment is `Default::default`
133+ if match_def_path( cx, def_id, & paths:: DEFAULT_TRAIT_METHOD ) ;
134+ then {
135+ Some ( ( idx, ident. name) )
136+ }
137+ else {
138+ None
139+ }
140+ }
141+ } ) . collect ( )
142+ }
143+
144+ fn stmt_shadows_binding ( this : & Stmt < ' _ > , shadowed : & Symbol ) -> bool {
145+ if let StmtKind :: Local ( local) = & this. kind {
146+ if let PatKind :: Binding ( _, _, ident, _) = local. pat . kind {
147+ return & ident. name == shadowed;
148+ }
149+ }
150+ false
151+ }
152+
153+ /// Returns the reassigned field and the assigning expression (right-hand side of assign).
154+ fn field_reassigned_by_stmt < ' hir > ( this : & Stmt < ' hir > , binding_name : & Symbol ) -> Option < ( Ident , & ' hir Expr < ' hir > ) > {
155+ if_chain ! {
156+ // only take assignments
157+ if let StmtKind :: Semi ( ref later_expr) = this. kind;
158+ if let ExprKind :: Assign ( ref assign_lhs, ref assign_rhs, _) = later_expr. kind;
159+ // only take assignments to fields where the left-hand side field is a field of
160+ // the same binding as the previous statement
161+ if let ExprKind :: Field ( ref binding, field_ident) = assign_lhs. kind;
162+ if let ExprKind :: Path ( ref qpath) = binding. kind;
163+ if let QPath :: Resolved ( _, path) = qpath;
164+ if let Some ( second_binding_name) = path. segments. last( ) ;
165+ if & second_binding_name. ident. name == binding_name;
166+ then {
167+ Some ( ( field_ident, assign_rhs) )
168+ } else {
169+ None
170+ }
171+ }
172+ }
0 commit comments