]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/needless_late_init.rs
Auto merge of #93893 - oli-obk:sad_revert, r=oli-obk
[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::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 by a `let` statement
15     /// with an initializer.
16     ///
17     /// ### Why is this bad?
18     /// Assigning in the `let` statement 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 by a `let` statement with an initializer"
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         .iter()
159         .map(|assignment| Some((assignment.span.until(assignment.rhs_span), String::new())))
160         .chain(assignments.iter().map(|assignment| {
161             Some((
162                 assignment.rhs_span.shrink_to_hi().with_hi(assignment.span.hi()),
163                 String::new(),
164             ))
165         }))
166         .collect::<Option<Vec<(Span, String)>>>()?;
167
168     let applicability = if suggestions.len() > 1 {
169         // multiple suggestions don't work with rustfix in multipart_suggest
170         // https://github.com/rust-lang/rustfix/issues/141
171         Applicability::Unspecified
172     } else {
173         Applicability::MachineApplicable
174     };
175     Some((applicability, suggestions))
176 }
177
178 struct Usage<'tcx> {
179     stmt: &'tcx Stmt<'tcx>,
180     expr: &'tcx Expr<'tcx>,
181     needs_semi: bool,
182 }
183
184 fn first_usage<'tcx>(
185     cx: &LateContext<'tcx>,
186     binding_id: HirId,
187     local_stmt_id: HirId,
188     block: &'tcx Block<'tcx>,
189 ) -> Option<Usage<'tcx>> {
190     block
191         .stmts
192         .iter()
193         .skip_while(|stmt| stmt.hir_id != local_stmt_id)
194         .skip(1)
195         .find(|&stmt| is_local_used(cx, stmt, binding_id))
196         .and_then(|stmt| match stmt.kind {
197             StmtKind::Expr(expr) => Some(Usage {
198                 stmt,
199                 expr,
200                 needs_semi: true,
201             }),
202             StmtKind::Semi(expr) => Some(Usage {
203                 stmt,
204                 expr,
205                 needs_semi: false,
206             }),
207             _ => None,
208         })
209 }
210
211 fn local_snippet_without_semicolon(cx: &LateContext<'_>, local: &Local<'_>) -> Option<String> {
212     let span = local.span.with_hi(match local.ty {
213         // let <pat>: <ty>;
214         // ~~~~~~~~~~~~~~~
215         Some(ty) => ty.span.hi(),
216         // let <pat>;
217         // ~~~~~~~~~
218         None => local.pat.span.hi(),
219     });
220
221     snippet_opt(cx, span)
222 }
223
224 fn check<'tcx>(
225     cx: &LateContext<'tcx>,
226     local: &'tcx Local<'tcx>,
227     local_stmt: &'tcx Stmt<'tcx>,
228     block: &'tcx Block<'tcx>,
229     binding_id: HirId,
230 ) -> Option<()> {
231     let usage = first_usage(cx, binding_id, local_stmt.hir_id, block)?;
232     let binding_name = cx.tcx.hir().opt_name(binding_id)?;
233     let let_snippet = local_snippet_without_semicolon(cx, local)?;
234
235     match usage.expr.kind {
236         ExprKind::Assign(..) => {
237             let assign = LocalAssign::new(cx, usage.expr, binding_id)?;
238
239             span_lint_and_then(
240                 cx,
241                 NEEDLESS_LATE_INIT,
242                 local_stmt.span,
243                 "unneeded late initalization",
244                 |diag| {
245                     diag.tool_only_span_suggestion(
246                         local_stmt.span,
247                         "remove the local",
248                         String::new(),
249                         Applicability::MachineApplicable,
250                     );
251
252                     diag.span_suggestion(
253                         assign.lhs_span,
254                         &format!("declare `{}` here", binding_name),
255                         let_snippet,
256                         Applicability::MachineApplicable,
257                     );
258                 },
259             );
260         },
261         ExprKind::If(_, then_expr, Some(else_expr)) => {
262             let (applicability, suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?;
263
264             span_lint_and_then(
265                 cx,
266                 NEEDLESS_LATE_INIT,
267                 local_stmt.span,
268                 "unneeded late initalization",
269                 |diag| {
270                     diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
271
272                     diag.span_suggestion_verbose(
273                         usage.stmt.span.shrink_to_lo(),
274                         &format!("declare `{}` here", binding_name),
275                         format!("{} = ", let_snippet),
276                         applicability,
277                     );
278
279                     diag.multipart_suggestion("remove the assignments from the branches", suggestions, applicability);
280
281                     if usage.needs_semi {
282                         diag.span_suggestion(
283                             usage.stmt.span.shrink_to_hi(),
284                             "add a semicolon after the `if` expression",
285                             ";".to_string(),
286                             applicability,
287                         );
288                     }
289                 },
290             );
291         },
292         ExprKind::Match(_, arms, MatchSource::Normal) => {
293             let (applicability, suggestions) = assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?;
294
295             span_lint_and_then(
296                 cx,
297                 NEEDLESS_LATE_INIT,
298                 local_stmt.span,
299                 "unneeded late initalization",
300                 |diag| {
301                     diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
302
303                     diag.span_suggestion_verbose(
304                         usage.stmt.span.shrink_to_lo(),
305                         &format!("declare `{}` here", binding_name),
306                         format!("{} = ", let_snippet),
307                         applicability,
308                     );
309
310                     diag.multipart_suggestion(
311                         "remove the assignments from the `match` arms",
312                         suggestions,
313                         applicability,
314                     );
315
316                     if usage.needs_semi {
317                         diag.span_suggestion(
318                             usage.stmt.span.shrink_to_hi(),
319                             "add a semicolon after the `match` expression",
320                             ";".to_string(),
321                             applicability,
322                         );
323                     }
324                 },
325             );
326         },
327         _ => {},
328     };
329
330     Some(())
331 }
332
333 impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit {
334     fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
335         let mut parents = cx.tcx.hir().parent_iter(local.hir_id);
336
337         if_chain! {
338             if let Local {
339                 init: None,
340                 pat: &Pat {
341                     kind: PatKind::Binding(_, binding_id, _, None),
342                     ..
343                 },
344                 source: LocalSource::Normal,
345                 ..
346             } = local;
347             if let Some((_, Node::Stmt(local_stmt))) = parents.next();
348             if let Some((_, Node::Block(block))) = parents.next();
349
350             then {
351                 check(cx, local, local_stmt, block, binding_id);
352             }
353         }
354     }
355 }