Skip to content

Commit b688fb2

Browse files
committed
Change divergence checking to match the compiler's type system based definition of divergence.
1 parent 16d58a2 commit b688fb2

File tree

4 files changed

+484
-119
lines changed

4 files changed

+484
-119
lines changed

clippy_lints/src/manual_let_else.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl<'tcx> QuestionMark {
6464
IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => {
6565
if let Some(ident_map) = expr_simple_identity_map(local.pat, let_pat, if_then)
6666
&& let Some(if_else) = if_else
67-
&& is_never_expr(cx, if_else)
67+
&& is_never_expr(cx, if_else).is_some()
6868
&& let qm_allowed = is_lint_allowed(cx, QUESTION_MARK, stmt.hir_id)
6969
&& (qm_allowed || pat_and_expr_can_be_question_mark(cx, let_pat, if_else).is_none())
7070
{
@@ -88,10 +88,9 @@ impl<'tcx> QuestionMark {
8888
return;
8989
}
9090
let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes;
91-
let diverging_arm_opt = arms
92-
.iter()
93-
.enumerate()
94-
.find(|(_, arm)| is_never_expr(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+
});
9594
let Some((idx, diverging_arm)) = diverging_arm_opt else {
9695
return;
9796
};

clippy_utils/src/lib.rs

Lines changed: 236 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ pub use self::hir_utils::{
7171
both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over, HirEqInterExpr, SpanlessEq, SpanlessHash,
7272
};
7373

74+
use core::mem;
7475
use core::ops::ControlFlow;
7576
use std::collections::hash_map::Entry;
7677
use std::hash::BuildHasherDefault;
@@ -88,7 +89,7 @@ use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
8889
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
8990
use rustc_hir::{
9091
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, ItemId,
92+
ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item,
9293
ItemKind, LangItem, Local, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy,
9394
QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp,
9495
};
@@ -117,7 +118,7 @@ use crate::ty::{
117118
adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type,
118119
ty_is_fn_once_param,
119120
};
120-
use crate::visitors::{for_each_expr, Descend};
121+
use crate::visitors::for_each_expr;
121122

122123
use rustc_middle::hir::nested_filter;
123124

@@ -2975,100 +2976,263 @@ pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: i
29752976
}
29762977
}
29772978

2978-
/// Check if the expression either returns, or could be coerced into returning, `!`.
2979-
pub fn is_never_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2979+
#[derive(Clone, Copy)]
2980+
pub enum RequiresSemi {
2981+
Yes,
2982+
No,
2983+
}
2984+
impl RequiresSemi {
2985+
pub fn requires_semi(self) -> bool {
2986+
matches!(self, Self::Yes)
2987+
}
2988+
}
2989+
2990+
/// Check if the expression return `!`, a type coerced from `!`, or could return `!` if the final
2991+
/// expression were turned into a statement.
2992+
#[expect(clippy::too_many_lines)]
2993+
pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<RequiresSemi> {
2994+
struct BreakTarget {
2995+
id: HirId,
2996+
unused: bool,
2997+
}
2998+
29802999
struct V<'cx, 'tcx> {
29813000
cx: &'cx LateContext<'tcx>,
2982-
res: ControlFlow<(), Descend>,
3001+
break_targets: Vec<BreakTarget>,
3002+
break_targets_for_result_ty: u32,
3003+
in_final_expr: bool,
3004+
requires_semi: bool,
3005+
is_never: bool,
3006+
}
3007+
3008+
impl<'tcx> V<'_, 'tcx> {
3009+
fn push_break_target(&mut self, id: HirId) {
3010+
self.break_targets.push(BreakTarget { id, unused: true });
3011+
self.break_targets_for_result_ty += u32::from(self.in_final_expr);
3012+
}
29833013
}
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-
}
29923014

