Skip to content

Commit 16d58a2

Browse files
committed
Lift expr_diverges to clippy_utils as is_never_expr
1 parent 789bc73 commit 16d58a2

File tree

2 files changed

+105
-108
lines changed

2 files changed

+105
-108
lines changed

clippy_lints/src/manual_let_else.rs

Lines changed: 5 additions & 106 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)
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
{
@@ -94,7 +91,7 @@ impl<'tcx> QuestionMark {
9491
let diverging_arm_opt = arms
9592
.iter()
9693
.enumerate()
97-
.find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types));
94+
.find(|(_, arm)| is_never_expr(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types));
9895
let Some((idx, diverging_arm)) = diverging_arm_opt else {
9996
return;
10097
};
@@ -272,104 +269,6 @@ fn replace_in_pattern(
272269
sn_pat.into_owned()
273270
}
274271

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-
373272
fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool {
374273
// Check whether the pattern contains any bindings, as the
375274
// binding might potentially be used in the body.

clippy_utils/src/lib.rs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
8888
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
8989
use rustc_hir::{
9090
self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Destination, Expr,
91-
ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item,
91+
ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item, ItemId,
9292
ItemKind, LangItem, Local, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy,
9393
QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp,
9494
};
@@ -117,7 +117,7 @@ use crate::ty::{
117117
adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type,
118118
ty_is_fn_once_param,
119119
};
120-
use crate::visitors::for_each_expr;
120+
use crate::visitors::{for_each_expr, Descend};
121121

122122
use rustc_middle::hir::nested_filter;
123123

@@ -2974,3 +2974,101 @@ pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: i
29742974
_ => false,
29752975
}
29762976
}
2977+
2978+
/// Check if the expression either returns, or could be coerced into returning, `!`.
2979+
pub fn is_never_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2980+
struct V<'cx, 'tcx> {
2981+
cx: &'cx LateContext<'tcx>,
2982+
res: ControlFlow<(), Descend>,
2983+
}
2984+
impl<'tcx> Visitor<'tcx> for V<'_, '_> {
2985+
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
2986+
fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
2987+
if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
2988+
return ty.is_never();
2989+
}
2990+
false
2991+
}
2992+
2993+
if self.res.is_break() {
2994+
return;
2995+
}
2996+
2997+
// We can't just call is_never on expr and be done, because the type system
2998+
// sometimes coerces the ! type to something different before we can get
2999+
// our hands on it. So instead, we do a manual search. We do fall back to
3000+
// is_never in some places when there is no better alternative.
3001+
self.res = match e.kind {
3002+
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()),
3003+
ExprKind::Call(call, _) => {
3004+
if is_never(self.cx, e) || is_never(self.cx, call) {
3005+
ControlFlow::Break(())
3006+
} else {
3007+
ControlFlow::Continue(Descend::Yes)
3008+
}
3009+
},
3010+
ExprKind::MethodCall(..) => {
3011+
if is_never(self.cx, e) {
3012+
ControlFlow::Break(())
3013+
} else {
3014+
ControlFlow::Continue(Descend::Yes)
3015+
}
3016+
},
3017+
ExprKind::If(if_expr, if_then, if_else) => {
3018+
let else_diverges = if_else.map_or(false, |ex| is_never_expr(self.cx, ex));
3019+
let diverges =
3020+
is_never_expr(self.cx, if_expr) || (else_diverges && is_never_expr(self.cx, if_then));
3021+
if diverges {
3022+
ControlFlow::Break(())
3023+
} else {
3024+
ControlFlow::Continue(Descend::No)
3025+
}
3026+
},
3027+
ExprKind::Match(match_expr, match_arms, _) => {
3028+
let diverges = is_never_expr(self.cx, match_expr)
3029+
|| match_arms.iter().all(|arm| {
3030+
let guard_diverges = arm.guard.as_ref().map_or(false, |g| is_never_expr(self.cx, g.body()));
3031+
guard_diverges || is_never_expr(self.cx, arm.body)
3032+
});
3033+
if diverges {
3034+
ControlFlow::Break(())
3035+
} else {
3036+
ControlFlow::Continue(Descend::No)
3037+
}
3038+
},
3039+
3040+
// Don't continue into loops or labeled blocks, as they are breakable,
3041+
// and we'd have to start checking labels.
3042+
ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
3043+
3044+
// Default: descend
3045+
_ => ControlFlow::Continue(Descend::Yes),
3046+
};
3047+
if let ControlFlow::Continue(Descend::Yes) = self.res {
3048+
walk_expr(self, e);
3049+
}
3050+
}
3051+
3052+
fn visit_local(&mut self, local: &'tcx Local<'_>) {
3053+
// Don't visit the else block of a let/else statement as it will not make
3054+
// the statement divergent even though the else block is divergent.
3055+
if let Some(init) = local.init {
3056+
self.visit_expr(init);
3057+
}
3058+
}
3059+
3060+
// Avoid unnecessary `walk_*` calls.
3061+
fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {}
3062+
fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
3063+
fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
3064+
// Avoid monomorphising all `visit_*` functions.
3065+
fn visit_nested_item(&mut self, _: ItemId) {}
3066+
}
3067+
3068+
let mut v = V {
3069+
cx,
3070+
res: ControlFlow::Continue(Descend::Yes),
3071+
};
3072+
expr.visit(&mut v);
3073+
v.res.is_break()
3074+
}

0 commit comments

Comments
 (0)