Skip to content

Commit dde825d

Browse files
committed
Auto merge of #89841 - cormacrelf:let-else-typed, r=nagisa
Implement let-else type annotations natively Tracking issue: #87335 Fixes #89688, fixes #89807, edit: fixes #89960 as well As explained in #89688 (comment), the previous desugaring moved the let-else scrutinee into a dummy variable, which meant if you wanted to refer to it again in the else block, it had moved. This introduces a new hir type, ~~`hir::LetExpr`~~ `hir::Let`, which takes over all the fields of `hir::ExprKind::Let(...)` and adds an optional type annotation. The `hir::Let` is then treated like a `hir::Local` when type checking a function body, specifically: * `GatherLocalsVisitor` overrides a new `Visitor::visit_let_expr` and does pretty much exactly what it does for `visit_local`, assigning a local type to the `hir::Let` ~~(they could be deduplicated but they are right next to each other, so at least we know they're the same)~~ * It reuses the code in `check_decl_local` to typecheck the `hir::Let`, simply returning 'bool' for the expression type after doing that. * ~~`FnCtxt::check_expr_let` passes this local type in to `demand_scrutinee_type`, and then imitates check_decl_local's pattern checking~~ * ~~`demand_scrutinee_type` (the blindest change for me, please give this extra scrutiny) uses this local type instead of of creating a new one~~ * ~~Just realised the `check_expr_with_needs` was passing NoExpectation further down, need to pass the type there too. And apparently this Expectation API already exists.~~ Some other misc notes: * ~~Is the clippy code supposed to be autoformatted? I tried not to give huge diffs but maybe some rustfmt changes simply haven't hit it yet.~~ * in `rustc_ast_lowering/src/block.rs`, I noticed some existing `self.alias_attrs()` calls in `LoweringContext::lower_stmts` seem to be copying attributes from the lowered locals/etc to the statements. Is that right? I'm new at this, I don't know.
2 parents 7abab1e + fec8a50 commit dde825d

Some content is hidden

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

46 files changed

+900
-142
lines changed

compiler/rustc_ast_lowering/src/block.rs

+10-22
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::{ImplTraitContext, ImplTraitPosition, LoweringContext};
22
use rustc_ast::{AttrVec, Block, BlockCheckMode, Expr, Local, LocalKind, Stmt, StmtKind};
33
use rustc_hir as hir;
44
use rustc_session::parse::feature_err;
5-
use rustc_span::symbol::Ident;
65
use rustc_span::{sym, DesugaringKind};
76

87
use smallvec::SmallVec;
@@ -39,8 +38,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
3938
let hir_id = self.lower_node_id(s.id);
4039
match &local.kind {
4140
LocalKind::InitElse(init, els) => {
42-
let (s, e) = self.lower_let_else(hir_id, local, init, els, tail);
43-
stmts.push(s);
41+
let e = self.lower_let_else(hir_id, local, init, els, tail);
4442
expr = Some(e);
4543
// remaining statements are in let-else expression
4644
break;
@@ -125,36 +123,25 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
125123
init: &Expr,
126124
els: &Block,
127125
tail: &[Stmt],
128-
) -> (hir::Stmt<'hir>, &'hir hir::Expr<'hir>) {
126+
) -> &'hir hir::Expr<'hir> {
129127
let ty = local
130128
.ty
131129
.as_ref()
132130
.map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Binding)));
133131
let span = self.lower_span(local.span);
134132
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);
133+
let init = self.lower_expr(init);
139134
let local_hir_id = self.lower_node_id(local.id);
140135
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 {
136+
let let_expr = {
137+
let lex = self.arena.alloc(hir::Let {
144138
hir_id: local_hir_id,
139+
pat: self.lower_pat(&local.pat),
145140
ty,
146-
pat,
147141
init,
148142
span,
149-
source: hir::LocalSource::Normal,
150143
});
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()))
144+
self.arena.alloc(self.expr(span, hir::ExprKind::Let(lex), AttrVec::new()))
158145
};
159146
let then_expr = {
160147
let (stmts, expr) = self.lower_stmts(tail);
@@ -165,9 +152,10 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
165152
let block = self.lower_block(els, false);
166153
self.arena.alloc(self.expr_block(block, AttrVec::new()))
167154
};
155+
self.alias_attrs(let_expr.hir_id, local_hir_id);
168156
self.alias_attrs(else_expr.hir_id, local_hir_id);
169157
let if_expr = self.arena.alloc(hir::Expr {
170-
hir_id: self.next_id(),
158+
hir_id: stmt_hir_id,
171159
span,
172160
kind: hir::ExprKind::If(let_expr, then_expr, Some(else_expr)),
173161
});
@@ -180,6 +168,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
180168
)
181169
.emit();
182170
}
183-
(stmt, if_expr)
171+
if_expr
184172
}
185173
}