2993-
if self.res.is_break() {
3015+
impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
3016+
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
3017+
// Note: Part of the complexity here comes from the fact that
3018+
// coercions are applied to the innermost expression.
3019+
// e.g. In `let x: u32 = { break () };` the never-to-any coercion
3020+
// is applied to the break expression. This means we can't just
3021+
// check the block's type as it will be `u32` despite the fact
3022+
// that the block always diverges.
3023+
3024+
// The rest of the complexity comes from checking blocks which
3025+
// syntactically return a value, but will always diverge before
3026+
// reaching that point.
3027+
// e.g. In `let x = { foo(panic!()) };` the block's type will be the
3028+
// return type of `foo` even though it will never actually run. This
3029+
// can be trivially fixed by adding a semicolon after the call, but
3030+
// we must first detect that a semicolon is needed to make that
3031+
// suggestion.
3032+
3033+
if self.is_never && self.break_targets.is_empty() {
3034+
if self.in_final_expr && !self.requires_semi {
3035+
// This expression won't ever run, but we still need to check
3036+
// if it can affect the type of the final expression.
3037+
match e.kind {
3038+
ExprKind::DropTemps(e) => self.visit_expr(e),
3039+
ExprKind::If(_, then, Some(else_)) => {
3040+
self.visit_expr(then);
3041+
self.visit_expr(else_);
3042+
},
3043+
ExprKind::Match(_, arms, _) => {
3044+
for arm in arms {
3045+
self.visit_expr(arm.body);
3046+
}
3047+
},
3048+
ExprKind::Loop(b, ..) => {
3049+
self.push_break_target(e.hir_id);
3050+
self.in_final_expr = false;
3051+
self.visit_block(b);
3052+
self.break_targets.pop();
3053+
}
3054+
ExprKind::Block(b, _) => {
3055+
if b.targeted_by_break {
3056+
self.push_break_target(b.hir_id);
3057+
self.visit_block(b);
3058+
self.break_targets.pop();
3059+
} else {
3060+
self.visit_block(b);
3061+
}
3062+
},
3063+
_ => {
3064+
self.requires_semi = !self.cx.typeck_results().expr_ty(e).is_never();
3065+
},
3066+
}
3067+
}
29943068
return;
29953069
}
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)
3070+
match e.kind {
3071+
ExprKind::DropTemps(e) => {
3072+
self.visit_expr(e);
3073+
return;
3074+
},
3075+
ExprKind::Ret(None) => {
3076+
self.is_never = true;
3077+
return;
3078+
},
3079+
ExprKind::Ret(Some(e)) | ExprKind::Become(e) => {
3080+
self.in_final_expr = false;
3081+
self.visit_expr(e);
3082+
self.is_never = true;
3083+
return;
3084+
},
3085+
ExprKind::Continue(_) => {
3086+
self.is_never = true;
3087+
return;
3088+
},
3089+
ExprKind::Break(dest, e) => {
3090+
if let Some(e) = e {
3091+
self.in_final_expr = false;
3092+
self.visit_expr(e);
30083093
}
3094+
if let Ok(id) = dest.target_id
3095+
&& let Some((i, target)) = self
3096+
.break_targets
3097+
.iter_mut()
3098+
.enumerate()
3099+
.find(|(_, target)| target.id == id)
3100+
{
3101+
target.unused &= self.is_never;
3102+
if i < self.break_targets_for_result_ty as usize {
3103+
self.requires_semi = true;
3104+
}
3105+
}
3106+
self.is_never = true;
3107+
return;
30093108
},
3010-
ExprKind::MethodCall(..) => {
3011-
if is_never(self.cx, e) {
3012-
ControlFlow::Break(())
3109+
ExprKind::If(cond, then, else_) => {
3110+
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3111+
self.visit_expr(cond);
3112+
self.in_final_expr = in_final_expr;
3113+
3114+
if self.is_never {
3115+
self.visit_expr(then);
3116+
if let Some(else_) = else_ {
3117+
self.visit_expr(else_);
3118+
}
30133119
} else {
3014-
ControlFlow::Continue(Descend::Yes)
3120+
self.visit_expr(then);
3121+
let is_never = mem::replace(&mut self.is_never, false);
3122+
if let Some(else_) = else_ {
3123+
self.visit_expr(else_);
3124+
self.is_never &= is_never;
3125+
}
30153126
}
3127+
return;
30163128
},
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(())
3129+
ExprKind::Match(scrutinee, arms, _) => {
3130+
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3131+
self.visit_expr(scrutinee);
3132+
self.in_final_expr = in_final_expr;
3133+
3134+
if self.is_never {
3135+
for arm in arms {
3136+
self.visit_arm(arm);
3137+
}
30233138
} else {
3024-
ControlFlow::Continue(Descend::No)
3139+
let mut is_never = true;
3140+
for arm in arms {
3141+
self.is_never = false;
3142+
if let Some(guard) = arm.guard {
3143+
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3144+
self.visit_expr(guard.body());
3145+
self.in_final_expr = in_final_expr;
3146+
// The compiler doesn't consider diverging guards as causing the arm to diverge.
3147+
self.is_never = false;
3148+
}
3149+
self.visit_expr(arm.body);
3150+
is_never &= self.is_never;
3151+
}
3152+
self.is_never = is_never;
30253153
}
3154+
return;
30263155
},
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(())
3156+
ExprKind::Loop(b, _, _, _) => {
3157+
self.push_break_target(e.hir_id);
3158+
self.in_final_expr = false;
3159+
self.visit_block(b);
3160+
self.is_never = self.break_targets.pop().unwrap().unused;
3161+
return;
3162+
},
3163+
ExprKind::Block(b, _) => {
3164+
if b.targeted_by_break {
3165+
self.push_break_target(b.hir_id);
3166+
self.visit_block(b);
3167+
self.is_never &= self.break_targets.pop().unwrap().unused;
30353168
} else {
3036-
ControlFlow::Continue(Descend::No)
3169+
self.visit_block(b);
30373170
}
3171+
return;
30383172
},
3173+
_ => {
3174+
self.in_final_expr = false;
3175+
walk_expr(self, e);
3176+
self.is_never |= self.cx.typeck_results().expr_ty(e).is_never();
3177+
},
3178+
}
3179+
}
30393180

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);
3181+
fn visit_block(&mut self, b: &'tcx Block<'_>) {
3182+
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3183+
for s in b.stmts {
3184+
self.visit_stmt(s);
3185+
}
3186+
self.in_final_expr = in_final_expr;
3187+
if let Some(e) = b.expr {
3188+
self.visit_expr(e);
30493189
}
30503190
}
30513191

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);
3192+
fn visit_local(&mut self, l: &'tcx Local<'_>) {
3193+
if let Some(e) = l.init {
3194+
self.visit_expr(e);
3195+
}
3196+
if let Some(else_) = l.els {
3197+
let is_never = self.is_never;
3198+
self.visit_block(else_);
3199+
self.is_never = is_never;
30573200
}
30583201
}
30593202

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) {}
3203+
fn visit_arm(&mut self, arm: &Arm<'tcx>) {
3204+
if let Some(guard) = arm.guard {
3205+
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3206+
self.visit_expr(guard.body());
3207+
self.in_final_expr = in_final_expr;
3208+
}
3209+
self.visit_expr(arm.body);
3210+
}
30663211
}
30673212

3068-
let mut v = V {
3069-
cx,
3070-
res: ControlFlow::Continue(Descend::Yes),
3071-
};
3072-
expr.visit(&mut v);
3073-
v.res.is_break()
3213+
if cx.typeck_results().expr_ty(e).is_never() {
3214+
Some(RequiresSemi::No)
3215+
} else if let ExprKind::Block(b, _) = e.kind
3216+
&& !b.targeted_by_break
3217+
&& b.expr.is_none()
3218+
{
3219+
// If a block diverges without a final expression then it's type is `!`.
3220+
None
3221+
} else {
3222+
let mut v = V {
3223+
cx,
3224+
break_targets: Vec::new(),
3225+
break_targets_for_result_ty: 0,
3226+
in_final_expr: true,
3227+
requires_semi: false,
3228+
is_never: false,
3229+
};
3230+
v.visit_expr(e);
3231+
v.is_never
3232+
.then_some(if v.requires_semi && matches!(e.kind, ExprKind::Block(..)) {
3233+
RequiresSemi::Yes
3234+
} else {
3235+
RequiresSemi::No
3236+
})
3237+
}
30743238
}

0 commit comments

Comments
 (0)