Skip to content

Commit 6b339db

Browse files
committed
document move expression lowering and capture flow
document move expression lowering flow
1 parent 96d9369 commit 6b339db

4 files changed

Lines changed: 51 additions & 1 deletion

File tree

compiler/rustc_ast_lowering/src/expr.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,22 @@ use crate::{AllowReturnTypeNotation, FnDeclKind, ImplTraitPosition, TryBlockScop
3131

3232
pub(super) struct WillCreateDefIdsVisitor;
3333

34+
/// A `move(...)` expression found while scanning a plain closure body.
3435
struct MoveExprOccurrence<'a> {
36+
/// The `NodeId` of the outer `move(...)` expression.
3537
id: NodeId,
38+
/// Span of the `move` token, used for the generated binding name.
3639
move_kw_span: Span,
40+
/// The expression inside `move(...)`; e.g. `foo.bar` in `move(foo.bar)`.
3741
expr: &'a Expr,
3842
}
3943

44+
/// Collects the `move(...)` expressions that belong to one plain closure body.
45+
///
46+
/// For `|| move(foo.bar).clone()`, this records the outer `move(foo.bar)`
47+
/// occurrence and the inner expression `foo.bar`. Nested closures, generators,
48+
/// const blocks, and items are lowered as separate bodies, so this visitor does
49+
/// not collect `move(...)` expressions from them.
4050
struct MoveExprCollector<'a> {
4151
occurrences: Vec<MoveExprOccurrence<'a>>,
4252
}
@@ -53,6 +63,8 @@ impl<'a> Visitor<'a> for MoveExprCollector<'a> {
5363
fn visit_expr(&mut self, expr: &'a Expr) {
5464
match &expr.kind {
5565
ExprKind::Move(inner, move_kw_span) => {
66+
// For `move(foo.bar)`, first collect any nested `move(...)`
67+
// expressions in `foo.bar`, then record this outer occurrence.
5668
self.visit_expr(inner);
5769
self.occurrences.push(MoveExprOccurrence {
5870
id: expr.id,
@@ -64,6 +76,8 @@ impl<'a> Visitor<'a> for MoveExprCollector<'a> {
6476
_ => walk_expr(self, expr),
6577
}
6678
}
79+
80+
fn visit_item(&mut self, _: &'a Item) {}
6781
}
6882

6983
impl<'v> rustc_ast::visit::Visitor<'v> for WillCreateDefIdsVisitor {
@@ -1082,6 +1096,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
10821096
hir::ExprKind::Use(self.lower_expr(expr), self.lower_span(use_kw_span))
10831097
}
10841098

1099+
// Lowers closure expressions, including the `move(...)` desugaring for
1100+
// plain closures.
10851101
fn lower_expr_closure_expr(&mut self, e: &Expr, closure: &Closure) -> hir::Expr<'hir> {
10861102
let expr_hir_id = self.lower_node_id(e.id);
10871103
let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e));
@@ -1137,8 +1153,18 @@ impl<'hir> LoweringContext<'_, 'hir> {
11371153
fn_arg_span: Span,
11381154
whole_span: Span,
11391155
) -> hir::Expr<'hir> {
1156+
// `move(...)` evaluates its inner expression when the closure is created
1157+
// and captures the result by value. For example:
1158+
//
1159+
// `|| move(foo).bar`
1160+
//
1161+
// is lowered roughly as:
1162+
//
1163+
// `let __move_expr_0 = foo; || __move_expr_0.bar`
11401164
let occurrences = MoveExprCollector::collect(body);
11411165
if occurrences.is_empty() {
1166+
// No `move(...)` expressions in this closure body; lower the closure
1167+
// normally, with no explicit captures.
11421168
return hir::Expr {
11431169
hir_id: expr_hir_id,
11441170
kind: self.lower_expr_closure(
@@ -1161,6 +1187,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
11611187
let mut bindings = NodeMap::default();
11621188
let mut lowered_occurrences = Vec::with_capacity(occurrences.len());
11631189
for (index, occurrence) in occurrences.iter().enumerate() {
1190+
// Create one synthetic local per `move(...)` expression and remember
1191+
// which AST node should be replaced by that local while lowering the
1192+
// closure body.
11641193
let ident =
11651194
Ident::from_str_and_span(&format!("__move_expr_{index}"), occurrence.move_kw_span);
11661195
let (pat, binding) = self.pat_ident(occurrence.expr.span, ident);
@@ -1174,6 +1203,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
11741203
self.move_expr_bindings.push(bindings);
11751204
let mut stmts = Vec::with_capacity(lowered_occurrences.len());
11761205
for (occurrence, pat, _) in &lowered_occurrences {
1206+
// Evaluate the expression inside `move(...)` before creating the
1207+
// closure and store it in a synthetic local:
1208+
// `|| move(foo).bar` becomes roughly
1209+
// `let __move_expr_0 = foo; || __move_expr_0.bar`.
11771210
let init = self.lower_expr(occurrence.expr);
11781211
stmts.push(self.stmt_let_pat(
11791212
None,
@@ -1187,9 +1220,15 @@ impl<'hir> LoweringContext<'_, 'hir> {
11871220
let explicit_captures = self.arena.alloc_from_iter(
11881221
lowered_occurrences
11891222
.iter()
1223+
// Force the generated locals to be captured by value even if
1224+
// the lowered closure body only borrows them, as in
1225+
// `move(foo).clone()`.
11901226
.map(|(_, _, binding)| hir::ExplicitCapture { var_hir_id: *binding }),
11911227
);
11921228

1229+
// Lower the closure itself while `move_expr_bindings` contains this
1230+
// closure's substitutions, so each `move(...)` in the body is replaced
1231+
// with its generated local.
11931232
let closure_expr = self.arena.alloc(hir::Expr {
11941233
hir_id: expr_hir_id,
11951234
kind: self.lower_expr_closure(
@@ -1388,7 +1427,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
13881427
constness: self.lower_constness(constness),
13891428
explicit_captures: &[],
13901429
});
1391-
13921430
hir::ExprKind::Closure(c)
13931431
}
13941432

compiler/rustc_ast_lowering/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ struct LoweringContext<'a, 'hir> {
158158
allow_async_fn_traits: Arc<[Symbol]>,
159159

160160
delayed_lints: Vec<DelayedLint>,
161+
/// Stack of per-closure `move(...)` substitution maps. Each map is keyed by
162+
/// the AST `NodeId` of a `move(...)` occurrence and points to the synthetic
163+
/// local used while lowering that closure body.
161164
move_expr_bindings: Vec<NodeMap<(Ident, HirId)>>,
162165

163166
attribute_parser: AttributeParser<'hir>,

compiler/rustc_hir/src/hir.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,6 +1685,8 @@ pub struct Closure<'hir> {
16851685
pub explicit_captures: &'hir [ExplicitCapture],
16861686
}
16871687

1688+
/// A HIR local that must be captured by value even if ordinary closure capture
1689+
/// analysis would infer a weaker capture kind from its uses in the body.
16881690
#[derive(Debug, Clone, Copy, HashStable_Generic)]
16891691
pub struct ExplicitCapture {
16901692
pub var_hir_id: HirId,

compiler/rustc_hir_typeck/src/upvar.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
209209
fake_reads: Default::default(),
210210
};
211211

212+
// First collect the captures implied by the operations in the closure
213+
// body. This records how each place is actually used: borrowed, modified,
214+
// moved, and so on.
212215
let _ = euv::ExprUseVisitor::new(&closure_fcx, &mut delegate).consume_body(body);
213216

217+
// `consume_body` only sees how the lowered closure body uses those
218+
// places. For `move(foo).clone()`, the body may only borrow the
219+
// synthetic local for `foo`, but the source `move(...)` still requires
220+
// capturing that local by value.
214221
let explicit_captures = match self.tcx.hir_node(closure_hir_id).expect_expr().kind {
215222
hir::ExprKind::Closure(closure) => closure.explicit_captures,
216223
_ => bug!("expected closure expr for {:?}", closure_hir_id),

0 commit comments

Comments
 (0)