Skip to content

Commit fa75f63

Browse files
camsteffenflip1995
authored andcommitted
Fix field_reassign_with_default for private fields
1 parent f74e200 commit fa75f63

File tree

2 files changed

+81
-93
lines changed

2 files changed

+81
-93
lines changed

clippy_lints/src/default.rs

Lines changed: 69 additions & 93 deletions
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,18 +103,41 @@ 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
@@ -154,49 +177,45 @@ impl LateLintPass<'_> for Default {
154177
// if there are incorrectly assigned fields, do a span_lint_and_note to suggest
155178
// construction using `Ty { fields, ..Default::default() }`
156179
if !assigned_fields.is_empty() && !cancel_lint {
157-
// take the original assignment as span
158-
let stmt = &block.stmts[stmt_idx];
159-
160-
if let StmtKind::Local(preceding_local) = &stmt.kind {
161-
// if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
162-
let ext_with_default = !fields_of_type(binding_type)
163-
.iter()
164-
.all(|field| assigned_fields.iter().any(|(a, _)| a == &field.name));
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));
165185

166-
let field_list = assigned_fields
167-
.into_iter()
168-
.map(|(field, rhs)| {
169-
// extract and store the assigned value for help message
170-
let value_snippet = snippet(cx, rhs.span, "..");
171-
format!("{}: {}", field, value_snippet)
172-
})
173-
.collect::<Vec<String>>()
174-
.join(", ");
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(", ");
175195

176-
let sugg = if ext_with_default {
177-
if field_list.is_empty() {
178-
format!("{}::default()", binding_type)
179-
} else {
180-
format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
181-
}
196+
let sugg = if ext_with_default {
197+
if field_list.is_empty() {
198+
format!("{}::default()", binding_type)
182199
} else {
183-
format!("{} {{ {} }}", binding_type, field_list)
184-
};
200+
format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
201+
}
202+
} else {
203+
format!("{} {{ {} }}", binding_type, field_list)
204+
};
185205

186-
// span lint once per statement that binds default
187-
span_lint_and_note(
188-
cx,
189-
FIELD_REASSIGN_WITH_DEFAULT,
190-
first_assign.unwrap().span,
191-
"field assignment outside of initializer for an instance created with Default::default()",
192-
Some(preceding_local.span),
193-
&format!(
194-
"consider initializing the variable with `{}` and removing relevant reassignments",
195-
sugg
196-
),
197-
);
198-
self.reassigned_linted.insert(span);
199-
}
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);
200219
}
201220
}
202221
}
@@ -217,38 +236,6 @@ fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool
217236
}
218237
}
219238

220-
/// Returns the block indices, identifiers and types of bindings set as `Default::default()`, except
221-
/// for when the pattern type is a tuple.
222-
fn enumerate_bindings_using_default<'tcx>(
223-
cx: &LateContext<'tcx>,
224-
block: &Block<'tcx>,
225-
) -> Vec<(usize, Symbol, Ty<'tcx>, Span)> {
226-
block
227-
.stmts
228-
.iter()
229-
.enumerate()
230-
.filter_map(|(idx, stmt)| {
231-
if_chain! {
232-
// only take `let ...` statements
233-
if let StmtKind::Local(ref local) = stmt.kind;
234-
// only take bindings to identifiers
235-
if let PatKind::Binding(_, _, ident, _) = local.pat.kind;
236-
// that are not tuples
237-
let ty = cx.typeck_results().pat_ty(local.pat);
238-
if !matches!(ty.kind(), ty::Tuple(_));
239-
// only when assigning `... = Default::default()`
240-
if let Some(ref expr) = local.init;
241-
if is_expr_default(expr, cx);
242-
then {
243-
Some((idx, ident.name, ty, expr.span))
244-
} else {
245-
None
246-
}
247-
}
248-
})
249-
.collect()
250-
}
251-
252239
/// Returns the reassigned field and the assigning expression (right-hand side of assign).
253240
fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
254241
if_chain! {
@@ -268,14 +255,3 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
268255
}
269256
}
270257
}
271-
272-
/// Returns the vec of fields for a struct and an empty vec for non-struct ADTs.
273-
fn fields_of_type(ty: Ty<'_>) -> Vec<Ident> {
274-
if let Adt(adt, _) = ty.kind() {
275-
if adt.is_struct() {
276-
let variant = &adt.non_enum_variant();
277-
return variant.fields.iter().map(|f| f.ident).collect();
278-
}
279-
}
280-
vec![]
281-
}

tests/ui/field_reassign_with_default.rs

Lines changed: 12 additions & 0 deletions
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
}

0 commit comments

Comments
 (0)