Skip to content

Commit 3661848

Browse files
committed
Auto merge of rust-lang#6375 - camsteffen:reassign-default-private, r=flip1995
Reassign default private changelog: fix field_reassign_with_default false positive * Fix rust-lang#6344 * Fix assumption that `field: Default::default()` is the same as `..Default::default()` * Cleanup some redundant logic
2 parents 0154965 + c6450c7 commit 3661848

File tree

3 files changed

+85
-119
lines changed

3 files changed

+85
-119
lines changed

Diff for: clippy_lints/src/default.rs

+71-117
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_errors::Applicability;
66
use rustc_hir::def::Res;
77
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
88
use rustc_lint::{LateContext, LateLintPass};
9-
use rustc_middle::ty::{self, Adt, Ty};
9+
use rustc_middle::ty;
1010
use rustc_session::{declare_tool_lint, impl_lint_pass};
1111
use rustc_span::symbol::{Ident, Symbol};
1212
use rustc_span::Span;
@@ -103,34 +103,50 @@ impl LateLintPass<'_> for Default {
103103
}
104104

105105
fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
106-
// find all binding statements like `let mut _ = T::default()` where `T::default()` is the
107-
// `default` method of the `Default` trait, and store statement index in current block being
108-
// checked and the name of the bound variable
109-
let binding_statements_using_default = enumerate_bindings_using_default(cx, block);
110-
111106
// start from the `let mut _ = _::default();` and look at all the following
112107
// statements, see if they re-assign the fields of the binding
113-
for (stmt_idx, binding_name, binding_type, span) in binding_statements_using_default {
114-
// the last statement of a block cannot trigger the lint
115-
if stmt_idx == block.stmts.len() - 1 {
116-
break;
117-
}
108+
let stmts_head = match block.stmts {
109+
// Skip the last statement since there cannot possibly be any following statements that re-assign fields.
110+
[head @ .., _] if !head.is_empty() => head,
111+
_ => return,
112+
};
113+
for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
114+
// find all binding statements like `let mut _ = T::default()` where `T::default()` is the
115+
// `default` method of the `Default` trait, and store statement index in current block being
116+
// checked and the name of the bound variable
117+
let (local, variant, binding_name, binding_type, span) = if_chain! {
118+
// only take `let ...` statements
119+
if let StmtKind::Local(local) = stmt.kind;
120+
if let Some(expr) = local.init;
121+
// only take bindings to identifiers
122+
if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
123+
// only when assigning `... = Default::default()`
124+
if is_expr_default(expr, cx);
125+
let binding_type = cx.typeck_results().node_type(binding_id);
126+
if let Some(adt) = binding_type.ty_adt_def();
127+
if adt.is_struct();
128+
let variant = adt.non_enum_variant();
129+
if adt.did.is_local() || !variant.is_field_list_non_exhaustive();
130+
let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id();
131+
if variant
132+
.fields
133+
.iter()
134+
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
135+
then {
136+
(local, variant, ident.name, binding_type, expr.span)
137+
} else {
138+
continue;
139+
}
140+
};
118141

119142
// find all "later statement"'s where the fields of the binding set as
120143
// Default::default() get reassigned, unless the reassignment refers to the original binding
121144
let mut first_assign = None;
122145
let mut assigned_fields = Vec::new();
123146
let mut cancel_lint = false;
124147
for consecutive_statement in &block.stmts[stmt_idx + 1..] {
125-
// interrupt if the statement is a let binding (`Local`) that shadows the original
126-
// binding
127-
if stmt_shadows_binding(consecutive_statement, binding_name) {
128-
break;
129-
}
130148
// find out if and which field was set by this `consecutive_statement`
131-
else if let Some((field_ident, assign_rhs)) =
132-
field_reassigned_by_stmt(consecutive_statement, binding_name)
133-
{
149+
if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) {
134150
// interrupt and cancel lint if assign_rhs references the original binding
135151
if contains_name(binding_name, assign_rhs) {
136152
cancel_lint = true;
@@ -152,7 +168,7 @@ impl LateLintPass<'_> for Default {
152168
first_assign = Some(consecutive_statement);
153169
}
154170
}
155-
// interrupt also if no field was assigned, since we only want to look at consecutive statements
171+
// interrupt if no field was assigned, since we only want to look at consecutive statements
156172
else {
157173
break;
158174
}
@@ -161,55 +177,45 @@ impl LateLintPass<'_> for Default {
161177
// if there are incorrectly assigned fields, do a span_lint_and_note to suggest
162178
// construction using `Ty { fields, ..Default::default() }`
163179
if !assigned_fields.is_empty() && !cancel_lint {
164-
// take the original assignment as span
165-
let stmt = &block.stmts[stmt_idx];
166-
167-
if let StmtKind::Local(preceding_local) = &stmt.kind {
168-
// filter out fields like `= Default::default()`, because the FRU already covers them
169-
let assigned_fields = assigned_fields
170-
.into_iter()
171-
.filter(|(_, rhs)| !is_expr_default(rhs, cx))
172-
.collect::<Vec<(Symbol, &Expr<'_>)>>();
180+
// if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
181+
let ext_with_default = !variant
182+
.fields
183+
.iter()
184+
.all(|field| assigned_fields.iter().any(|(a, _)| a == &field.ident.name));
173185

174-
// if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
175-
let ext_with_default = !fields_of_type(binding_type)
176-
.iter()
177-
.all(|field| assigned_fields.iter().any(|(a, _)| a == &field.name));
186+
let field_list = assigned_fields
187+
.into_iter()
188+
.map(|(field, rhs)| {
189+
// extract and store the assigned value for help message
190+
let value_snippet = snippet(cx, rhs.span, "..");
191+
format!("{}: {}", field, value_snippet)
192+
})
193+
.collect::<Vec<String>>()
194+
.join(", ");
178195

179-
let field_list = assigned_fields
180-
.into_iter()
181-
.map(|(field, rhs)| {
182-
// extract and store the assigned value for help message
183-
let value_snippet = snippet(cx, rhs.span, "..");
184-
format!("{}: {}", field, value_snippet)
185-
})
186-
.collect::<Vec<String>>()
187-
.join(", ");
188-
189-
let sugg = if ext_with_default {
190-
if field_list.is_empty() {
191-
format!("{}::default()", binding_type)
192-
} else {
193-
format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
194-
}
196+
let sugg = if ext_with_default {
197+
if field_list.is_empty() {
198+
format!("{}::default()", binding_type)
195199
} else {
196-
format!("{} {{ {} }}", binding_type, field_list)
197-
};
200+
format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
201+
}
202+
} else {
203+
format!("{} {{ {} }}", binding_type, field_list)
204+
};
198205

199-
// span lint once per statement that binds default
200-
span_lint_and_note(
201-
cx,
202-
FIELD_REASSIGN_WITH_DEFAULT,
203-
first_assign.unwrap().span,
204-
"field assignment outside of initializer for an instance created with Default::default()",
205-
Some(preceding_local.span),
206-
&format!(
207-
"consider initializing the variable with `{}` and removing relevant reassignments",
208-
sugg
209-
),
210-
);
211-
self.reassigned_linted.insert(span);
212-
}
206+
// span lint once per statement that binds default
207+
span_lint_and_note(
208+
cx,
209+
FIELD_REASSIGN_WITH_DEFAULT,
210+
first_assign.unwrap().span,
211+
"field assignment outside of initializer for an instance created with Default::default()",
212+
Some(local.span),
213+
&format!(
214+
"consider initializing the variable with `{}` and removing relevant reassignments",
215+
sugg
216+
),
217+
);
218+
self.reassigned_linted.insert(span);
213219
}
214220
}
215221
}
@@ -230,47 +236,6 @@ fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool
230236
}
231237
}
232238