compiler/rustc_ast_lowering/src/expr.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,15 @@ impl<'hir> LoweringContext<'_, 'hir> {
9191
let ohs = self.lower_expr(ohs);
9292
hir::ExprKind::AddrOf(k, m, ohs)
9393
}
94-
ExprKind::Let(ref pat, ref scrutinee, span) => hir::ExprKind::Let(
95-
self.lower_pat(pat),
96-
self.lower_expr(scrutinee),
97-
self.lower_span(span),
98-
),
94+
ExprKind::Let(ref pat, ref scrutinee, span) => {
95+
hir::ExprKind::Let(self.arena.alloc(hir::Let {
96+
hir_id: self.next_id(),
97+
span: self.lower_span(span),
98+
pat: self.lower_pat(pat),
99+
ty: None,
100+
init: self.lower_expr(scrutinee),
101+
}))
102+
}
99103
ExprKind::If(ref cond, ref then, ref else_opt) => {
100104
self.lower_expr_if(cond, then, else_opt.as_deref())
101105
}

compiler/rustc_hir/src/arena.rs

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ macro_rules! arena_types {
2020
[] generic_bound: rustc_hir::GenericBound<'tcx>,
2121
[] generic_param: rustc_hir::GenericParam<'tcx>,
2222
[] expr: rustc_hir::Expr<'tcx>,
23+
[] let_expr: rustc_hir::Let<'tcx>,
2324
[] expr_field: rustc_hir::ExprField<'tcx>,
2425
[] pat_field: rustc_hir::PatField<'tcx>,
2526
[] fn_decl: rustc_hir::FnDecl<'tcx>,

compiler/rustc_hir/src/hir.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -1160,10 +1160,24 @@ pub struct Arm<'hir> {
11601160
pub body: &'hir Expr<'hir>,
11611161
}
11621162

1163+
/// Represents a `let <pat>[: <ty>] = <expr>` expression (not a Local), occurring in an `if-let` or
1164+
/// `let-else`, evaluating to a boolean. Typically the pattern is refutable.
1165+
///
1166+
/// In an if-let, imagine it as `if (let <pat> = <expr>) { ... }`; in a let-else, it is part of the
1167+
/// desugaring to if-let. Only let-else supports the type annotation at present.
1168+
#[derive(Debug, HashStable_Generic)]
1169+
pub struct Let<'hir> {
1170+
pub hir_id: HirId,
1171+
pub span: Span,
1172+
pub pat: &'hir Pat<'hir>,
1173+
pub ty: Option<&'hir Ty<'hir>>,
1174+
pub init: &'hir Expr<'hir>,
1175+
}
1176+
11631177
#[derive(Debug, HashStable_Generic)]
11641178
pub enum Guard<'hir> {
11651179
If(&'hir Expr<'hir>),
1166-
// FIXME use ExprKind::Let for this.
1180+
// FIXME use hir::Let for this.
11671181
IfLet(&'hir Pat<'hir>, &'hir Expr<'hir>),
11681182
}
11691183

@@ -1680,7 +1694,7 @@ pub enum ExprKind<'hir> {
16801694
///
16811695
/// These are not `Local` and only occur as expressions.
16821696
/// The `let Some(x) = foo()` in `if let Some(x) = foo()` is an example of `Let(..)`.
1683-
Let(&'hir Pat<'hir>, &'hir Expr<'hir>, Span),
1697+
Let(&'hir Let<'hir>),
16841698
/// An `if` block, with an optional else block.
16851699
///
16861700
/// I.e., `if <expr> { <expr> } else { <expr> }`.

