Skip to content

Commit c2a4088

Browse files
committed
Auto merge of #87688 - camsteffen:let-else, r=cjgillot
Introduce `let...else` Tracking issue: #87335 The trickiest part for me was enforcing the diverging else block with clear diagnostics. Perhaps the obvious solution is to expand to `let _: ! = ..`, but I decided against this because, when a "mismatched type" error is found in typeck, there is no way to trace where in the HIR the expected type originated, AFAICT. In order to pass down this information, I believe we should introduce `Expectation::LetElseNever(HirId)` or maybe add `HirId` to `Expectation::HasType`, but I left that as a future enhancement. For now, I simply assert that the block is `!` with a custom `ObligationCauseCode`, and I think this is clear enough, at least to start. The downside here is that the error points at the entire block rather than the specific expression with the wrong type. I left a todo to this effect. Overall, I believe this PR is feature-complete with regard to the RFC.
2 parents a395610 + 3ff1d6b commit c2a4088

File tree

59 files changed

+901
-232
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+901
-232
lines changed

Diff for: compiler/rustc_ast/src/ast.rs

+31-2
Original file line numberDiff line numberDiff line change
@@ -1005,13 +1005,42 @@ pub struct Local {
10051005
pub id: NodeId,
10061006
pub pat: P<Pat>,
10071007
pub ty: Option<P<Ty>>,
1008-
/// Initializer expression to set the value, if any.
1009-
pub init: Option<P<Expr>>,
1008+
pub kind: LocalKind,
10101009
pub span: Span,
10111010
pub attrs: AttrVec,
10121011
pub tokens: Option<LazyTokenStream>,
10131012
}
10141013

1014+
#[derive(Clone, Encodable, Decodable, Debug)]
1015+
pub enum LocalKind {
1016+
/// Local declaration.
1017+
/// Example: `let x;`
1018+
Decl,
1019+
/// Local declaration with an initializer.
1020+
/// Example: `let x = y;`
1021+
Init(P<Expr>),
1022+
/// Local declaration with an initializer and an `else` clause.
1023+
/// Example: `let Some(x) = y else { return };`
1024+
InitElse(P<Expr>, P<Block>),
1025+
}
1026+
1027+
impl LocalKind {
1028+
pub fn init(&self) -> Option<&Expr> {
1029+
match self {
1030+
Self::Decl => None,
1031+
Self::Init(i) | Self::InitElse(i, _) => Some(i),
1032+
}
1033+
}
1034+
1035+
pub fn init_else_opt(&self) -> Option<(&Expr, Option<&Block>)> {
1036+
match self {
1037+
Self::Decl => None,
1038+
Self::Init(init) => Some((init, None)),
1039+
Self::InitElse(init, els) => Some((init, Some(els))),
1040+
}
1041+
}
1042+
}
1043+
10151044
/// An arm of a 'match'.
10161045
///
10171046
/// E.g., `0..=10 => { println!("match!") }` as in

Diff for: compiler/rustc_ast/src/mut_visit.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -571,11 +571,20 @@ pub fn noop_visit_parenthesized_parameter_data<T: MutVisitor>(
571571
}
572572

573573
pub fn noop_visit_local<T: MutVisitor>(local: &mut P<Local>, vis: &mut T) {
574-
let Local { id, pat, ty, init, span, attrs, tokens } = local.deref_mut();
574+
let Local { id, pat, ty, kind, span, attrs, tokens } = local.deref_mut();
575575
vis.visit_id(id);
576576
vis.visit_pat(pat);
577577
visit_opt(ty, |ty| vis.visit_ty(ty));
578-
visit_opt(init, |init| vis.visit_expr(init));
578+
match kind {
579+
LocalKind::Decl => {}
580+
LocalKind::Init(init) => {
581+
vis.visit_expr(init);
582+
}
583+
LocalKind::InitElse(init, els) => {
584+
vis.visit_expr(init);
585+
vis.visit_block(els);
586+
}
587+
}
579588
vis.visit_span(span);
580589
visit_thin_attrs(attrs, vis);
581590
visit_lazy_tts(tokens, vis);

Diff for: compiler/rustc_ast/src/util/classify.rs

+27
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,30 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
2323
| ast::ExprKind::TryBlock(..)
2424
)
2525
}
26+
27+
/// If an expression ends with `}`, returns the innermost expression ending in the `}`
28+
pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
29+
use ast::ExprKind::*;
30+
31+
loop {
32+
match &expr.kind {
33+
AddrOf(_, _, e)
34+
| Assign(_, e, _)
35+
| AssignOp(_, _, e)
36+
| Binary(_, _, e)
37+
| Box(e)
38+
| Break(_, Some(e))
39+
| Closure(.., e, _)
40+
| Let(_, e, _)
41+
| Range(_, Some(e), _)
42+
| Ret(Some(e))
43+
| Unary(_, e)
44+
| Yield(Some(e)) => {
45+
expr = e;
46+
}
47+
Async(..) | Block(..) | ForLoop(..) | If(..) | Loop(..) | Match(..) | Struct(..)
48+
| TryBlock(..) | While(..) => break Some(expr),
49+
_ => break None,
50+
}
51+
}
52+
}