233-
/// Returns the block indices, identifiers and types of bindings set as `Default::default()`, except
234-
/// for when the pattern type is a tuple.
235-
fn enumerate_bindings_using_default<'tcx>(
236-
cx: &LateContext<'tcx>,
237-
block: &Block<'tcx>,
238-
) -> Vec<(usize, Symbol, Ty<'tcx>, Span)> {
239-
block
240-
.stmts
241-
.iter()
242-
.enumerate()
243-
.filter_map(|(idx, stmt)| {
244-
if_chain! {
245-
// only take `let ...` statements
246-
if let StmtKind::Local(ref local) = stmt.kind;
247-
// only take bindings to identifiers
248-
if let PatKind::Binding(_, _, ident, _) = local.pat.kind;
249-
// that are not tuples
250-
let ty = cx.typeck_results().pat_ty(local.pat);
251-
if !matches!(ty.kind(), ty::Tuple(_));
252-
// only when assigning `... = Default::default()`
253-
if let Some(ref expr) = local.init;
254-
if is_expr_default(expr, cx);
255-
then {
256-
Some((idx, ident.name, ty, expr.span))
257-
} else {
258-
None
259-
}
260-
}
261-
})
262-
.collect()
263-
}
264-
265-
fn stmt_shadows_binding(this: &Stmt<'_>, shadowed: Symbol) -> bool {
266-
if let StmtKind::Local(local) = &this.kind {
267-
if let PatKind::Binding(_, _, ident, _) = local.pat.kind {
268-
return ident.name == shadowed;
269-
}
270-
}
271-
false
272-
}
273-
274239
/// Returns the reassigned field and the assigning expression (right-hand side of assign).
275240
fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
276241
if_chain! {
@@ -290,14 +255,3 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
290255
}
291256
}
292257
}
293-
294-
/// Returns the vec of fields for a struct and an empty vec for non-struct ADTs.
295-
fn fields_of_type(ty: Ty<'_>) -> Vec<Ident> {
296-
if let Adt(adt, _) = ty.kind() {
297-
if adt.is_struct() {
298-
let variant = &adt.non_enum_variant();
299-
return variant.fields.iter().map(|f| f.ident).collect();
300-
}
301-
}
302-
vec![]
303-
}