compiler/rustc_hir/src/intravisit.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,9 @@ pub trait Visitor<'v>: Sized {
389389
fn visit_expr(&mut self, ex: &'v Expr<'v>) {
390390
walk_expr(self, ex)
391391
}
392+
fn visit_let_expr(&mut self, lex: &'v Let<'v>) {
393+
walk_let_expr(self, lex)
394+
}
392395
fn visit_ty(&mut self, t: &'v Ty<'v>) {
393396
walk_ty(self, t)
394397
}
@@ -1126,6 +1129,14 @@ pub fn walk_anon_const<'v, V: Visitor<'v>>(visitor: &mut V, constant: &'v AnonCo
11261129
visitor.visit_nested_body(constant.body);
11271130
}
11281131

1132+
pub fn walk_let_expr<'v, V: Visitor<'v>>(visitor: &mut V, let_expr: &'v Let<'v>) {
1133+
// match the visit order in walk_local
1134+
visitor.visit_expr(let_expr.init);
1135+
visitor.visit_id(let_expr.hir_id);
1136+
visitor.visit_pat(let_expr.pat);
1137+
walk_list!(visitor, visit_ty, let_expr.ty);
1138+
}
1139+
11291140
pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) {
11301141
visitor.visit_id(expression.hir_id);
11311142
match expression.kind {
@@ -1172,10 +1183,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>)
11721183
ExprKind::DropTemps(ref subexpression) => {
11731184
visitor.visit_expr(subexpression);
11741185
}
1175-
ExprKind::Let(ref pat, ref expr, _) => {
1176-
visitor.visit_expr(expr);
1177-
visitor.visit_pat(pat);
1178-
}
1186+
ExprKind::Let(ref let_expr) => visitor.visit_let_expr(let_expr),
11791187
ExprKind::If(ref cond, ref then, ref else_opt) => {
11801188
visitor.visit_expr(cond);
11811189
visitor.visit_expr(then);

compiler/rustc_hir_pretty/src/lib.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -1101,13 +1101,17 @@ impl<'a> State<'a> {
11011101
}
11021102

11031103
/// Print a `let pat = expr` expression.
1104-
fn print_let(&mut self, pat: &hir::Pat<'_>, expr: &hir::Expr<'_>) {
1105-
self.word("let ");
1104+
fn print_let(&mut self, pat: &hir::Pat<'_>, ty: Option<&hir::Ty<'_>>, init: &hir::Expr<'_>) {
1105+
self.word_space("let");
11061106
self.print_pat(pat);
1107+
if let Some(ty) = ty {
1108+
self.word_space(":");
1109+
self.print_type(ty);
1110+
}
11071111
self.space();
11081112
self.word_space("=");
1109-
let npals = || parser::needs_par_as_let_scrutinee(expr.precedence().order());
1110-
self.print_expr_cond_paren(expr, Self::cond_needs_par(expr) || npals())
1113+
let npals = || parser::needs_par_as_let_scrutinee(init.precedence().order());
1114+
self.print_expr_cond_paren(init, Self::cond_needs_par(init) || npals())
11111115
}
11121116

11131117
// Does `expr` need parentheses when printed in a condition position?
@@ -1462,8 +1466,8 @@ impl<'a> State<'a> {
14621466
// Print `}`:
14631467
self.bclose_maybe_open(expr.span, true);
14641468
}
1465-
hir::ExprKind::Let(ref pat, ref scrutinee, _) => {
1466-
self.print_let(pat, scrutinee);
1469+
hir::ExprKind::Let(hir::Let { pat, ty, init, .. }) => {
1470+
self.print_let(pat, *ty, init);
14671471
}
14681472
hir::ExprKind::If(ref test, ref blk, ref elseopt) => {
14691473
self.print_if(&test, &blk, elseopt.as_ref().map(|e| &**e));

compiler/rustc_mir_build/src/thir/cx/expr.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -605,9 +605,10 @@ impl<'tcx> Cx<'tcx> {
605605
},
606606
Err(err) => bug!("invalid loop id for continue: {}", err),
607607
},
608-
hir::ExprKind::Let(ref pat, ref expr, _) => {
609-
ExprKind::Let { expr: self.mirror_expr(expr), pat: self.pattern_from_hir(pat) }
610-
}
608+
hir::ExprKind::Let(let_expr) => ExprKind::Let {
609+
expr: self.mirror_expr(let_expr.init),
610+
pat: self.pattern_from_hir(let_expr.pat),
611+
},
611612
hir::ExprKind::If(cond, then, else_opt) => ExprKind::If {
612613
if_then_scope: region::Scope {
613614
id: then.hir_id.local_id,

compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, '_, 'tcx> {
6464
intravisit::walk_expr(self, ex);
6565
match &ex.kind {
6666
hir::ExprKind::Match(scrut, arms, source) => self.check_match(scrut, arms, *source),
67-
hir::ExprKind::Let(pat, scrut, span) => self.check_let(pat, scrut, *span),
67+
hir::ExprKind::Let(hir::Let { pat, init, span, .. }) => {
68+
self.check_let(pat, init, *span)
69+
}
6870
_ => {}
6971
}
7072
}
@@ -148,9 +150,9 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
148150
}
149151
}
150152

151-
fn check_let(&mut self, pat: &'tcx hir::Pat<'tcx>, expr: &hir::Expr<'_>, span: Span) {
153+
fn check_let(&mut self, pat: &'tcx hir::Pat<'tcx>, scrutinee: &hir::Expr<'_>, span: Span) {
152154
self.check_patterns(pat, Refutable);
153-
let mut cx = self.new_cx(expr.hir_id);
155+
let mut cx = self.new_cx(scrutinee.hir_id);
154156
let tpat = self.lower_pattern(&mut cx, pat, &mut false);
155157
check_let_reachability(&mut cx, pat.hir_id, tpat, span);
156158
}

compiler/rustc_passes/src/liveness.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,8 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
429429
intravisit::walk_expr(self, expr);
430430
}
431431

432-
hir::ExprKind::Let(ref pat, ..) => {
433-
self.add_from_pat(pat);
432+
hir::ExprKind::Let(let_expr) => {
433+
self.add_from_pat(let_expr.pat);
434434
intravisit::walk_expr(self, expr);
435435
}
436436

@@ -856,9 +856,9 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
856856
})
857857
}
858858

859-
hir::ExprKind::Let(ref pat, ref scrutinee, _) => {
860-
let succ = self.propagate_through_expr(scrutinee, succ);
861-
self.define_bindings_in_pat(pat, succ)
859+
hir::ExprKind::Let(let_expr) => {
860+
let succ = self.propagate_through_expr(let_expr.init, succ);
861+
self.define_bindings_in_pat(let_expr.pat, succ)
862862
}
863863

864864
// Note that labels have been resolved, so we don't need to look
@@ -1401,8 +1401,8 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) {
14011401
}
14021402
}
14031403

1404-
hir::ExprKind::Let(ref pat, ..) => {
1405-
this.check_unused_vars_in_pat(pat, None, |_, _, _, _| {});
1404+
hir::ExprKind::Let(let_expr) => {
1405+
this.check_unused_vars_in_pat(let_expr.pat, None, |_, _, _, _| {});
14061406
}
14071407

14081408
// no correctness conditions related to liveness

compiler/rustc_typeck/src/check/expr.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
300300
}
301301
}
302302
ExprKind::Ret(ref expr_opt) => self.check_expr_return(expr_opt.as_deref(), expr),
303-
ExprKind::Let(pat, let_expr, _) => self.check_expr_let(let_expr, pat),
303+
ExprKind::Let(let_expr) => self.check_expr_let(let_expr),
304304
ExprKind::Loop(body, _, source, _) => {
305305
self.check_expr_loop(body, source, expected, expr)
306306
}
@@ -1044,10 +1044,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10441044
}
10451045
}
10461046

1047-
fn check_expr_let(&self, expr: &'tcx hir::Expr<'tcx>, pat: &'tcx hir::Pat<'tcx>) -> Ty<'tcx> {
1048-
self.warn_if_unreachable(expr.hir_id, expr.span, "block in `let` expression");
1049-
let expr_ty = self.demand_scrutinee_type(expr, pat.contains_explicit_ref_binding(), false);
1050-
self.check_pat_top(pat, expr_ty, Some(expr.span), true);
1047+
fn check_expr_let(&self, let_expr: &'tcx hir::Let<'tcx>) -> Ty<'tcx> {
1048+
// for let statements, this is done in check_stmt
1049+
let init = let_expr.init;
1050+
self.warn_if_unreachable(init.hir_id, init.span, "block in `let` expression");
1051+
// otherwise check exactly as a let statement
1052+
self.check_decl(let_expr.into());
1053+
// but return a bool, for this is a boolean expression
10511054
self.tcx.types.bool
10521055
}
10531056

0 commit comments

Comments
 (0)