Skip to content

Commit 11a23cb

Browse files
committed
Add needless_late_init lint
1 parent 8dd1bce commit 11a23cb

18 files changed

+838
-27
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3033,6 +3033,7 @@ Released 2018-09-13
30333033
[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue
30343034
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
30353035
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
3036+
[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
30363037
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
30373038
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
30383039
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value

clippy_lints/src/lib.register_all.rs

+1
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
206206
LintId::of(needless_bool::NEEDLESS_BOOL),
207207
LintId::of(needless_borrow::NEEDLESS_BORROW),
208208
LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
209+
LintId::of(needless_late_init::NEEDLESS_LATE_INIT),
209210
LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF),
210211
LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
211212
LintId::of(needless_update::NEEDLESS_UPDATE),

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ store.register_lints(&[
362362
needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
363363
needless_continue::NEEDLESS_CONTINUE,
364364
needless_for_each::NEEDLESS_FOR_EACH,
365+
needless_late_init::NEEDLESS_LATE_INIT,
365366
needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF,
366367
needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
367368
needless_question_mark::NEEDLESS_QUESTION_MARK,

clippy_lints/src/lib.register_style.rs

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
8282
LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK),
8383
LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
8484
LintId::of(needless_borrow::NEEDLESS_BORROW),
85+
LintId::of(needless_late_init::NEEDLESS_LATE_INIT),
8586
LintId::of(neg_multiply::NEG_MULTIPLY),
8687
LintId::of(new_without_default::NEW_WITHOUT_DEFAULT),
8788
LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ mod needless_borrow;
300300
mod needless_borrowed_ref;
301301
mod needless_continue;
302302
mod needless_for_each;
303+
mod needless_late_init;
303304
mod needless_option_as_deref;
304305
mod needless_pass_by_value;
305306
mod needless_question_mark;
@@ -851,6 +852,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
851852
store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch));
852853
store.register_late_pass(move || Box::new(format_args::FormatArgs));
853854
store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray));
855+
store.register_late_pass(|| Box::new(needless_late_init::NeedlessLateInit));
854856
// add lints here, do not remove this comment, it's used in `new_lint`
855857
}
856858

