Skip to content

Commit 3e052c9

Browse files
Henri Lunnikivihegza
Henri Lunnikivi
authored andcommitted
Do not add fields assigned with default to suggestion
Add test case.
1 parent ea97478 commit 3e052c9

File tree

3 files changed

+73
-14
lines changed

3 files changed

+73
-14
lines changed

clippy_lints/src/field_reassign_with_default.rs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ declare_clippy_lint! {
4040
declare_lint_pass!(FieldReassignWithDefault => [FIELD_REASSIGN_WITH_DEFAULT]);
4141

4242
impl LateLintPass<'_> for FieldReassignWithDefault {
43-
fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
43+
fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
4444
// find all binding statements like `let mut _ = T::default()` where `T::default()` is the
4545
// `default` method of the `Default` trait, and store statement index in current block being
4646
// checked and the name of the bound variable
@@ -68,11 +68,8 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
6868
else if let Some((field_ident, assign_rhs)) =
6969
field_reassigned_by_stmt(consecutive_statement, binding_name)
7070
{
71-
// extract and store the assigned value for help message
72-
let value_snippet = snippet(cx, assign_rhs.span, "..");
73-
7471
// always re-insert set value, this way the latest value is stored for output snippet
75-
assigned_fields.insert(field_ident.name, value_snippet);
72+
assigned_fields.insert(field_ident.name, assign_rhs);
7673

7774
// also set first instance of error for help message
7875
if first_assign.is_none() {
@@ -92,19 +89,33 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
9289
let stmt = &block.stmts[stmt_idx];
9390

9491
if let StmtKind::Local(preceding_local) = &stmt.kind {
92+
// filter out fields like `= Default::default()`, because the FRU already covers them
93+
let assigned_fields = assigned_fields
94+
.into_iter()
95+
.filter(|(_, rhs)| !is_expr_default(rhs, cx))
96+
.collect::<FxHashMap<Symbol, &Expr<'_>>>();
97+
9598
// if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
9699
let ext_with_default = !fields_of_type(binding_type)
97100
.iter()
98101
.all(|field| assigned_fields.contains_key(&field.name));
99102

100103
let field_list = assigned_fields
101104
.into_iter()
102-
.map(|(field, value)| format!("{}: {}", field, value))
105+
.map(|(field, rhs)| {
106+
// extract and store the assigned value for help message
107+
let value_snippet = snippet(cx, rhs.span, "..");
108+
format!("{}: {}", field, value_snippet)
109+
})
103110
.collect::<Vec<String>>()
104111
.join(", ");
105112

106113
let sugg = if ext_with_default {
107-
format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
114+
if field_list.is_empty() {
115+
format!("{}::default()", binding_type)
116+
} else {
117+
format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
118+
}
108119
} else {
109120
format!("{} {{ {} }}", binding_type, field_list)
110121
};
@@ -124,9 +135,28 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
124135
}
125136
}
126137

138+
/// Checks if the given expression is the `default` method belonging to the `Default` trait.
139+
fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
140+
if_chain! {
141+
if let ExprKind::Call(ref fn_expr, _) = &expr.kind;
142+
if let ExprKind::Path(qpath) = &fn_expr.kind;
143+
if let Res::Def(_, def_id) = qpath_res(cx, qpath, fn_expr.hir_id);
144+
// right hand side of assignment is `Default::default`
145+
if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
146+
then {
147+
true
148+
} else {
149+
false
150+
}
151+
}
152+
}
153+
127154
/// Returns the block indices, identifiers and types of bindings set as `Default::default()`, except
128155
/// for when the pattern type is a tuple.
129-
fn enumerate_bindings_using_default<'tcx>(cx: &LateContext<'tcx>, block: &Block<'_>) -> Vec<(usize, Symbol, Ty<'tcx>)> {
156+
fn enumerate_bindings_using_default<'tcx>(
157+
cx: &LateContext<'tcx>,
158+
block: &Block<'tcx>,
159+
) -> Vec<(usize, Symbol, Ty<'tcx>)> {
130160
block
131161
.stmts
132162
.iter()
@@ -142,11 +172,7 @@ fn enumerate_bindings_using_default<'tcx>(cx: &LateContext<'tcx>, block: &Block<
142172
if !matches!(ty.kind(), ty::Tuple(_));
143173
// only when assigning `... = Default::default()`
144174
if let Some(ref expr) = local.init;
145-
if let ExprKind::Call(ref fn_expr, _) = &expr.kind;
146-
if let ExprKind::Path(qpath) = &fn_expr.kind;
147-
if let Res::Def(_, def_id) = qpath_res(cx, qpath, fn_expr.hir_id);
148-
// right hand side of assignment is `Default::default`
149-
if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
175+
if is_expr_default(expr, cx);
150176
then {
151177
Some((idx, ident.name, ty))
152178
} else {

tests/ui/field_reassign_with_default.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,13 @@ fn main() {
7272
let mut c: (i32, i32) = Default::default();
7373
c.0 = 42;
7474
c.1 = 21;
75+
76+
// wrong, produces the fifth error in stderr
77+
let mut a: A = Default::default();
78+
a.i = Default::default();
79+
80+
// wrong, produces the sixth error in stderr
81+
let mut a: A = Default::default();
82+
a.i = Default::default();
83+
a.j = 45;
7584
}

tests/ui/field_reassign_with_default.stderr

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,29 @@ note: consider initializing the variable with `A { i: 42, ..Default::default() }
4747
LL | let mut a = A::default();
4848
| ^^^^^^^^^^^^^^^^^^^^^^^^^
4949

50-
error: aborting due to 4 previous errors
50+
error: field assignment outside of initializer for an instance created with Default::default()
51+
--> $DIR/field_reassign_with_default.rs:78:5
52+
|
53+
LL | a.i = Default::default();
54+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
55+
|
56+
note: consider initializing the variable with `A::default()`
57+
--> $DIR/field_reassign_with_default.rs:77:5
58+
|
59+
LL | let mut a: A = Default::default();
60+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
61+
62+
error: field assignment outside of initializer for an instance created with Default::default()
63+
--> $DIR/field_reassign_with_default.rs:82:5
64+
|
65+
LL | a.i = Default::default();
66+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
67+
|
68+
note: consider initializing the variable with `A { j: 45, ..Default::default() }`
69+
--> $DIR/field_reassign_with_default.rs:81:5
70+
|
71+
LL | let mut a: A = Default::default();
72+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73+
74+
error: aborting due to 6 previous errors
5175

0 commit comments

Comments
 (0)