2021-12-06 12:33:31 +01:00
|
|
|
use clippy_utils::diagnostics::span_lint_and_then;
|
|
|
|
use clippy_utils::path_to_local;
|
|
|
|
use clippy_utils::source::snippet_opt;
|
2022-05-05 15:12:52 +01:00
|
|
|
use clippy_utils::ty::needs_ordered_drop;
|
2024-05-17 16:45:00 +00:00
|
|
|
use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures, is_local_used};
|
2022-10-06 09:44:38 +02:00
|
|
|
use core::ops::ControlFlow;
|
2022-05-05 15:12:52 +01:00
|
|
|
use rustc_errors::{Applicability, MultiSpan};
|
|
|
|
use rustc_hir::{
|
2024-05-02 14:21:00 +02:00
|
|
|
BindingMode, Block, Expr, ExprKind, HirId, LetStmt, LocalSource, MatchSource, Node, Pat, PatKind, Stmt, StmtKind,
|
2022-05-05 15:12:52 +01:00
|
|
|
};
|
2021-12-06 12:33:31 +01:00
|
|
|
use rustc_lint::{LateContext, LateLintPass};
|
2023-12-01 18:21:58 +01:00
|
|
|
use rustc_session::declare_lint_pass;
|
2021-12-06 12:33:31 +01:00
|
|
|
use rustc_span::Span;
|
|
|
|
|
|
|
|
declare_clippy_lint! {
|
|
|
|
/// ### What it does
|
|
|
|
/// Checks for late initializations that can be replaced by a `let` statement
|
|
|
|
/// with an initializer.
|
|
|
|
///
|
|
|
|
/// ### Why is this bad?
|
|
|
|
/// Assigning in the `let` statement is less repetitive.
|
|
|
|
///
|
|
|
|
/// ### Example
|
2023-11-02 17:35:56 +01:00
|
|
|
/// ```no_run
|
2021-12-06 12:33:31 +01:00
|
|
|
/// let a;
|
|
|
|
/// a = 1;
|
|
|
|
///
|
|
|
|
/// let b;
|
|
|
|
/// match 3 {
|
|
|
|
/// 0 => b = "zero",
|
|
|
|
/// 1 => b = "one",
|
|
|
|
/// _ => b = "many",
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// let c;
|
|
|
|
/// if true {
|
|
|
|
/// c = 1;
|
|
|
|
/// } else {
|
|
|
|
/// c = -1;
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
/// Use instead:
|
2023-11-02 17:35:56 +01:00
|
|
|
/// ```no_run
|
2021-12-06 12:33:31 +01:00
|
|
|
/// let a = 1;
|
|
|
|
///
|
|
|
|
/// let b = match 3 {
|
|
|
|
/// 0 => "zero",
|
|
|
|
/// 1 => "one",
|
|
|
|
/// _ => "many",
|
|
|
|
/// };
|
|
|
|
///
|
|
|
|
/// let c = if true {
|
|
|
|
/// 1
|
|
|
|
/// } else {
|
|
|
|
/// -1
|
|
|
|
/// };
|
|
|
|
/// ```
|
2022-06-16 17:39:06 +02:00
|
|
|
#[clippy::version = "1.59.0"]
|
2021-12-06 12:33:31 +01:00
|
|
|
pub NEEDLESS_LATE_INIT,
|
|
|
|
style,
|
|
|
|
"late initializations that can be replaced by a `let` statement with an initializer"
|
|
|
|
}
|
|
|
|
declare_lint_pass!(NeedlessLateInit => [NEEDLESS_LATE_INIT]);
|
|
|
|
|
|
|
|
fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> bool {
|
2024-05-17 16:45:00 +00:00
|
|
|
for_each_expr(cx, stmt, |e| {
|
2022-10-06 09:44:38 +02:00
|
|
|
if matches!(e.kind, ExprKind::Assign(..)) {
|
|
|
|
ControlFlow::Break(())
|
|
|
|
} else {
|
|
|
|
ControlFlow::Continue(())
|
2021-12-06 12:33:31 +01:00
|
|
|
}
|
|
|
|
})
|
2022-10-06 09:44:38 +02:00
|
|
|
.is_some()
|
2021-12-06 12:33:31 +01:00
|
|
|
}
|
|
|
|
|
2022-05-05 15:12:52 +01:00
|
|
|
fn contains_let(cond: &Expr<'_>) -> bool {
|
2024-05-17 16:45:00 +00:00
|
|
|
for_each_expr_without_closures(cond, |e| {
|
2022-10-06 09:44:38 +02:00
|
|
|
if matches!(e.kind, ExprKind::Let(_)) {
|
|
|
|
ControlFlow::Break(())
|
|
|
|
} else {
|
|
|
|
ControlFlow::Continue(())
|
2022-05-05 15:12:52 +01:00
|
|
|
}
|
|
|
|
})
|
2022-10-06 09:44:38 +02:00
|
|
|
.is_some()
|
2022-05-05 15:12:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn stmt_needs_ordered_drop(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
|
2024-03-14 11:53:38 +01:00
|
|
|
let StmtKind::Let(local) = stmt.kind else {
|
2023-07-17 10:19:29 +02:00
|
|
|
return false;
|
|
|
|
};
|
2022-05-05 15:12:52 +01:00
|
|
|
!local.pat.walk_short(|pat| {
|
|
|
|
if let PatKind::Binding(.., None) = pat.kind {
|
|
|
|
!needs_ordered_drop(cx, cx.typeck_results().pat_ty(pat))
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-12-17 13:40:22 +01:00
|
|
|
#[derive(Debug)]
|
2021-12-06 12:33:31 +01:00
|
|
|
struct LocalAssign {
|
|
|
|
lhs_id: HirId,
|
|
|
|
lhs_span: Span,
|
|
|
|
rhs_span: Span,
|
|
|
|
span: Span,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl LocalAssign {
|
|
|
|
fn from_expr(expr: &Expr<'_>, span: Span) -> Option<Self> {
|
|
|
|
if let ExprKind::Assign(lhs, rhs, _) = expr.kind {
|
|
|
|
if lhs.span.from_expansion() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(Self {
|
|
|
|
lhs_id: path_to_local(lhs)?,
|
|
|
|
lhs_span: lhs.span,
|
|
|
|
rhs_span: rhs.span.source_callsite(),
|
|
|
|
span,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, binding_id: HirId) -> Option<LocalAssign> {
|
|
|
|
let assign = match expr.kind {
|
|
|
|
ExprKind::Block(Block { expr: Some(expr), .. }, _) => Self::from_expr(expr, expr.span),
|
|
|
|
ExprKind::Block(block, _) => {
|
2023-11-16 19:13:24 +01:00
|
|
|
if let Some((last, other_stmts)) = block.stmts.split_last()
|
|
|
|
&& let StmtKind::Expr(expr) | StmtKind::Semi(expr) = last.kind
|
2021-12-06 12:33:31 +01:00
|
|
|
|
2023-11-16 19:13:24 +01:00
|
|
|
&& let assign = Self::from_expr(expr, last.span)?
|
2021-12-06 12:33:31 +01:00
|
|
|
|
|
|
|
// avoid visiting if not needed
|
2023-11-16 19:13:24 +01:00
|
|
|
&& assign.lhs_id == binding_id
|
|
|
|
&& other_stmts.iter().all(|stmt| !contains_assign_expr(cx, stmt))
|
|
|
|
{
|
|
|
|
Some(assign)
|
|
|
|
} else {
|
|
|
|
None
|
2021-12-06 12:33:31 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
ExprKind::Assign(..) => Self::from_expr(expr, expr.span),
|
|
|
|
_ => None,
|
|
|
|
}?;
|
|
|
|
|
|
|
|
if assign.lhs_id == binding_id {
|
|
|
|
Some(assign)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn assignment_suggestions<'tcx>(
|
|
|
|
cx: &LateContext<'tcx>,
|
|
|
|
binding_id: HirId,
|
|
|
|
exprs: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
|
|
|
|
) -> Option<(Applicability, Vec<(Span, String)>)> {
|
|
|
|
let mut assignments = Vec::new();
|
|
|
|
|
|
|
|
for expr in exprs {
|
|
|
|
let ty = cx.typeck_results().expr_ty(expr);
|
|
|
|
|
|
|
|
if ty.is_never() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if !ty.is_unit() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let assign = LocalAssign::new(cx, expr, binding_id)?;
|
|
|
|
|
|
|
|
assignments.push(assign);
|
|
|
|
}
|
|
|
|
|
2021-12-17 13:40:22 +01:00
|
|
|
let suggestions = assignments
|
|
|
|
.iter()
|
2022-06-16 17:39:06 +02:00
|
|
|
.flat_map(|assignment| {
|
2022-10-11 16:17:59 +00:00
|
|
|
let mut spans = vec![assignment.span.until(assignment.rhs_span)];
|
|
|
|
|
|
|
|
if assignment.rhs_span.hi() != assignment.span.hi() {
|
|
|
|
spans.push(assignment.rhs_span.shrink_to_hi().with_hi(assignment.span.hi()));
|
|
|
|
}
|
|
|
|
|
|
|
|
spans
|
2022-06-16 17:39:06 +02:00
|
|
|
})
|
|
|
|
.map(|span| (span, String::new()))
|
|
|
|
.collect::<Vec<(Span, String)>>();
|
2021-12-06 12:33:31 +01:00
|
|
|
|
2022-06-04 13:34:07 +02:00
|
|
|
match suggestions.len() {
|
|
|
|
// All of `exprs` are never types
|
|
|
|
// https://github.com/rust-lang/rust-clippy/issues/8911
|
|
|
|
0 => None,
|
|
|
|
1 => Some((Applicability::MachineApplicable, suggestions)),
|
2021-12-06 12:33:31 +01:00
|
|
|
// multiple suggestions don't work with rustfix in multipart_suggest
|
|
|
|
// https://github.com/rust-lang/rustfix/issues/141
|
2022-06-04 13:34:07 +02:00
|
|
|
_ => Some((Applicability::Unspecified, suggestions)),
|
|
|
|
}
|
2021-12-06 12:33:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
struct Usage<'tcx> {
|
|
|
|
stmt: &'tcx Stmt<'tcx>,
|
|
|
|
expr: &'tcx Expr<'tcx>,
|
|
|
|
needs_semi: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn first_usage<'tcx>(
|
|
|
|
cx: &LateContext<'tcx>,
|
|
|
|
binding_id: HirId,
|
|
|
|
local_stmt_id: HirId,
|
|
|
|
block: &'tcx Block<'tcx>,
|
|
|
|
) -> Option<Usage<'tcx>> {
|
2022-05-05 15:12:52 +01:00
|
|
|
let significant_drop = needs_ordered_drop(cx, cx.typeck_results().node_type(binding_id));
|
|
|
|
|
2021-12-06 12:33:31 +01:00
|
|
|
block
|
|
|
|
.stmts
|
|
|
|
.iter()
|
|
|
|
.skip_while(|stmt| stmt.hir_id != local_stmt_id)
|
|
|
|
.skip(1)
|
2022-05-05 15:12:52 +01:00
|
|
|
.take_while(|stmt| !significant_drop || !stmt_needs_ordered_drop(cx, stmt))
|
2021-12-06 12:33:31 +01:00
|
|
|
.find(|&stmt| is_local_used(cx, stmt, binding_id))
|
|
|
|
.and_then(|stmt| match stmt.kind {
|
|
|
|
StmtKind::Expr(expr) => Some(Usage {
|
|
|
|
stmt,
|
|
|
|
expr,
|
|
|
|
needs_semi: true,
|
|
|
|
}),
|
|
|
|
StmtKind::Semi(expr) => Some(Usage {
|
|
|
|
stmt,
|
|
|
|
expr,
|
|
|
|
needs_semi: false,
|
|
|
|
}),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-03-20 17:50:31 +01:00
|
|
|
fn local_snippet_without_semicolon(cx: &LateContext<'_>, local: &LetStmt<'_>) -> Option<String> {
|
2021-12-06 12:33:31 +01:00
|
|
|
let span = local.span.with_hi(match local.ty {
|
|
|
|
// let <pat>: <ty>;
|
|
|
|
// ~~~~~~~~~~~~~~~
|
|
|
|
Some(ty) => ty.span.hi(),
|
|
|
|
// let <pat>;
|
|
|
|
// ~~~~~~~~~
|
|
|
|
None => local.pat.span.hi(),
|
|
|
|
});
|
|
|
|
|
|
|
|
snippet_opt(cx, span)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check<'tcx>(
|
|
|
|
cx: &LateContext<'tcx>,
|
2024-03-20 17:50:31 +01:00
|
|
|
local: &'tcx LetStmt<'tcx>,
|
2021-12-06 12:33:31 +01:00
|
|
|
local_stmt: &'tcx Stmt<'tcx>,
|
|
|
|
block: &'tcx Block<'tcx>,
|
|
|
|
binding_id: HirId,
|
|
|
|
) -> Option<()> {
|
|
|
|
let usage = first_usage(cx, binding_id, local_stmt.hir_id, block)?;
|
|
|
|
let binding_name = cx.tcx.hir().opt_name(binding_id)?;
|
|
|
|
let let_snippet = local_snippet_without_semicolon(cx, local)?;
|
|
|
|
|
|
|
|
match usage.expr.kind {
|
|
|
|
ExprKind::Assign(..) => {
|
|
|
|
let assign = LocalAssign::new(cx, usage.expr, binding_id)?;
|
2022-05-05 15:12:52 +01:00
|
|
|
let mut msg_span = MultiSpan::from_spans(vec![local_stmt.span, assign.span]);
|
|
|
|
msg_span.push_span_label(local_stmt.span, "created here");
|
|
|
|
msg_span.push_span_label(assign.span, "initialised here");
|
2021-12-06 12:33:31 +01:00
|
|
|
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
NEEDLESS_LATE_INIT,
|
2022-05-05 15:12:52 +01:00
|
|
|
msg_span,
|
|
|
|
"unneeded late initialization",
|
2021-12-06 12:33:31 +01:00
|
|
|
|diag| {
|
2024-05-08 13:38:00 +08:00
|
|
|
diag.multipart_suggestion(
|
|
|
|
format!("move the declaration `{binding_name}` here"),
|
|
|
|
vec![(local_stmt.span, String::new()), (assign.lhs_span, let_snippet)],
|
2021-12-06 12:33:31 +01:00
|
|
|
Applicability::MachineApplicable,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
},
|
2022-05-05 15:12:52 +01:00
|
|
|
ExprKind::If(cond, then_expr, Some(else_expr)) if !contains_let(cond) => {
|
2024-05-08 13:38:00 +08:00
|
|
|
let (applicability, mut suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?;
|
2021-12-06 12:33:31 +01:00
|
|
|
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
NEEDLESS_LATE_INIT,
|
|
|
|
local_stmt.span,
|
2022-05-05 15:12:52 +01:00
|
|
|
"unneeded late initialization",
|
2021-12-06 12:33:31 +01:00
|
|
|
|diag| {
|
2024-05-08 13:38:00 +08:00
|
|
|
suggestions.push((local_stmt.span, String::new()));
|
|
|
|
suggestions.push((usage.stmt.span.shrink_to_lo(), format!("{let_snippet} = ")));
|
2021-12-06 12:33:31 +01:00
|
|
|
|
|
|
|
if usage.needs_semi {
|
2024-05-08 13:38:00 +08:00
|
|
|
suggestions.push((usage.stmt.span.shrink_to_hi(), ";".to_owned()));
|
2021-12-06 12:33:31 +01:00
|
|
|
}
|
2024-05-08 13:38:00 +08:00
|
|
|
|
|
|
|
diag.multipart_suggestion(
|
|
|
|
format!(
|
|
|
|
"move the declaration `{binding_name}` here and remove the assignments from the branches"
|
|
|
|
),
|
|
|
|
suggestions,
|
|
|
|
applicability,
|
|
|
|
);
|
2021-12-06 12:33:31 +01:00
|
|
|
},
|
|
|
|
);
|
|
|
|
},
|
|
|
|
ExprKind::Match(_, arms, MatchSource::Normal) => {
|
2024-05-08 13:38:00 +08:00
|
|
|
let (applicability, mut suggestions) =
|
|
|
|
assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?;
|
2021-12-06 12:33:31 +01:00
|
|
|
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
NEEDLESS_LATE_INIT,
|
|
|
|
local_stmt.span,
|
2022-05-05 15:12:52 +01:00
|
|
|
"unneeded late initialization",
|
2021-12-06 12:33:31 +01:00
|
|
|
|diag| {
|
2024-05-08 13:38:00 +08:00
|
|
|
suggestions.push((local_stmt.span, String::new()));
|
|
|
|
suggestions.push((usage.stmt.span.shrink_to_lo(), format!("{let_snippet} = ")));
|
2021-12-06 12:33:31 +01:00
|
|
|
|
2024-05-08 13:38:00 +08:00
|
|
|
if usage.needs_semi {
|
|
|
|
suggestions.push((usage.stmt.span.shrink_to_hi(), ";".to_owned()));
|
|
|
|
}
|
2021-12-06 12:33:31 +01:00
|
|
|
|
|
|
|
diag.multipart_suggestion(
|
2024-05-08 13:38:00 +08:00
|
|
|
format!("move the declaration `{binding_name}` here and remove the assignments from the `match` arms"),
|
2021-12-06 12:33:31 +01:00
|
|
|
suggestions,
|
|
|
|
applicability,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
};
|
|
|
|
|
|
|
|
Some(())
|
|
|
|
}
|
|
|
|
|
2022-01-13 13:18:19 +01:00
|
|
|
impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit {
|
2024-03-20 17:50:31 +01:00
|
|
|
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'tcx>) {
|
2021-12-06 12:33:31 +01:00
|
|
|
let mut parents = cx.tcx.hir().parent_iter(local.hir_id);
|
2024-03-20 17:50:31 +01:00
|
|
|
if let LetStmt {
|
2023-11-16 19:13:24 +01:00
|
|
|
init: None,
|
|
|
|
pat:
|
|
|
|
&Pat {
|
2024-04-16 19:23:30 -04:00
|
|
|
kind: PatKind::Binding(BindingMode::NONE, binding_id, _, None),
|
2021-12-06 12:33:31 +01:00
|
|
|
..
|
|
|
|
},
|
2023-11-16 19:13:24 +01:00
|
|
|
source: LocalSource::Normal,
|
|
|
|
..
|
|
|
|
} = local
|
|
|
|
&& let Some((_, Node::Stmt(local_stmt))) = parents.next()
|
|
|
|
&& let Some((_, Node::Block(block))) = parents.next()
|
|
|
|
{
|
|
|
|
check(cx, local, local_stmt, block, binding_id);
|
2021-12-06 12:33:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|