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