Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,7 @@ impl Expr {
// need parens sometimes. E.g. we can print `(let _ = a) && b` as `let _ = a && b`
// but we need to print `(let _ = a) < b` as-is with parens.
| ExprKind::Let(..)
| ExprKind::Move(..)
| ExprKind::Unary(..) => ExprPrecedence::Prefix,

// Need parens if and only if there are prefix attributes.
Expand Down Expand Up @@ -1764,6 +1765,8 @@ pub enum ExprKind {
Binary(BinOp, Box<Expr>, Box<Expr>),
/// A unary operation (e.g., `!x`, `*x`).
Unary(UnOp, Box<Expr>),
/// A `move(expr)` expression.
Move(Box<Expr>, Span),
/// A literal (e.g., `1`, `"foo"`).
Lit(token::Lit),
/// A cast (e.g., `foo as f64`).
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_ast/src/util/classify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool {
Assign(e, _, _)
| AssignOp(_, e, _)
| Await(e, _)
| Move(e, _)
| Use(e, _)
| Binary(_, e, _)
| Call(e, _)
Expand Down Expand Up @@ -183,6 +184,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
| Ret(Some(e))
| Unary(_, e)
| Yeet(Some(e))
| Move(e, _)
| Become(e) => {
expr = e;
}
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,9 @@ macro_rules! common_visitor_and_walkers {
visit_visitable!($($mut)? vis, block, opt_label),
ExprKind::Gen(capt, body, kind, decl_span) =>
visit_visitable!($($mut)? vis, capt, body, kind, decl_span),
ExprKind::Await(expr, span) | ExprKind::Use(expr, span) =>
ExprKind::Await(expr, span)
| ExprKind::Move(expr, span)
| ExprKind::Use(expr, span) =>
visit_visitable!($($mut)? vis, expr, span),
ExprKind::Assign(lhs, rhs, span) =>
visit_visitable!($($mut)? vis, lhs, rhs, span),
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_ast_lowering/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ pub(crate) struct ClosureCannotBeStatic {
pub fn_decl_span: Span,
}

#[derive(Diagnostic)]
#[diag("`move(expr)` is only supported in plain closures")]
pub(crate) struct MoveExprOnlyInPlainClosures {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("functional record updates are not allowed in destructuring assignments")]
pub(crate) struct FunctionalRecordUpdateDestructuringAssignment {
Expand Down
246 changes: 206 additions & 40 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::mem;
use std::ops::ControlFlow;
use std::sync::Arc;

use rustc_ast::node_id::NodeMap;
use rustc_ast::*;
use rustc_ast_pretty::pprust::expr_to_string;
use rustc_data_structures::stack::ensure_sufficient_stack;
Expand All @@ -20,15 +21,50 @@ use visit::{Visitor, walk_expr};
use super::errors::{
AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, ClosureCannotBeStatic,
CoroutineTooManyParameters, FunctionalRecordUpdateDestructuringAssignment,
InclusiveRangeWithNoEnd, MatchArmWithNoBody, NeverPatternWithBody, NeverPatternWithGuard,
UnderscoreExprLhsAssign,
InclusiveRangeWithNoEnd, MatchArmWithNoBody, MoveExprOnlyInPlainClosures, NeverPatternWithBody,
NeverPatternWithGuard, UnderscoreExprLhsAssign,
};
use super::{
GenericArgsMode, ImplTraitContext, LoweringContext, ParamMode, ResolverAstLoweringExt,
};
use crate::errors::{InvalidLegacyConstGenericArg, UseConstGenericArg, YieldInClosure};
use crate::{AllowReturnTypeNotation, FnDeclKind, ImplTraitPosition, TryBlockScope};

struct MoveExprOccurrence<'a> {
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment please -- what is this struct for? what are the roles of its fields?

View changes since the review

id: NodeId,
move_kw_span: Span,
expr: &'a Expr,
}

struct MoveExprCollector<'a> {
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment please: what does this struct do?

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my preference is to have comments that walk through an example, e.g.,

// Given an expression like this
//
// ```
// || move(foo.bar).clone()
// ```
//
// this will pass walk the AST for the closure and collect `move(foo.bar)` expressions into the vector.

occurrences: Vec<MoveExprOccurrence<'a>>,
}

impl<'a> MoveExprCollector<'a> {
fn collect(expr: &'a Expr) -> Vec<MoveExprOccurrence<'a>> {
let mut this = Self { occurrences: Vec::new() };
this.visit_expr(expr);
this.occurrences
}
}

impl<'a> Visitor<'a> for MoveExprCollector<'a> {
fn visit_expr(&mut self, expr: &'a Expr) {
match &expr.kind {
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...then here you can refer back to the example...

// If this is `move` expression (`move(foo.bar)` in the example above) push its id into the vector.
// Else recurse.

or something lik ethat.

View changes since the review

ExprKind::Move(inner, move_kw_span) => {
self.visit_expr(inner);
self.occurrences.push(MoveExprOccurrence {
id: expr.id,
move_kw_span: *move_kw_span,
expr: inner,
});
}
ExprKind::Closure(..) | ExprKind::Gen(..) | ExprKind::ConstBlock(..) => {}
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth calling this behavior out too: does not recurse into nested closures or const blocks. What about things like...

|| {
    fn bar() {
        move(x)
    }
}

...I guess that'd be an error, but still, seems like we should stop recursing at items.

View changes since the review

_ => walk_expr(self, expr),
}
}
}

struct WillCreateDefIdsVisitor {}

impl<'v> rustc_ast::visit::Visitor<'v> for WillCreateDefIdsVisitor {
Expand Down Expand Up @@ -95,11 +131,12 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
ExprKind::ForLoop { pat, iter, body, label, kind } => {
return self.lower_expr_for(e, pat, iter, body, *label, *kind);
}
ExprKind::Closure(box closure) => return self.lower_expr_closure_expr(e, closure),
_ => (),
}

let expr_hir_id = self.lower_node_id(e.id);
let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e));
self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e));

