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