+345
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::path_to_local;
3+
use clippy_utils::source::snippet_opt;
4+
use clippy_utils::visitors::{expr_visitor, is_local_used};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::intravisit::Visitor;
7+
use rustc_hir::{Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt, StmtKind};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_session::{declare_lint_pass, declare_tool_lint};
10+
use rustc_span::Span;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Checks for late initializations that can be replaced with a regular
15+
/// declaration
16+
///
17+
/// ### Why is this bad?
18+
/// Assigning in the declaration is less repetitive
19+
///
20+
/// ### Example
21+
/// ```rust
22+
/// let a;
23+
/// a = 1;
24+
///
25+
/// let b;
26+
/// match 3 {
27+
/// 0 => b = "zero",
28+
/// 1 => b = "one",
29+
/// _ => b = "many",
30+
/// }
31+
///
32+
/// let c;
33+
/// if true {
34+
/// c = 1;
35+
/// } else {
36+
/// c = -1;
37+
/// }
38+
/// ```
39+
/// Use instead:
40+
/// ```rust
41+
/// let a = 1;
42+
///
43+
/// let b = match 3 {
44+
/// 0 => "zero",
45+
/// 1 => "one",
46+
/// _ => "many",
47+
/// };
48+
///
49+
/// let c = if true {
50+
/// 1
51+
/// } else {
52+
/// -1
53+
/// };
54+
/// ```
55+
#[clippy::version = "1.58.0"]
56+
pub NEEDLESS_LATE_INIT,
57+
style,
58+
"late initializations that can be replaced with a regular declaration"
59+
}
60+
declare_lint_pass!(NeedlessLateInit => [NEEDLESS_LATE_INIT]);
61+
62+
fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> bool {
63+
let mut seen = false;
64+
expr_visitor(cx, |expr| {
65+
if let ExprKind::Assign(..) = expr.kind {
66+
seen = true;
67+
}
68+
69+
!seen
70+
})
71+
.visit_stmt(stmt);
72+
73+
seen
74+
}
75+
76+
#[derive(Debug)]
77+
struct LocalAssign {
78+
lhs_id: HirId,
79+
lhs_span: Span,
80+
rhs_span: Span,
81+
span: Span,
82+
}
83+
84+
impl LocalAssign {
85+
fn from_expr(expr: &Expr<'_>, span: Span) -> Option<Self> {
86+
if let ExprKind::Assign(lhs, rhs, _) = expr.kind {
87+
if lhs.span.from_expansion() {
88+
return None;
89+
}
90+
91+
Some(Self {
92+
lhs_id: path_to_local(lhs)?,
93+
lhs_span: lhs.span,
94+
rhs_span: rhs.span.source_callsite(),
95+
span,
96+
})
97+
} else {
98+
None
99+
}
100+
}
101+
102+
fn new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, binding_id: HirId) -> Option<LocalAssign> {
103+
let assign = match expr.kind {
104+
ExprKind::Block(Block { expr: Some(expr), .. }, _) => Self::from_expr(expr, expr.span),
105+
ExprKind::Block(block, _) => {
106+
if_chain! {
107+
if let Some((last, other_stmts)) = block.stmts.split_last();
108+
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = last.kind;
109+
110+
let assign = Self::from_expr(expr, last.span)?;
111+
112+
// avoid visiting if not needed
113+
if assign.lhs_id == binding_id;
114+
if other_stmts.iter().all(|stmt| !contains_assign_expr(cx, stmt));
115+
116+
then {
117+
Some(assign)
118+
} else {
119+
None
120+
}
121+
}
122+
},
123+
ExprKind::Assign(..) => Self::from_expr(expr, expr.span),
124+
_ => None,
125+
}?;
126+
127+
if assign.lhs_id == binding_id {
128+
Some(assign)
129+
} else {
130+
None
131+
}
132+
}
133+
}
134+
135+
fn assignment_suggestions<'tcx>(
136+
cx: &LateContext<'tcx>,
137+
binding_id: HirId,
138+
exprs: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
139+
) -> Option<(Applicability, Vec<(Span, String)>)> {
140+
let mut assignments = Vec::new();
141+
142+
for expr in exprs {
143+
let ty = cx.typeck_results().expr_ty(expr);
144+
145+
if ty.is_never() {
146+
continue;
147+
}
148+
if !ty.is_unit() {
149+
return None;
150+
}
151+
152+
let assign = LocalAssign::new(cx, expr, binding_id)?;
153+
154+
assignments.push(assign);
155+
}
156+
157+
let suggestions = assignments
158+
.into_iter()
159+
.map(|assignment| Some((assignment.span, snippet_opt(cx, assignment.rhs_span)?)))
160+
.collect::<Option<Vec<(Span, String)>>>()?;
161+
162+
let applicability = if suggestions.len() > 1 {
163+
// multiple suggestions don't work with rustfix in multipart_suggest
164+
// https://github.com/rust-lang/rustfix/issues/141
165+
Applicability::Unspecified
166+
} else {
167+
Applicability::MachineApplicable
168+
};
169+
Some((applicability, suggestions))
170+
}
171+
172+
struct Usage<'tcx> {
173+
stmt: &'tcx Stmt<'tcx>,
174+
expr: &'tcx Expr<'tcx>,
175+
needs_semi: bool,
176+
}
177+
178+
fn first_usage<'tcx>(
179+
cx: &LateContext<'tcx>,
180+
binding_id: HirId,
181+
local_stmt_id: HirId,
182+
block: &'tcx Block<'tcx>,
183+
) -> Option<Usage<'tcx>> {
184+
block
185+
.stmts
186+
.iter()
187+
.skip_while(|stmt| stmt.hir_id != local_stmt_id)
188+
.skip(1)
189+
.find(|&stmt| is_local_used(cx, stmt, binding_id))
190+
.and_then(|stmt| match stmt.kind {
191+
StmtKind::Expr(expr) => Some(Usage {
192+
stmt,
193+
expr,
194+
needs_semi: true,
195+
}),
196+
StmtKind::Semi(expr) => Some(Usage {
197+
stmt,
198+
expr,
199+
needs_semi: false,
200+
}),
201+
_ => None,
202+
})
203+
}
204+
205+
fn local_snippet_without_semicolon(cx: &LateContext<'_>, local: &Local<'_>) -> Option<String> {
206+
let span = local.span.with_hi(match local.ty {
207+
// let <pat>: <ty>;
208+
// ~~~~~~~~~~~~~~~
209+
Some(ty) => ty.span.hi(),
210+
// let <pat>;
211+
// ~~~~~~~~~
212+
None => local.pat.span.hi(),
213+
});
214+
215+
snippet_opt(cx, span)
216+
}
217+
218+
fn check<'tcx>(
219+
cx: &LateContext<'tcx>,
220+
local: &'tcx Local<'tcx>,
221+
local_stmt: &'tcx Stmt<'tcx>,
222+
block: &'tcx Block<'tcx>,
223+
binding_id: HirId,
224+
) -> Option<()> {
225+
let usage = first_usage(cx, binding_id, local_stmt.hir_id, block)?;
226+
let binding_name = cx.tcx.hir().opt_name(binding_id)?;
227+
let let_snippet = local_snippet_without_semicolon(cx, local)?;
228+
229+
match usage.expr.kind {
230+
ExprKind::Assign(..) => {
231+
let assign = LocalAssign::new(cx, usage.expr, binding_id)?;
232+
233+
span_lint_and_then(
234+
cx,
235+
NEEDLESS_LATE_INIT,
236+
local_stmt.span,
237+
"unneeded late initalization",
238+
|diag| {
239+
diag.tool_only_span_suggestion(
240+
local_stmt.span,
241+
"remove the local",
242+
String::new(),
243+
Applicability::MachineApplicable,
244+
);
245+
246+
diag.span_suggestion(
247+
assign.lhs_span,
248+
&format!("declare `{}` here", binding_name),
249+
let_snippet,
250+
Applicability::MachineApplicable,
251+
);
252+
},
253+
);
254+
},
255+
ExprKind::If(_, then_expr, Some(else_expr)) => {
256+
let (applicability, suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?;
257+
258+
span_lint_and_then(
259+
cx,
260+
NEEDLESS_LATE_INIT,
261+
local_stmt.span,
262+
"unneeded late initalization",
263+
|diag| {
264+
diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
265+
266+
diag.span_suggestion_verbose(
267+
usage.stmt.span.shrink_to_lo(),
268+
&format!("declare `{}` here", binding_name),
269+
format!("{} = ", let_snippet),
270+
applicability,
271+
);
272+
273+
diag.multipart_suggestion("remove the assignments from the branches", suggestions, applicability);
274+
275+
if usage.needs_semi {
276+
diag.span_suggestion(
277+
usage.stmt.span.shrink_to_hi(),
278+
"add a semicolon after the if expression",
279+
";".to_string(),
280+
applicability,
281+
);
282+
}
283+
},
284+
);
285+
},
286+
ExprKind::Match(_, arms, MatchSource::Normal) => {
287+
let (applicability, suggestions) = assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?;
288+
289+
span_lint_and_then(
290+
cx,
291+
NEEDLESS_LATE_INIT,
292+
local_stmt.span,
293+
"unneeded late initalization",
294+
|diag| {
295+
diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
296+
297+
diag.span_suggestion_verbose(
298+
usage.stmt.span.shrink_to_lo(),
299+
&format!("declare `{}` here", binding_name),
300+
format!("{} = ", let_snippet),
301+
applicability,
302+
);
303+
304+
diag.multipart_suggestion("remove the assignments from the match arms", suggestions, applicability);
305+
306+
if usage.needs_semi {
307+
diag.span_suggestion(
308+
usage.stmt.span.shrink_to_hi(),
309+
"add a semicolon after the match expression",
310+
";".to_string(),
311+
applicability,
312+
);
313+
}
314+
},
315+
);
316+
},
317+
_ => {},
318+
};
319+
320+
Some(())
321+
}
322+
323+
impl LateLintPass<'tcx> for NeedlessLateInit {
324+
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
325+
let mut parents = cx.tcx.hir().parent_iter(local.hir_id);
326+
327+
if_chain! {
328+
if let Local {
329+
init: None,
330+
pat: &Pat {
331+
kind: PatKind::Binding(_, binding_id, _, None),
332+
..
333+
},
334+
source: LocalSource::Normal,
335+
..
336+
} = local;
337+
if let Some((_, Node::Stmt(local_stmt))) = parents.next();
338+
if let Some((_, Node::Block(block))) = parents.next();
339+
340+
then {
341+
check(cx, local, local_stmt, block, binding_id);
342+
}
343+
}
344+
}
345+
}

0 commit comments

Comments
 (0)