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};
12 declare_clippy_lint! {
14 /// Checks for late initializations that can be replaced by a `let` statement
15 /// with an initializer.
17 /// ### Why is this bad?
18 /// Assigning in the `let` statement is less repetitive.
55 #[clippy::version = "1.58.0"]
56 pub NEEDLESS_LATE_INIT,
58 "late initializations that can be replaced by a `let` statement with an initializer"
60 declare_lint_pass!(NeedlessLateInit => [NEEDLESS_LATE_INIT]);
62 fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> bool {
64 expr_visitor(cx, |expr| {
65 if let ExprKind::Assign(..) = expr.kind {
76 #[derive(Debug, Clone)]
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() {
92 lhs_id: path_to_local(lhs)?,
94 rhs_span: rhs.span.source_callsite(),
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, _) => {
107 if let Some((last, other_stmts)) = block.stmts.split_last();
108 if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = last.kind;
110 let assign = Self::from_expr(expr, last.span)?;
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));
123 ExprKind::Assign(..) => Self::from_expr(expr, expr.span),
127 if assign.lhs_id == binding_id {
135 fn assignment_suggestions<'tcx>(
136 cx: &LateContext<'tcx>,
138 exprs: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
139 ) -> Option<(Applicability, Vec<(Span, String)>)> {
140 let mut assignments = Vec::new();
143 let ty = cx.typeck_results().expr_ty(expr);
152 let assign = LocalAssign::new(cx, expr, binding_id)?;
154 assignments.push(assign);
157 let suggestions = assignments.clone()
159 .map(|assignment| Some((assignment.span.until(assignment.rhs_span), String::new())))
163 .map(|assignment| Some((assignment.rhs_span.shrink_to_hi().with_hi(assignment.span.hi()), String::new())))
165 .collect::<Option<Vec<(Span, String)>>>()?;
167 let applicability = if suggestions.len() > 1 {
168 // multiple suggestions don't work with rustfix in multipart_suggest
169 // https://github.com/rust-lang/rustfix/issues/141
170 Applicability::Unspecified
172 Applicability::MachineApplicable
174 Some((applicability, suggestions))
178 stmt: &'tcx Stmt<'tcx>,
179 expr: &'tcx Expr<'tcx>,
183 fn first_usage<'tcx>(
184 cx: &LateContext<'tcx>,
186 local_stmt_id: HirId,
187 block: &'tcx Block<'tcx>,
188 ) -> Option<Usage<'tcx>> {
192 .skip_while(|stmt| stmt.hir_id != local_stmt_id)
194 .find(|&stmt| is_local_used(cx, stmt, binding_id))
195 .and_then(|stmt| match stmt.kind {
196 StmtKind::Expr(expr) => Some(Usage {
201 StmtKind::Semi(expr) => Some(Usage {
210 fn local_snippet_without_semicolon(cx: &LateContext<'_>, local: &Local<'_>) -> Option<String> {
211 let span = local.span.with_hi(match local.ty {
214 Some(ty) => ty.span.hi(),
217 None => local.pat.span.hi(),
220 snippet_opt(cx, span)
224 cx: &LateContext<'tcx>,
225 local: &'tcx Local<'tcx>,
226 local_stmt: &'tcx Stmt<'tcx>,
227 block: &'tcx Block<'tcx>,
230 let usage = first_usage(cx, binding_id, local_stmt.hir_id, block)?;
231 let binding_name = cx.tcx.hir().opt_name(binding_id)?;
232 let let_snippet = local_snippet_without_semicolon(cx, local)?;
234 match usage.expr.kind {
235 ExprKind::Assign(..) => {
236 let assign = LocalAssign::new(cx, usage.expr, binding_id)?;
242 "unneeded late initalization",
244 diag.tool_only_span_suggestion(
248 Applicability::MachineApplicable,
251 diag.span_suggestion(
253 &format!("declare `{}` here", binding_name),
255 Applicability::MachineApplicable,
260 ExprKind::If(_, then_expr, Some(else_expr)) => {
261 let (applicability, suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?;
267 "unneeded late initalization",
269 diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
271 diag.span_suggestion_verbose(
272 usage.stmt.span.shrink_to_lo(),
273 &format!("declare `{}` here", binding_name),
274 format!("{} = ", let_snippet),
278 diag.multipart_suggestion("remove the assignments from the branches", suggestions, applicability);
280 if usage.needs_semi {
281 diag.span_suggestion(
282 usage.stmt.span.shrink_to_hi(),
283 "add a semicolon after the `if` expression",
291 ExprKind::Match(_, arms, MatchSource::Normal) => {
292 let (applicability, suggestions) = assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?;
298 "unneeded late initalization",
300 diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
302 diag.span_suggestion_verbose(
303 usage.stmt.span.shrink_to_lo(),
304 &format!("declare `{}` here", binding_name),
305 format!("{} = ", let_snippet),
309 diag.multipart_suggestion(
310 "remove the assignments from the `match` arms",
315 if usage.needs_semi {
316 diag.span_suggestion(
317 usage.stmt.span.shrink_to_hi(),
318 "add a semicolon after the `match` expression",
332 impl LateLintPass<'tcx> for NeedlessLateInit {
333 fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
334 let mut parents = cx.tcx.hir().parent_iter(local.hir_id);
340 kind: PatKind::Binding(_, binding_id, _, None),
343 source: LocalSource::Normal,
346 if let Some((_, Node::Stmt(local_stmt))) = parents.next();
347 if let Some((_, Node::Block(block))) = parents.next();
350 check(cx, local, local_stmt, block, binding_id);