Skip to content

Commit 6722c97

Browse files
author
Henri Lunnikivi
committed
Implement lint for field_reassign_with_default
1 parent 6b6f4ce commit 6722c97

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed
Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,92 @@
1-
use rustc::lint::{LateLintPass, LintArray, LintPass};
1+
use crate::utils::span_note_and_lint;
2+
use if_chain::if_chain;
3+
use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
24
use rustc::{declare_lint_pass, declare_tool_lint};
5+
use syntax::ast::{BindingMode, Block, ExprKind, Mutability, PatKind, StmtKind};
6+
use syntax::print::pprust::{expr_to_string, ty_to_string};
7+
use syntax::symbol::Symbol;
38

49
declare_clippy_lint! {
510
pub FIELD_REASSIGN_WITH_DEFAULT,
611
pedantic,
7-
"instance initialized with Default should have its fields set in the initializer"
12+
"binding initialized with Default should have its fields set in the initializer"
813
}
914

1015
declare_lint_pass!(FieldReassignWithDefault => [FIELD_REASSIGN_WITH_DEFAULT]);
1116

12-
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FieldReassignWithDefault {}
17+
impl EarlyLintPass for FieldReassignWithDefault {
18+
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
19+
// store statement index and name of binding for all statements like
20+
// `let mut <binding> = Default::default();`
21+
let binding_statements_using_default: Vec<(usize, Symbol)> = block
22+
.stmts
23+
.iter()
24+
.enumerate()
25+
.filter_map(|(idx, stmt)| {
26+
if_chain! {
27+
// only take `let ...`
28+
if let StmtKind::Local(ref local) = stmt.kind;
29+
// only take `... mut <binding> ...`
30+
if let PatKind::Ident(BindingMode::ByValue(Mutability::Mutable), binding, _) = local.pat.kind;
31+
// only when assigning `... = Default::default()`
32+
if let Some(ref expr) = local.init;
33+
if let ExprKind::Call(ref fn_expr, _) = &expr.kind;
34+
if let ExprKind::Path(_, path) = &fn_expr.kind;
35+
if path.segments.len() >= 2;
36+
if path.segments[path.segments.len()-2].ident.as_str() == "Default";
37+
if path.segments.last().unwrap().ident.as_str() == "default";
38+
then {
39+
Some((idx, binding.name))
40+
}
41+
else {
42+
None
43+
}
44+
}
45+
})
46+
.collect::<Vec<_>>();
47+
48+
// look at all the following statements for the binding statements and see if they reassign
49+
// the fields of the binding
50+
for (stmt_idx, binding_name) in binding_statements_using_default {
51+
// last statement of block cannot trigger the lint
52+
if stmt_idx == block.stmts.len() - 1 {
53+
break;
54+
}
55+
56+
// find "later statement"'s where the fields of the binding set by Default::default()
57+
// get reassigned
58+
let later_stmt = &block.stmts[stmt_idx + 1];
59+
if_chain! {
60+
// only take assignments
61+
if let StmtKind::Semi(ref later_expr) = later_stmt.kind;
62+
if let ExprKind::Assign(ref later_lhs, ref later_rhs) = later_expr.kind;
63+
// only take assignments to fields where the field refers to the same binding as the previous statement
64+
if let ExprKind::Field(ref binding_name_candidate, later_field_ident) = later_lhs.kind;
65+
if let ExprKind::Path(_, path) = &binding_name_candidate.kind;
66+
if let Some(second_binding_name) = path.segments.last();
67+
if second_binding_name.ident.name == binding_name;
68+
then {
69+
// take the preceding statement as span
70+
let stmt = &block.stmts[stmt_idx];
71+
if let StmtKind::Local(preceding_local) = &stmt.kind {
72+
// reorganize the latter assigment statement into `field` and `value` for the lint
73+
let field = later_field_ident.name.as_str();
74+
let value = expr_to_string(later_rhs);
75+
if let Some(ty) = &preceding_local.ty {
76+
let ty = ty_to_string(ty);
77+
78+
span_note_and_lint(
79+
cx,
80+
FIELD_REASSIGN_WITH_DEFAULT,
81+
later_stmt.span,
82+
"field assignment outside of initializer for an instance created with Default::default()",
83+
preceding_local.span,
84+
&format!("consider initializing the variable immutably with `{} {{ {}: {}, ..Default::default() }}`", ty, field, value),
85+
);
86+
}
87+
}
88+
}
89+
}
90+
}
91+
}
92+
}

src/lintlist/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ pub const ALL_LINTS: [Lint; 333] = [
556556
Lint {
557557
name: "field_reassign_with_default",
558558
group: "pedantic",
559-
desc: "instance initialized with Default should have its fields set in the initializer",
559+
desc: "binding initialized with Default should have its fields set in the initializer",
560560
deprecation: None,
561561
module: "field_reassign_with_default",
562562
},

0 commit comments

Comments
 (0)