]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/needless_late_init.rs
New upstream version 1.60.0+dfsg1
[rustc.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 }