Skip to content

Commit afcb3ee

Browse files
author
Henri Lunnikivi
committed
Only add ..Default::default() to suggestion if needed
1 parent 51766b7 commit afcb3ee

File tree

2 files changed

+36
-10
lines changed

2 files changed

+36
-10
lines changed

clippy_lints/src/field_reassign_with_default.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rustc_hir::def::Res;
44
use rustc_data_structures::fx::FxHashMap;
55
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
66
use rustc_span::symbol::{Ident, Symbol};
7+
use rustc_middle::ty::{Adt, TyS};
78

89
use rustc_lint::{LateContext, LateLintPass};
910
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -44,11 +45,11 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
4445
// find all binding statements like `let mut _ = T::default()` where `T::default()` is the
4546
// `default` method of the `Default` trait. and store statement index in current block being
4647
// checked and the name of the bound variable
47-
let binding_statements_using_default: Vec<(usize, Symbol)> = enumerate_bindings_using_default(cx, block);
48+
let binding_statements_using_default: Vec<(usize, Symbol, &TyS<'_>)> = enumerate_bindings_using_default(cx, block);
4849

4950
// start from the `let mut _ = _::default();` and look at all the following
5051
// statements, see if they re-assign the fields of the binding
51-
for (stmt_idx, binding_name) in binding_statements_using_default {
52+
for (stmt_idx, binding_name, binding_type) in binding_statements_using_default {
5253
// last statement of block cannot trigger the lint
5354
if stmt_idx == block.stmts.len() - 1 {
5455
break;
@@ -71,9 +72,9 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
7172
if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consequtive_statement, &binding_name) {
7273
// extract and store the assigned value for help message
7374
let value_snippet = snippet(cx, assign_rhs.span, "..");
74-
if !assigned_fields.contains_key(&field_ident.name) {
75-
assigned_fields.insert(field_ident.name, value_snippet);
76-
}
75+
76+
// always re-insert set value, this way the latest value is stored for output snippet
77+
assigned_fields.insert(field_ident.name, value_snippet);
7778

7879
// also set first instance of error for help message
7980
if first_assign.is_none() {
@@ -88,8 +89,13 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
8889
if !assigned_fields.is_empty() {
8990
// take the original assignment as span
9091
let stmt = &block.stmts[stmt_idx];
92+
9193
if let StmtKind::Local(preceding_local) = &stmt.kind {
9294
if let Some(ty) = &preceding_local.ty {
95+
96+
// if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
97+
let ext_with_default = !fields_of_type(&binding_type).iter().all(|field| assigned_fields.contains_key(&field.name));
98+
9399
let ty_snippet = snippet(cx, ty.span, "_");
94100

95101
let field_list = assigned_fields
@@ -98,7 +104,11 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
98104
.collect::<Vec<String>>()
99105
.join(", ");
100106

101-
let sugg = format!("{} {{ {}, ..Default::default() }}", ty_snippet, field_list);
107+
let sugg = if ext_with_default {
108+
format!("{} {{ {}, ..Default::default() }}", ty_snippet, field_list)
109+
} else {
110+
format!("{} {{ {} }}", ty_snippet, field_list)
111+
};
102112

103113
// span lint once per statement that binds default
104114
span_lint_and_note(
@@ -109,14 +119,16 @@ impl LateLintPass<'_> for FieldReassignWithDefault {
109119
Some(preceding_local.span),
110120
&format!("consider initializing the variable with `{}`", sugg),
111121
);
122+
112123
}
113124
}
114125
}
115126
}
116127
}
117128
}
118129

119-
fn enumerate_bindings_using_default(cx: &LateContext<'_>, block: &Block<'_>) -> Vec<(usize, Symbol)> {
130+
/// Returns the indices, identifiers and types of bindings set as `Default::default()`.
131+
fn enumerate_bindings_using_default<'cx, 'hir>(cx: &LateContext<'cx>, block: &Block<'hir>) -> Vec<(usize, Symbol, &'cx TyS<'cx>)> {
120132
block
121133
.stmts
122134
.iter()
@@ -135,7 +147,9 @@ fn enumerate_bindings_using_default(cx: &LateContext<'_>, block: &Block<'_>) ->
135147
// right hand side of assignment is `Default::default`
136148
if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
137149
then {
138-
Some((idx, ident.name))
150+
// Get the type of the pattern
151+
let ty = cx.typeck_results().pat_ty(local.pat);
152+
Some((idx, ident.name, ty))
139153
}
140154
else {
141155
None
@@ -173,3 +187,15 @@ fn field_reassigned_by_stmt<'hir>(this: &Stmt<'hir>, binding_name: &Symbol) -> O
173187
}
174188
}
175189
}
190+
191+
/// Returns the vec of fields for a struct and an empty vec for non-struct ADTs.
192+
fn fields_of_type<'a>(ty: &'a TyS<'_>) -> Vec<Ident> {
193+
if let Adt(adt, _) = ty.kind() {
194+
if adt.is_struct() {
195+
// unwrap is safe, because this is a struct and structs have only one variant
196+
let variant = &adt.variants.get(0usize.into()).unwrap();
197+
return variant.fields.iter().map(|f| f.ident).collect();
198+
}
199+
}
200+
vec![]
201+
}

tests/ui/field_reassign_with_default.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ error: field assignment outside of initializer for an instance created with Defa
1717
LL | a.i = 42;
1818
| ^^^^^^^^^
1919
|
20-
note: consider initializing the variable with `A { i: 42, j: 43, ..Default::default() }`
20+
note: consider initializing the variable with `A { i: 42, j: 43 }`
2121
--> $DIR/field_reassign_with_default.rs:56:5
2222
|
2323
LL | let mut a: A = Default::default();
@@ -29,7 +29,7 @@ error: field assignment outside of initializer for an instance created with Defa
2929
LL | a.i = 42;
3030
| ^^^^^^^^^
3131
|
32-
note: consider initializing the variable with `A { i: 42, j: 43, ..Default::default() }`
32+
note: consider initializing the variable with `A { i: 42, j: 44 }`
3333
--> $DIR/field_reassign_with_default.rs:61:5
3434
|
3535
LL | let mut a: A = Default::default();

0 commit comments

Comments
 (0)