Diff for: tests/ui/field_reassign_with_default.rs

+12
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,16 @@ fn main() {
107107
x.i = side_effect.next();
108108
x.j = 2;
109109
x.i = side_effect.next();
110+
111+
// don't lint - some private fields
112+
let mut x = m::F::default();
113+
x.a = 1;
114+
}
115+
116+
mod m {
117+
#[derive(Default)]
118+
pub struct F {
119+
pub a: u64,
120+
b: u64,
121+
}
110122
}

Diff for: tests/ui/field_reassign_with_default.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ error: field assignment outside of initializer for an instance created with Defa
5353
LL | a.i = Default::default();
5454
| ^^^^^^^^^^^^^^^^^^^^^^^^^
5555
|
56-
note: consider initializing the variable with `A::default()` and removing relevant reassignments
56+
note: consider initializing the variable with `A { i: Default::default(), ..Default::default() }` and removing relevant reassignments
5757
--> $DIR/field_reassign_with_default.rs:90:5
5858
|
5959
LL | let mut a: A = Default::default();
@@ -65,7 +65,7 @@ error: field assignment outside of initializer for an instance created with Defa
6565
LL | a.i = Default::default();
6666
| ^^^^^^^^^^^^^^^^^^^^^^^^^
6767
|
68-
note: consider initializing the variable with `A { j: 45, ..Default::default() }` and removing relevant reassignments
68+
note: consider initializing the variable with `A { i: Default::default(), j: 45 }` and removing relevant reassignments
6969
--> $DIR/field_reassign_with_default.rs:94:5
7070
|
7171
LL | let mut a: A = Default::default();

0 commit comments

Comments
 (0)