Skip to content

Commit 6a15f3b

Browse files
committed
Auto merge of #11787 - Jarcho:divergence_check, r=dswij
Fixes to `manual_let_else`'s divergence check A few changes to the divergence check in `manual_let_else` and moves it the implementation to `clippy_utils` since it's generally useful: * Handle internal `break` and `continue` expressions. e.g. The first loop is divergent, but the second is not. ```rust { loop { break 'outer; }; } { loop { break; }; } ``` * Match rust's definition of divergence which is defined via the type system. e.g. The following is not considered divergent by rustc as the inner block has a result type of `()`: ```rust { 'a: { panic!(); break 'a; }; } ``` * Handle when adding a single semicolon would make the expression divergent. e.g. The following would be a divergent if a semicolon were added after the `if` expression: ```rust { if panic!() { 0 } else { 1 } } ``` changelog: None
2 parents 886d5fb + a44bb07 commit 6a15f3b

File tree

4 files changed

+496
-151
lines changed

4 files changed

+496
-151
lines changed

clippy_lints/src/manual_let_else.rs

Lines changed: 7 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,15 @@ use clippy_utils::diagnostics::span_lint_and_then;
55
use clippy_utils::higher::IfLetOrMatch;
66
use clippy_utils::source::snippet_with_context;
77
use clippy_utils::ty::is_type_diagnostic_item;
8-
use clippy_utils::visitors::{Descend, Visitable};
9-
use clippy_utils::{is_lint_allowed, pat_and_expr_can_be_question_mark, peel_blocks};
8+
use clippy_utils::{is_lint_allowed, is_never_expr, pat_and_expr_can_be_question_mark, peel_blocks};
109
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1110
use rustc_errors::Applicability;
12-
use rustc_hir::intravisit::{walk_expr, Visitor};
13-
use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty};
11+
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
1412
use rustc_lint::{LateContext, LintContext};
1513
use rustc_middle::lint::in_external_macro;
1614
use rustc_session::declare_tool_lint;
1715
use rustc_span::symbol::{sym, Symbol};
1816
use rustc_span::Span;
19-
use std::ops::ControlFlow;
2017
use std::slice;
2118

2219
declare_clippy_lint! {
@@ -51,7 +48,7 @@ declare_clippy_lint! {
5148
}
5249

5350
impl<'tcx> QuestionMark {
54-
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
51+
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) {
5552
if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) {
5653
return;
5754
}
@@ -67,7 +64,7 @@ impl<'tcx> QuestionMark {
6764
IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => {
6865
if let Some(ident_map) = expr_simple_identity_map(local.pat, let_pat, if_then)
6966
&& let Some(if_else) = if_else
70-
&& expr_diverges(cx, if_else)
67+
&& is_never_expr(cx, if_else).is_some()
7168
&& let qm_allowed = is_lint_allowed(cx, QUESTION_MARK, stmt.hir_id)
7269
&& (qm_allowed || pat_and_expr_can_be_question_mark(cx, let_pat, if_else).is_none())
7370
{
@@ -91,10 +88,9 @@ impl<'tcx> QuestionMark {
9188
return;
9289
}
9390
let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes;
94-
let diverging_arm_opt = arms
95-
.iter()
96-
.enumerate()
97-
.find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types));
91+
let diverging_arm_opt = arms.iter().enumerate().find(|(_, arm)| {
92+
is_never_expr(cx, arm.body).is_some() && pat_allowed_for_else(cx, arm.pat, check_types)
93+
});
9894
let Some((idx, diverging_arm)) = diverging_arm_opt else {
9995
return;
10096
};
@@ -272,104 +268,6 @@ fn replace_in_pattern(
272268
sn_pat.into_owned()
273269
}
274270

275-
/// Check whether an expression is divergent. May give false negatives.
276-
fn expr_diverges(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
277-
struct V<'cx, 'tcx> {
278-
cx: &'cx LateContext<'tcx>,
279-
res: ControlFlow<(), Descend>,
280-
}
281-
impl<'tcx> Visitor<'tcx> for V<'_, '_> {
282-
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
283-
fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
284-
if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
285-
return ty.is_never();
286-
}
287-
false
288-
}
289-
290-
if self.res.is_break() {
291-
return;
292-
}
293-
294-
// We can't just call is_never on expr and be done, because the type system
295-
// sometimes coerces the ! type to something different before we can get
296-
// our hands on it. So instead, we do a manual search. We do fall back to
297-
// is_never in some places when there is no better alternative.
298-
self.res = match e.kind {
299-
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()),
300-
ExprKind::Call(call, _) => {
301-
if is_never(self.cx, e) || is_never(self.cx, call) {
302-
ControlFlow::Break(())
303-
} else {
304-
ControlFlow::Continue(Descend::Yes)
305-
}
306-
},
307-
ExprKind::MethodCall(..) => {
308-
if is_never(self.cx, e) {
309-
ControlFlow::Break(())
310-
} else {
311-
ControlFlow::Continue(Descend::Yes)
312-
}
313-
},
314-
ExprKind::If(if_expr, if_then, if_else) => {
315-
let else_diverges = if_else.map_or(false, |ex| expr_diverges(self.cx, ex));
316-
let diverges =
317-
expr_diverges(self.cx, if_expr) || (else_diverges && expr_diverges(self.cx, if_then));
318-
if diverges {
319-
ControlFlow::Break(())
320-
} else {
321-
ControlFlow::Continue(Descend::No)
322-
}
323-
},
324-
ExprKind::Match(match_expr, match_arms, _) => {
325-
let diverges = expr_diverges(self.cx, match_expr)
326-
|| match_arms.iter().all(|arm| {
327-
let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(self.cx, g.body()));
328-
guard_diverges || expr_diverges(self.cx, arm.body)
329-
});
330-
if diverges {
331-
ControlFlow::Break(())
332-
} else {
333-
ControlFlow::Continue(Descend::No)
334-
}
335-
},
336-
337-
// Don't continue into loops or labeled blocks, as they are breakable,
338-
// and we'd have to start checking labels.
339-
ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
340-
341-
// Default: descend
342-
_ => ControlFlow::Continue(Descend::Yes),
343-
};
344-
if let ControlFlow::Continue(Descend::Yes) = self.res {
345-
walk_expr(self, e);
346-
}
347-
}
348-
349-
fn visit_local(&mut self, local: &'tcx Local<'_>) {
350-
// Don't visit the else block of a let/else statement as it will not make
351-
// the statement divergent even though the else block is divergent.
352-
if let Some(init) = local.init {
353-
self.visit_expr(init);
354-
}
355-
}
356-
357-
// Avoid unnecessary `walk_*` calls.
358-
fn visit_ty(&mut self, _: &'tcx Ty<'tcx>) {}
359-
fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
360-
fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
361-
// Avoid monomorphising all `visit_*` functions.
362-
fn visit_nested_item(&mut self, _: ItemId) {}
363-
}
364-
365-
let mut v = V {
366-
cx,
367-
res: ControlFlow::Continue(Descend::Yes),
368-
};
369-
expr.visit(&mut v);
370-
v.res.is_break()
371-
}
372-
373271
fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool {
374272
// Check whether the pattern contains any bindings, as the
375273
// binding might potentially be used in the body.

0 commit comments

Comments
 (0)