let kind = match &e.kind {
ExprKind::Array(exprs) => hir::ExprKind::Array(self.lower_exprs(exprs)),
Expand Down Expand Up @@ -212,43 +249,38 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
},
),
ExprKind::Await(expr, await_kw_span) => self.lower_expr_await(*await_kw_span, expr),
ExprKind::Move(_, move_kw_span) => {
if !self.tcx.features().move_expr() {
return self.expr_err(*move_kw_span, self.dcx().has_errors().unwrap());
}
if let Some((ident, binding)) = self
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment here, I don't understand what is going on

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. OK, I get it a bit better now (still want a comment), but I don't undersatnd why last is the right thing to do -- I would expect this to be a map, what if there are multiple move() within a single closure?

.move_expr_bindings
.last()
.and_then(|bindings| bindings.get(&e.id).copied())
{
hir::ExprKind::Path(hir::QPath::Resolved(
None,
self.arena.alloc(hir::Path {
span: self.lower_span(e.span),
res: Res::Local(binding),
segments: arena_vec![
self;
hir::PathSegment::new(
self.lower_ident(ident),
self.next_id(),
Res::Local(binding),
)
],
}),
))
} else {
let guar = self
.dcx()
.emit_err(MoveExprOnlyInPlainClosures { span: *move_kw_span });
hir::ExprKind::Err(guar)
}
}
ExprKind::Use(expr, use_kw_span) => self.lower_expr_use(*use_kw_span, expr),
ExprKind::Closure(box Closure {
binder,
capture_clause,
constness,
coroutine_kind,
movability,
fn_decl,
body,
fn_decl_span,
fn_arg_span,
}) => match coroutine_kind {
Some(coroutine_kind) => self.lower_expr_coroutine_closure(
binder,
*capture_clause,
e.id,
expr_hir_id,
*coroutine_kind,
*constness,
fn_decl,
body,
*fn_decl_span,
*fn_arg_span,
),
None => self.lower_expr_closure(
attrs,
binder,
*capture_clause,
e.id,
*constness,
*movability,
fn_decl,
body,
*fn_decl_span,
*fn_arg_span,
),
},
ExprKind::Gen(capture_clause, block, genblock_kind, decl_span) => {
let desugaring_kind = match genblock_kind {
GenBlockKind::Async => hir::CoroutineDesugaring::Async,
Expand Down Expand Up @@ -383,7 +415,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {

ExprKind::Try(sub_expr) => self.lower_expr_try(e.span, sub_expr),

ExprKind::Paren(_) | ExprKind::ForLoop { .. } => {
ExprKind::Paren(_) | ExprKind::ForLoop { .. } | ExprKind::Closure(..) => {
unreachable!("already handled")
}

Expand Down Expand Up @@ -792,6 +824,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
fn_arg_span: None,
kind: hir::ClosureKind::Coroutine(coroutine_kind),
constness: hir::Constness::NotConst,
explicit_captures: &[],
}))
}

Expand Down Expand Up @@ -1055,6 +1088,135 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
hir::ExprKind::Use(self.lower_expr(expr), self.lower_span(use_kw_span))
}

fn lower_expr_closure_expr(&mut self, e: &Expr, closure: &Closure) -> hir::Expr<'hir> {
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have a short comment calling out the key points "manages lowering closure expressions and desugaring move() expressions" or something like that

View changes since the review

let expr_hir_id = self.lower_node_id(e.id);
let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e));

