]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/let_if_seq.rs
New upstream version 1.53.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / let_if_seq.rs
CommitLineData
cdc7bbd5
XL
1use clippy_utils::diagnostics::span_lint_and_then;
2use clippy_utils::source::snippet;
3use clippy_utils::{path_to_local_id, visitors::LocalUsedVisitor};
f20569fa
XL
4use if_chain::if_chain;
5use rustc_errors::Applicability;
6use rustc_hir as hir;
7use rustc_hir::BindingAnnotation;
8use rustc_lint::{LateContext, LateLintPass};
9use rustc_session::{declare_lint_pass, declare_tool_lint};
10
11declare_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
56declare_lint_pass!(LetIfSeq => [USELESS_LET_IF_SEQ]);
57
58impl<'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
139fn 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}