Diff for: compiler/rustc_ast/src/visit.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,10 @@ pub fn walk_local<'a, V: Visitor<'a>>(visitor: &mut V, local: &'a Local) {
242242
}
243243
visitor.visit_pat(&local.pat);
244244
walk_list!(visitor, visit_ty, &local.ty);
245-
walk_list!(visitor, visit_expr, &local.init);
245+
if let Some((init, els)) = local.kind.init_else_opt() {
246+
visitor.visit_expr(init);
247+
walk_list!(visitor, visit_block, els);
248+
}
246249
}
247250

248251
pub fn walk_label<'a, V: Visitor<'a>>(visitor: &mut V, label: &'a Label) {

Diff for: compiler/rustc_ast_lowering/src/block.rs

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use crate::{ImplTraitContext, ImplTraitPosition, LoweringContext};
2+
use rustc_ast::{AttrVec, Block, BlockCheckMode, Expr, Local, LocalKind, Stmt, StmtKind};
3+
use rustc_hir as hir;
4+
use rustc_session::parse::feature_err;
5+
use rustc_span::symbol::Ident;
6+
use rustc_span::{sym, DesugaringKind};
7+
8+
use smallvec::SmallVec;
9+
10+
impl<'a, 'hir> LoweringContext<'a, 'hir> {
11+
pub(super) fn lower_block(
12+
&mut self,
13+
b: &Block,
14+
targeted_by_break: bool,
15+
) -> &'hir hir::Block<'hir> {
16+
self.arena.alloc(self.lower_block_noalloc(b, targeted_by_break))
17+
}
18+
19+
pub(super) fn lower_block_noalloc(
20+
&mut self,
21+
b: &Block,
22+
targeted_by_break: bool,
23+
) -> hir::Block<'hir> {
24+
let (stmts, expr) = self.lower_stmts(&b.stmts);
25+
let rules = self.lower_block_check_mode(&b.rules);
26+
let hir_id = self.lower_node_id(b.id);
27+
hir::Block { hir_id, stmts, expr, rules, span: self.lower_span(b.span), targeted_by_break }
28+
}
29+
30+
fn lower_stmts(
31+
&mut self,
32+
mut ast_stmts: &[Stmt],
33+
) -> (&'hir [hir::Stmt<'hir>], Option<&'hir hir::Expr<'hir>>) {
34+
let mut stmts = SmallVec::<[hir::Stmt<'hir>; 8]>::new();
35+
let mut expr = None;
36+
while let [s, tail @ ..] = ast_stmts {
37+
match s.kind {
38+
StmtKind::Local(ref local) => {
39+
let hir_id = self.lower_node_id(s.id);
40+
match &local.kind {
41+
LocalKind::InitElse(init, els) => {
42+
let (s, e) = self.lower_let_else(hir_id, local, init, els, tail);
43+
stmts.push(s);
44+
expr = Some(e);
45+
// remaining statements are in let-else expression
46+
break;
47+
}
48+
_ => {
49+
let local = self.lower_local(local);
50+
self.alias_attrs(hir_id, local.hir_id);
51+
let kind = hir::StmtKind::Local(local);
52+
let span = self.lower_span(s.span);
53+
stmts.push(hir::Stmt { hir_id, kind, span });
54+
}
55+
}
56+
}
57+
StmtKind::Item(ref it) => {
58+
stmts.extend(self.lower_item_id(it).into_iter().enumerate().map(
59+
|(i, item_id)| {
60+
let hir_id = match i {
61+
0 => self.lower_node_id(s.id),
62+
_ => self.next_id(),
63+
};
64+
let kind = hir::StmtKind::Item(item_id);
65+
let span = self.lower_span(s.span);
66+
hir::Stmt { hir_id, kind, span }
67+
},
68+
));
69+
}
70+
StmtKind::Expr(ref e) => {
71+
let e = self.lower_expr(e);
72+
if tail.is_empty() {
73+
expr = Some(e);
74+
} else {
75+
let hir_id = self.lower_node_id(s.id);
76+
self.alias_attrs(hir_id, e.hir_id);
77+
let kind = hir::StmtKind::Expr(e);
78+
let span = self.lower_span(s.span);
79+
stmts.push(hir::Stmt { hir_id, kind, span });
80+
}
81+
}
82+
StmtKind::Semi(ref e) => {
83+
let e = self.lower_expr(e);
84+
let hir_id = self.lower_node_id(s.id);
85+
self.alias_attrs(hir_id, e.hir_id);
86+
let kind = hir::StmtKind::Semi(e);
87+
let span = self.lower_span(s.span);
88+
stmts.push(hir::Stmt { hir_id, kind, span });
89+
}
90+
StmtKind::Empty => {}
91+
StmtKind::MacCall(..) => panic!("shouldn't exist here"),
92+
}
93+
ast_stmts = &ast_stmts[1..];
94+
}
95+
(self.arena.alloc_from_iter(stmts), expr)
96+
}
97+
98+
fn lower_local(&mut self, l: &Local) -> &'hir hir::Local<'hir> {
99+
let ty = l
100+
.ty
101+
.as_ref()
102+
.map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Binding)));
103+
let init = l.kind.init().map(|init| self.lower_expr(init));
104+
let hir_id = self.lower_node_id(l.id);
105+
let pat = self.lower_pat(&l.pat);
106+
let span = self.lower_span(l.span);
107+
let source = hir::LocalSource::Normal;
108+
self.lower_attrs(hir_id, &l.attrs);
109+
self.arena.alloc(hir::Local { hir_id, ty, pat, init, span, source })
110+
}
111+
112+
fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode {
113+
match *b {
114+
BlockCheckMode::Default => hir::BlockCheckMode::DefaultBlock,
115+
BlockCheckMode::Unsafe(u) => {
116+
hir::BlockCheckMode::UnsafeBlock(self.lower_unsafe_source(u))
117+
}
118+
}
119+
}
120+
121+
fn lower_let_else(
122+
&mut self,
123+
stmt_hir_id: hir::HirId,
124+
local: &Local,
125+
init: &Expr,
126+
els: &Block,
127+
tail: &[Stmt],
128+
) -> (hir::Stmt<'hir>, &'hir hir::Expr<'hir>) {
129+
let ty = local
130+
.ty
131+
.as_ref()
132+
.map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Binding)));
133+
let span = self.lower_span(local.span);
134+
let span = self.mark_span_with_reason(DesugaringKind::LetElse, span, None);
135+
let init = Some(self.lower_expr(init));
136+
let val = Ident::with_dummy_span(sym::val);
137+
let (pat, val_id) =
138+
self.pat_ident_binding_mode(span, val, hir::BindingAnnotation::Unannotated);
139+
let local_hir_id = self.lower_node_id(local.id);
140+
self.lower_attrs(local_hir_id, &local.attrs);
141+
// first statement which basically exists for the type annotation
142+
let stmt = {
143+
let local = self.arena.alloc(hir::Local {
144+
hir_id: local_hir_id,
145+
ty,
146+
pat,
147+
init,
148+
span,
149+
source: hir::LocalSource::Normal,
150+
});
151+
let kind = hir::StmtKind::Local(local);
152+
hir::Stmt { hir_id: stmt_hir_id, kind, span }
153+
};
154+
let let_expr = {
155+
let scrutinee = self.expr_ident(span, val, val_id);
156+
let let_kind = hir::ExprKind::Let(self.lower_pat(&local.pat), scrutinee, span);
157+
self.arena.alloc(self.expr(span, let_kind, AttrVec::new()))
158+
};
159+
let then_expr = {
160+
let (stmts, expr) = self.lower_stmts(tail);
161+
let block = self.block_all(span, stmts, expr);
162+
self.arena.alloc(self.expr_block(block, AttrVec::new()))
163+
};
164+
let else_expr = {
165+
let block = self.lower_block(els, false);
166+
self.arena.alloc(self.expr_block(block, AttrVec::new()))
167+
};
168+
self.alias_attrs(else_expr.hir_id, local_hir_id);
169+
let if_expr = self.arena.alloc(hir::Expr {
170+
hir_id: self.next_id(),
171+
span,
172+
kind: hir::ExprKind::If(let_expr, then_expr, Some(else_expr)),
173+
});
174+
if !self.sess.features_untracked().let_else {
175+
feature_err(
176+
&self.sess.parse_sess,
177+
sym::let_else,
178+
local.span,
179+
"`let...else` statements are unstable",
180+
)
181+
.emit();
182+
}
183+
(stmt, if_expr)
184+
}
185+
}

0 commit comments

Comments
 (0)