]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use clippy_utils::diagnostics::span_lint_and_then; |
2 | use clippy_utils::source::snippet; | |
3 | use clippy_utils::{path_to_local_id, visitors::LocalUsedVisitor}; | |
f20569fa XL |
4 | use if_chain::if_chain; |
5 | use rustc_errors::Applicability; | |
6 | use rustc_hir as hir; | |
7 | use rustc_hir::BindingAnnotation; | |
8 | use rustc_lint::{LateContext, LateLintPass}; | |
9 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
10 | ||
11 | declare_clippy_lint! { | |
12 | /// **What it does:** Checks for variable declarations immediately followed by a | |
13 | /// conditional affectation. | |
14 | /// | |
15 | /// **Why is this bad?** This is not idiomatic Rust. | |
16 | /// | |
17 | /// **Known problems:** None. | |
18 | /// | |
19 | /// **Example:** | |
20 | /// ```rust,ignore | |
21 | /// let foo; | |
22 | /// | |
23 | /// if bar() { | |
24 | /// foo = 42; | |
25 | /// } else { | |
26 | /// foo = 0; | |
27 | /// } | |
28 | /// | |
29 | /// let mut baz = None; | |
30 | /// | |
31 | /// if bar() { | |
32 | /// baz = Some(42); | |
33 | /// } | |
34 | /// ``` | |
35 | /// | |
36 | /// should be written | |
37 | /// | |
38 | /// ```rust,ignore | |
39 | /// let foo = if bar() { | |
40 | /// 42 | |
41 | /// } else { | |
42 | /// 0 | |
43 | /// }; | |
44 | /// | |
45 | /// let baz = if bar() { | |
46 | /// Some(42) | |
47 | /// } else { | |
48 | /// None | |
49 | /// }; | |
50 | /// ``` | |
51 | pub USELESS_LET_IF_SEQ, | |
52 | nursery, | |
53 | "unidiomatic `let mut` declaration followed by initialization in `if`" | |
54 | } | |
55 | ||
56 | declare_lint_pass!(LetIfSeq => [USELESS_LET_IF_SEQ]); | |
57 | ||
58 | impl<'tcx> LateLintPass<'tcx> for LetIfSeq { | |
59 | fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { | |
60 | let mut it = block.stmts.iter().peekable(); | |
61 | while let Some(stmt) = it.next() { | |
62 | if_chain! { | |
63 | if let Some(expr) = it.peek(); | |
cdc7bbd5 | 64 | if let hir::StmtKind::Local(local) = stmt.kind; |
f20569fa | 65 | if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind; |
cdc7bbd5 XL |
66 | if let hir::StmtKind::Expr(if_) = expr.kind; |
67 | if let hir::ExprKind::If(cond, then, ref else_) = if_.kind; | |
f20569fa XL |
68 | let mut used_visitor = LocalUsedVisitor::new(cx, canonical_id); |
69 | if !used_visitor.check_expr(cond); | |
cdc7bbd5 | 70 | if let hir::ExprKind::Block(then, _) = then.kind; |
f20569fa XL |
71 | if let Some(value) = check_assign(cx, canonical_id, &*then); |
72 | if !used_visitor.check_expr(value); | |
73 | then { | |
74 | let span = stmt.span.to(if_.span); | |
75 | ||
76 | let has_interior_mutability = !cx.typeck_results().node_type(canonical_id).is_freeze( | |
77 | cx.tcx.at(span), | |
78 | cx.param_env, | |
79 | ); | |
80 | if has_interior_mutability { return; } | |
81 | ||
cdc7bbd5 XL |
82 | let (default_multi_stmts, default) = if let Some(else_) = *else_ { |
83 | if let hir::ExprKind::Block(else_, _) = else_.kind { | |
f20569fa XL |
84 | if let Some(default) = check_assign(cx, canonical_id, else_) { |
85 | (else_.stmts.len() > 1, default) | |
cdc7bbd5 XL |
86 | } else if let Some(default) = local.init { |
87 | (true, default) | |
f20569fa XL |
88 | } else { |
89 | continue; | |
90 | } | |
91 | } else { | |
92 | continue; | |
93 | } | |
cdc7bbd5 XL |
94 | } else if let Some(default) = local.init { |
95 | (false, default) | |
f20569fa XL |
96 | } else { |
97 | continue; | |
98 | }; | |
99 | ||
100 | let mutability = match mode { | |
101 | BindingAnnotation::RefMut | BindingAnnotation::Mutable => "<mut> ", | |
102 | _ => "", | |
103 | }; | |
104 | ||
105 | // FIXME: this should not suggest `mut` if we can detect that the variable is not | |
106 | // use mutably after the `if` | |
107 | ||
108 | let sug = format!( | |
109 | "let {mut}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};", | |
110 | mut=mutability, | |
111 | name=ident.name, | |
112 | cond=snippet(cx, cond.span, "_"), | |
113 | then=if then.stmts.len() > 1 { " ..;" } else { "" }, | |
114 | else=if default_multi_stmts { " ..;" } else { "" }, | |
115 | value=snippet(cx, value.span, "<value>"), | |
116 | default=snippet(cx, default.span, "<default>"), | |
117 | ); | |
118 | span_lint_and_then(cx, | |
119 | USELESS_LET_IF_SEQ, | |
120 | span, | |
121 | "`if _ { .. } else { .. }` is an expression", | |
122 | |diag| { | |
123 | diag.span_suggestion( | |
124 | span, | |
125 | "it is more idiomatic to write", | |
126 | sug, | |
127 | Applicability::HasPlaceholders, | |
128 | ); | |
129 | if !mutability.is_empty() { | |
130 | diag.note("you might not need `mut` at all"); | |
131 | } | |
132 | }); | |
133 | } | |
134 | } | |
135 | } | |
136 | } | |
137 | } | |
138 | ||
139 | fn check_assign<'tcx>( | |
140 | cx: &LateContext<'tcx>, | |
141 | decl: hir::HirId, | |
142 | block: &'tcx hir::Block<'_>, | |
143 | ) -> Option<&'tcx hir::Expr<'tcx>> { | |
144 | if_chain! { | |
145 | if block.expr.is_none(); | |
146 | if let Some(expr) = block.stmts.iter().last(); | |
cdc7bbd5 XL |
147 | if let hir::StmtKind::Semi(expr) = expr.kind; |
148 | if let hir::ExprKind::Assign(var, value, _) = expr.kind; | |
f20569fa XL |
149 | if path_to_local_id(var, decl); |
150 | then { | |
151 | let mut v = LocalUsedVisitor::new(cx, decl); | |
152 | ||
153 | if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| v.check_stmt(stmt)) { | |
154 | return None; | |
155 | } | |
156 | ||
157 | return Some(value); | |
158 | } | |
159 | } | |
160 | ||
161 | None | |
162 | } |