match closure.coroutine_kind {
// FIXME(TaKO8Ki): Support `move(expr)` in coroutine closures too.
// For the first step, we only support plain closures.
Some(coroutine_kind) => hir::Expr {
hir_id: expr_hir_id,
kind: self.lower_expr_coroutine_closure(
&closure.binder,
closure.capture_clause,
e.id,
expr_hir_id,
coroutine_kind,
closure.constness,
&closure.fn_decl,
&closure.body,
closure.fn_decl_span,
closure.fn_arg_span,
),
span: self.lower_span(e.span),
},
None => self.lower_expr_plain_closure_with_move_exprs(
expr_hir_id,
attrs,
&closure.binder,
closure.capture_clause,
e.id,
closure.constness,
closure.movability,
&closure.fn_decl,
&closure.body,
closure.fn_decl_span,
closure.fn_arg_span,
e.span,
),
}
}

fn lower_expr_plain_closure_with_move_exprs(
&mut self,
expr_hir_id: HirId,
attrs: &[rustc_hir::Attribute],
binder: &ClosureBinder,
capture_clause: CaptureBy,
closure_id: NodeId,
constness: Const,
movability: Movability,
decl: &FnDecl,
body: &Expr,
fn_decl_span: Span,
fn_arg_span: Span,
whole_span: Span,
) -> hir::Expr<'hir> {
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment. Give a code example that you can use to walk through the process.

View changes since the review

let occurrences = MoveExprCollector::collect(body);
if occurrences.is_empty() {
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment. What does it mean if this is empty in terms of the Rust source code?

View changes since the review

return hir::Expr {
hir_id: expr_hir_id,
kind: self.lower_expr_closure(
attrs,
binder,
capture_clause,
closure_id,
constness,
movability,
decl,
body,
fn_decl_span,
fn_arg_span,
&[],
),
span: self.lower_span(whole_span),
};
}

let mut bindings = NodeMap::default();
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment this "paragraph". I think that this code is, for each move-expresion, creating a new variable. This would be great to show in terms of the example.

View changes since the review

let mut lowered_occurrences = Vec::with_capacity(occurrences.len());
for (index, occurrence) in occurrences.iter().enumerate() {
let ident =
Ident::from_str_and_span(&format!("__move_expr_{index}"), occurrence.move_kw_span);
let (pat, binding) = self.pat_ident(occurrence.expr.span, ident);
bindings.insert(occurrence.id, (ident, binding));
lowered_occurrences.push((occurrence, pat, binding));
}

self.move_expr_bindings.push(bindings);
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment this "paragraph". I think that this code is, for each move-expresion, creating the let declaration for that variable.

View changes since the review

let mut stmts = Vec::with_capacity(lowered_occurrences.len());
for (occurrence, pat, _) in &lowered_occurrences {
let init = self.lower_expr(occurrence.expr);
stmts.push(self.stmt_let_pat(
None,
occurrence.expr.span,
Some(init),
*pat,
hir::LocalSource::Normal,
));
}

let explicit_captures = self.arena.alloc_from_iter(
lowered_occurrences
.iter()
.map(|(_, _, binding)| hir::ExplicitCapture { var_hir_id: *binding }),
);

let closure_expr = self.arena.alloc(hir::Expr {
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment: lower the closure itself. The move() expressions, when encountered, will match up with the move_expr_bindings vector.

View changes since the review

hir_id: expr_hir_id,
kind: self.lower_expr_closure(
attrs,
binder,
capture_clause,
closure_id,
constness,
movability,
decl,
body,
fn_decl_span,
fn_arg_span,
explicit_captures,
),
span: self.lower_span(whole_span),
});
self.move_expr_bindings.pop();
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd separate this out and put a comment that this restores the original state.

View changes since the review


let stmts = self.arena.alloc_from_iter(stmts);
let block = self.block_all(whole_span, stmts, Some(closure_expr));
self.expr(whole_span, hir::ExprKind::Block(block, None))
}

fn lower_expr_closure(
&mut self,
attrs: &[rustc_hir::Attribute],
Expand All @@ -1067,6 +1229,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
body: &Expr,
fn_decl_span: Span,
fn_arg_span: Span,
explicit_captures: &'hir [hir::ExplicitCapture],
) -> hir::ExprKind<'hir> {
let closure_def_id = self.local_def_id(closure_id);
let (binder_clause, generic_params) = self.lower_closure_binder(binder);
Expand Down Expand Up @@ -1108,6 +1271,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
fn_arg_span: Some(self.lower_span(fn_arg_span)),
kind: closure_kind,
constness: self.lower_constness(constness),
explicit_captures,
});

hir::ExprKind::Closure(c)
Expand Down Expand Up @@ -1230,7 +1394,9 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
// "coroutine that returns &str", rather than directly returning a `&str`.
kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring),
constness: self.lower_constness(constness),
explicit_captures: &[],
});

hir::ExprKind::Closure(c)
}

Expand Down
Loading
Loading