Skip to content

Commit 4c28fdd

Browse files
committed
Auto merge of rust-lang#10332 - samueltardieu:issue-10296, r=Alexendoo
manual_let_else: let/else is not divergent by default The divergent `else` block of a `let`/`else` statement does not make the `let/else` statement itself divergent. Fixes rust-lang#10296 changelog: [`manual_let_else`]: do not consider `let`/`else` to be divergent by default
2 parents 0e40f94 + 7f15a11 commit 4c28fdd

File tree

2 files changed

+105
-52
lines changed

2 files changed

+105
-52
lines changed

clippy_lints/src/manual_let_else.rs

Lines changed: 94 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ use clippy_utils::msrvs::{self, Msrv};
44
use clippy_utils::peel_blocks;
55
use clippy_utils::source::snippet_with_context;
66
use clippy_utils::ty::is_type_diagnostic_item;
7-
use clippy_utils::visitors::{for_each_expr, Descend};
7+
use clippy_utils::visitors::{Descend, Visitable};
88
use if_chain::if_chain;
99
use rustc_data_structures::fx::FxHashSet;
1010
use rustc_errors::Applicability;
11-
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
11+
use rustc_hir::intravisit::{walk_expr, Visitor};
12+
use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty};
1213
use rustc_lint::{LateContext, LateLintPass, LintContext};
1314
use rustc_middle::lint::in_external_macro;
1415
use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -162,61 +163,102 @@ fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat:
162163
);
163164
}
164165

165-
fn expr_diverges(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
166-
fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
167-
if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
168-
return ty.is_never();
169-
}
170-
false
166+
/// Check whether an expression is divergent. May give false negatives.
167+
fn expr_diverges(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
168+
struct V<'cx, 'tcx> {
169+
cx: &'cx LateContext<'tcx>,
170+
res: ControlFlow<(), Descend>,
171171
}
172-
// We can't just call is_never on expr and be done, because the type system
173-
// sometimes coerces the ! type to something different before we can get
174-
// our hands on it. So instead, we do a manual search. We do fall back to
175-
// is_never in some places when there is no better alternative.
176-
for_each_expr(expr, |ex| {
177-
match ex.kind {
178-
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()),
179-
ExprKind::Call(call, _) => {
180-
if is_never(cx, ex) || is_never(cx, call) {
181-
return ControlFlow::Break(());
182-
}
183-
ControlFlow::Continue(Descend::Yes)
184-
},
185-
ExprKind::MethodCall(..) => {
186-
if is_never(cx, ex) {
187-
return ControlFlow::Break(());
188-
}
189-
ControlFlow::Continue(Descend::Yes)
190-
},
191-
ExprKind::If(if_expr, if_then, if_else) => {
192-
let else_diverges = if_else.map_or(false, |ex| expr_diverges(cx, ex));
193-
let diverges = expr_diverges(cx, if_expr) || (else_diverges && expr_diverges(cx, if_then));
194-
if diverges {
195-
return ControlFlow::Break(());
196-
}
197-
ControlFlow::Continue(Descend::No)
198-
},
199-
ExprKind::Match(match_expr, match_arms, _) => {
200-
let diverges = expr_diverges(cx, match_expr)
201-
|| match_arms.iter().all(|arm| {
202-
let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(cx, g.body()));
203-
guard_diverges || expr_diverges(cx, arm.body)
204-
});
205-
if diverges {
206-
return ControlFlow::Break(());
172+
impl<'tcx> Visitor<'tcx> for V<'_, '_> {
173+
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
174+
fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
175+
if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
176+
return ty.is_never();
207177
}
208-
ControlFlow::Continue(Descend::No)
209-
},
178+
false
179+
}
180+
181+
if self.res.is_break() {
182+
return;
183+
}
210184

211-
// Don't continue into loops or labeled blocks, as they are breakable,
212-
// and we'd have to start checking labels.
213-
ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
185+
// We can't just call is_never on expr and be done, because the type system
186+
// sometimes coerces the ! type to something different before we can get
187+
// our hands on it. So instead, we do a manual search. We do fall back to
188+
// is_never in some places when there is no better alternative.
189+
self.res = match e.kind {
190+
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()),
191+
ExprKind::Call(call, _) => {
192+
if is_never(self.cx, e) || is_never(self.cx, call) {
193+
ControlFlow::Break(())
194+
} else {
195+
ControlFlow::Continue(Descend::Yes)
196+
}
197+
},
198+
ExprKind::MethodCall(..) => {
199+
if is_never(self.cx, e) {
200+
ControlFlow::Break(())
201+
} else {
202+
ControlFlow::Continue(Descend::Yes)
203+
}
204+
},
205+
ExprKind::If(if_expr, if_then, if_else) => {
206+
let else_diverges = if_else.map_or(false, |ex| expr_diverges(self.cx, ex));
207+
let diverges =
208+
expr_diverges(self.cx, if_expr) || (else_diverges && expr_diverges(self.cx, if_then));
209+
if diverges {
210+
ControlFlow::Break(())
211+
} else {
212+
ControlFlow::Continue(Descend::No)
213+
}
214+
},
215+
ExprKind::Match(match_expr, match_arms, _) => {
216+
let diverges = expr_diverges(self.cx, match_expr)
217+
|| match_arms.iter().all(|arm| {
218+
let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(self.cx, g.body()));
219+
guard_diverges || expr_diverges(self.cx, arm.body)
220+
});
221+
if diverges {
222+
ControlFlow::Break(())
223+
} else {
224+
ControlFlow::Continue(Descend::No)
225+
}
226+
},
214227

215-
// Default: descend
216-
_ => ControlFlow::Continue(Descend::Yes),
228+
// Don't continue into loops or labeled blocks, as they are breakable,
229+
// and we'd have to start checking labels.
230+
ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
231+
232+
// Default: descend
233+
_ => ControlFlow::Continue(Descend::Yes),
234+
};
235+
if let ControlFlow::Continue(Descend::Yes) = self.res {
236+
walk_expr(self, e);
237+
}
238+
}
239+
240+
fn visit_local(&mut self, local: &'tcx Local<'_>) {
241+
// Don't visit the else block of a let/else statement as it will not make
242+
// the statement divergent even though the else block is divergent.
243+
if let Some(init) = local.init {
244+
self.visit_expr(init);
245+
}
217246
}
218-
})
219-
.is_some()
247+
248+
// Avoid unnecessary `walk_*` calls.
249+
fn visit_ty(&mut self, _: &'tcx Ty<'tcx>) {}
250+
fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
251+
fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
252+
// Avoid monomorphising all `visit_*` functions.
253+
fn visit_nested_item(&mut self, _: ItemId) {}
254+
}
255+
256+
let mut v = V {
257+
cx,
258+
res: ControlFlow::Continue(Descend::Yes),
259+
};
260+
expr.visit(&mut v);
261+
v.res.is_break()
220262
}
221263

222264
fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool {

tests/ui/manual_let_else.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,15 @@ fn not_fire() {
248248
Some(value) => value,
249249
_ => macro_call!(),
250250
};
251+
252+
// Issue 10296
253+
// The let/else block in the else part is not divergent despite the presence of return
254+
let _x = if let Some(x) = Some(1) {
255+
x
256+
} else {
257+
let Some(_z) = Some(3) else {
258+
return
259+
};
260+
1
261+
};
251262
}

0 commit comments

Comments
 (0)