]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/manual_let_else.rs
New upstream version 1.69.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / manual_let_else.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::higher::IfLetOrMatch;
3 use clippy_utils::msrvs::{self, Msrv};
4 use clippy_utils::peel_blocks;
5 use clippy_utils::source::snippet_with_context;
6 use clippy_utils::ty::is_type_diagnostic_item;
7 use clippy_utils::visitors::{Descend, Visitable};
8 use if_chain::if_chain;
9 use rustc_data_structures::fx::FxHashSet;
10 use rustc_errors::Applicability;
11 use rustc_hir::intravisit::{walk_expr, Visitor};
12 use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty};
13 use rustc_lint::{LateContext, LateLintPass, LintContext};
14 use rustc_middle::lint::in_external_macro;
15 use rustc_session::{declare_tool_lint, impl_lint_pass};
16 use rustc_span::symbol::sym;
17 use rustc_span::Span;
18 use serde::Deserialize;
19 use std::ops::ControlFlow;
20
21 declare_clippy_lint! {
22 /// ### What it does
23 ///
24 /// Warn of cases where `let...else` could be used
25 ///
26 /// ### Why is this bad?
27 ///
28 /// `let...else` provides a standard construct for this pattern
29 /// that people can easily recognize. It's also more compact.
30 ///
31 /// ### Example
32 ///
33 /// ```rust
34 /// # let w = Some(0);
35 /// let v = if let Some(v) = w { v } else { return };
36 /// ```
37 ///
38 /// Could be written:
39 ///
40 /// ```rust
41 /// # #![feature(let_else)]
42 /// # fn main () {
43 /// # let w = Some(0);
44 /// let Some(v) = w else { return };
45 /// # }
46 /// ```
47 #[clippy::version = "1.67.0"]
48 pub MANUAL_LET_ELSE,
49 pedantic,
50 "manual implementation of a let...else statement"
51 }
52
53 pub struct ManualLetElse {
54 msrv: Msrv,
55 matches_behaviour: MatchLintBehaviour,
56 }
57
58 impl ManualLetElse {
59 #[must_use]
60 pub fn new(msrv: Msrv, matches_behaviour: MatchLintBehaviour) -> Self {
61 Self {
62 msrv,
63 matches_behaviour,
64 }
65 }
66 }
67
68 impl_lint_pass!(ManualLetElse => [MANUAL_LET_ELSE]);
69
70 impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
71 fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
72 let if_let_or_match = if_chain! {
73 if self.msrv.meets(msrvs::LET_ELSE);
74 if !in_external_macro(cx.sess(), stmt.span);
75 if let StmtKind::Local(local) = stmt.kind;
76 if let Some(init) = local.init;
77 if local.els.is_none();
78 if local.ty.is_none();
79 if init.span.ctxt() == stmt.span.ctxt();
80 if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init);
81 then {
82 if_let_or_match
83 } else {
84 return;
85 }
86 };
87
88 match if_let_or_match {
89 IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => if_chain! {
90 if expr_is_simple_identity(let_pat, if_then);
91 if let Some(if_else) = if_else;
92 if expr_diverges(cx, if_else);
93 then {
94 emit_manual_let_else(cx, stmt.span, if_let_expr, let_pat, if_else);
95 }
96 },
97 IfLetOrMatch::Match(match_expr, arms, source) => {
98 if self.matches_behaviour == MatchLintBehaviour::Never {
99 return;
100 }
101 if source != MatchSource::Normal {
102 return;
103 }
104 // Any other number than two arms doesn't (neccessarily)
105 // have a trivial mapping to let else.
106 if arms.len() != 2 {
107 return;
108 }
109 // Guards don't give us an easy mapping either
110 if arms.iter().any(|arm| arm.guard.is_some()) {
111 return;
112 }
113 let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes;
114 let diverging_arm_opt = arms
115 .iter()
116 .enumerate()
117 .find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types));
118 let Some((idx, diverging_arm)) = diverging_arm_opt else { return; };
119 // If the non-diverging arm is the first one, its pattern can be reused in a let/else statement.
120 // However, if it arrives in second position, its pattern may cover some cases already covered
121 // by the diverging one.
122 // TODO: accept the non-diverging arm as a second position if patterns are disjointed.
123 if idx == 0 {
124 return;
125 }
126 let pat_arm = &arms[1 - idx];
127 if !expr_is_simple_identity(pat_arm.pat, pat_arm.body) {
128 return;
129 }
130
131 emit_manual_let_else(cx, stmt.span, match_expr, pat_arm.pat, diverging_arm.body);
132 },
133 }
134 }
135
136 extract_msrv_attr!(LateContext);
137 }
138
139 fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat: &Pat<'_>, else_body: &Expr<'_>) {
140 span_lint_and_then(
141 cx,
142 MANUAL_LET_ELSE,
143 span,
144 "this could be rewritten as `let...else`",
145 |diag| {
146 // This is far from perfect, for example there needs to be:
147 // * mut additions for the bindings
148 // * renamings of the bindings
149 // * unused binding collision detection with existing ones
150 // * putting patterns with at the top level | inside ()
151 // for this to be machine applicable.
152 let mut app = Applicability::HasPlaceholders;
153 let (sn_pat, _) = snippet_with_context(cx, pat.span, span.ctxt(), "", &mut app);
154 let (sn_expr, _) = snippet_with_context(cx, expr.span, span.ctxt(), "", &mut app);
155 let (sn_else, _) = snippet_with_context(cx, else_body.span, span.ctxt(), "", &mut app);
156
157 let else_bl = if matches!(else_body.kind, ExprKind::Block(..)) {
158 sn_else.into_owned()
159 } else {
160 format!("{{ {sn_else} }}")
161 };
162 let sn_bl = if matches!(pat.kind, PatKind::Or(..)) {
163 format!("({sn_pat})")
164 } else {
165 sn_pat.into_owned()
166 };
167 let sugg = format!("let {sn_bl} = {sn_expr} else {else_bl};");
168 diag.span_suggestion(span, "consider writing", sugg, app);
169 },
170 );
171 }
172
173 /// Check whether an expression is divergent. May give false negatives.
174 fn expr_diverges(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
175 struct V<'cx, 'tcx> {
176 cx: &'cx LateContext<'tcx>,
177 res: ControlFlow<(), Descend>,
178 }
179 impl<'tcx> Visitor<'tcx> for V<'_, '_> {
180 fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
181 fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
182 if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
183 return ty.is_never();
184 }
185 false
186 }
187
188 if self.res.is_break() {
189 return;
190 }
191
192 // We can't just call is_never on expr and be done, because the type system
193 // sometimes coerces the ! type to something different before we can get
194 // our hands on it. So instead, we do a manual search. We do fall back to
195 // is_never in some places when there is no better alternative.
196 self.res = match e.kind {
197 ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()),
198 ExprKind::Call(call, _) => {
199 if is_never(self.cx, e) || is_never(self.cx, call) {
200 ControlFlow::Break(())
201 } else {
202 ControlFlow::Continue(Descend::Yes)
203 }
204 },
205 ExprKind::MethodCall(..) => {
206 if is_never(self.cx, e) {
207 ControlFlow::Break(())
208 } else {
209 ControlFlow::Continue(Descend::Yes)
210 }
211 },
212 ExprKind::If(if_expr, if_then, if_else) => {
213 let else_diverges = if_else.map_or(false, |ex| expr_diverges(self.cx, ex));
214 let diverges =
215 expr_diverges(self.cx, if_expr) || (else_diverges && expr_diverges(self.cx, if_then));
216 if diverges {
217 ControlFlow::Break(())
218 } else {
219 ControlFlow::Continue(Descend::No)
220 }
221 },
222 ExprKind::Match(match_expr, match_arms, _) => {
223 let diverges = expr_diverges(self.cx, match_expr)
224 || match_arms.iter().all(|arm| {
225 let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(self.cx, g.body()));
226 guard_diverges || expr_diverges(self.cx, arm.body)
227 });
228 if diverges {
229 ControlFlow::Break(())
230 } else {
231 ControlFlow::Continue(Descend::No)
232 }
233 },
234
235 // Don't continue into loops or labeled blocks, as they are breakable,
236 // and we'd have to start checking labels.
237 ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
238
239 // Default: descend
240 _ => ControlFlow::Continue(Descend::Yes),
241 };
242 if let ControlFlow::Continue(Descend::Yes) = self.res {
243 walk_expr(self, e);
244 }
245 }
246
247 fn visit_local(&mut self, local: &'tcx Local<'_>) {
248 // Don't visit the else block of a let/else statement as it will not make
249 // the statement divergent even though the else block is divergent.
250 if let Some(init) = local.init {
251 self.visit_expr(init);
252 }
253 }
254
255 // Avoid unnecessary `walk_*` calls.
256 fn visit_ty(&mut self, _: &'tcx Ty<'tcx>) {}
257 fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
258 fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
259 // Avoid monomorphising all `visit_*` functions.
260 fn visit_nested_item(&mut self, _: ItemId) {}
261 }
262
263 let mut v = V {
264 cx,
265 res: ControlFlow::Continue(Descend::Yes),
266 };
267 expr.visit(&mut v);
268 v.res.is_break()
269 }
270
271 fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool {
272 // Check whether the pattern contains any bindings, as the
273 // binding might potentially be used in the body.
274 // TODO: only look for *used* bindings.
275 let mut has_bindings = false;
276 pat.each_binding_or_first(&mut |_, _, _, _| has_bindings = true);
277 if has_bindings {
278 return false;
279 }
280
281 // If we shouldn't check the types, exit early.
282 if !check_types {
283 return true;
284 }
285
286 // Check whether any possibly "unknown" patterns are included,
287 // because users might not know which values some enum has.
288 // Well-known enums are excepted, as we assume people know them.
289 // We do a deep check, to be able to disallow Err(En::Foo(_))
290 // for usage of the En::Foo variant, as we disallow En::Foo(_),
291 // but we allow Err(_).
292 let typeck_results = cx.typeck_results();
293 let mut has_disallowed = false;
294 pat.walk_always(|pat| {
295 // Only do the check if the type is "spelled out" in the pattern
296 if !matches!(
297 pat.kind,
298 PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Path(..)
299 ) {
300 return;
301 };
302 let ty = typeck_results.pat_ty(pat);
303 // Option and Result are allowed, everything else isn't.
304 if !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) {
305 has_disallowed = true;
306 }
307 });
308 !has_disallowed
309 }
310
311 /// Checks if the passed block is a simple identity referring to bindings created by the pattern
312 fn expr_is_simple_identity(pat: &'_ Pat<'_>, expr: &'_ Expr<'_>) -> bool {
313 // We support patterns with multiple bindings and tuples, like:
314 // let ... = if let (Some(foo), bar) = g() { (foo, bar) } else { ... }
315 let peeled = peel_blocks(expr);
316 let paths = match peeled.kind {
317 ExprKind::Tup(exprs) | ExprKind::Array(exprs) => exprs,
318 ExprKind::Path(_) => std::slice::from_ref(peeled),
319 _ => return false,
320 };
321 let mut pat_bindings = FxHashSet::default();
322 pat.each_binding_or_first(&mut |_ann, _hir_id, _sp, ident| {
323 pat_bindings.insert(ident);
324 });
325 if pat_bindings.len() < paths.len() {
326 return false;
327 }
328 for path in paths {
329 if_chain! {
330 if let ExprKind::Path(QPath::Resolved(_ty, path)) = path.kind;
331 if let [path_seg] = path.segments;
332 then {
333 if !pat_bindings.remove(&path_seg.ident) {
334 return false;
335 }
336 } else {
337 return false;
338 }
339 }
340 }
341 true
342 }
343
344 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize)]
345 pub enum MatchLintBehaviour {
346 AllTypes,
347 WellKnownTypes,
348 Never,
349 }