Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c496571
Implement `Thread::os_id`
valentynkit Jul 30, 2026
84d2f10
Store the OS thread id in a `OnceLock`
valentynkit Jul 30, 2026
3a3ff33
Add `Thread::new_current` for current-thread handles
valentynkit Aug 2, 2026
5c8cb4e
Reword the `os_id` docs after review
valentynkit Aug 2, 2026
42cdf8c
Rename the spawned id binding in the `os_id` test
valentynkit Aug 2, 2026
71be0e9
Address review feedback on `Thread::os_id`
valentynkit Aug 10, 2026
1af640e
docs: address docs refinements
valentynkit Sep 7, 2026
e018c24
Add regression test for redundant shared reference suggestions
chenyukang Sep 7, 2026
fb19743
Prefer removing a redundant shared reference over reborrowing
chenyukang Sep 7, 2026
273f7fe
Add support for `DocJson` to `x perf`
Kobzol Sep 8, 2026
04f4b41
Allow passing trailing arguments to `rustc-perf` in `x perf`
Kobzol Sep 8, 2026
19cb4c5
Derive region assumptions from type outlives clauses
Dnreikronos Sep 3, 2026
3a57224
Include implied type outlives bounds at the root
Dnreikronos Sep 3, 2026
822cbdf
Invert free region map edges in root assumptions
Dnreikronos Sep 3, 2026
2bb92c7
lower move expressions in coroutine closures
TaKO8Ki Jun 9, 2026
ee0ee6b
handle move-expression captures in coroutine closures
TaKO8Ki Jun 11, 2026
79bad7d
update move-expression coroutine closure tests
TaKO8Ki Jun 11, 2026
24590c8
rustfmt
TaKO8Ki Jun 11, 2026
db7693a
refactor move expr initializer wrapping
TaKO8Ki Jun 22, 2026
f5c36e8
support move expr in coroutine blocks
TaKO8Ki Jun 22, 2026
7541f06
add coroutine block move expr tests
TaKO8Ki Jun 22, 2026
e831c53
fix nested move expression lowering across capture contexts
TaKO8Ki Aug 12, 2026
b3d7ec7
improve diagnostics for exhausted nested move expressions
TaKO8Ki Aug 12, 2026
7fabedc
add UI coverage for nested move expressions
TaKO8Ki Aug 12, 2026
216cf9e
evaluate coroutine-closure move expressions at closure creation
TaKO8Ki Aug 20, 2026
afff72e
update move-expression closure semantics tests
TaKO8Ki Aug 20, 2026
2ed8d0d
add move-expression tests for generator closures
TaKO8Ki Aug 20, 2026
922fbba
refactoring on upvar_tys
chenyukang Sep 8, 2026
2a61e69
Ignore `self-in-const-generics` test for parallel frontend
JonathanBrouwer Sep 8, 2026
71a523b
Move the `expect-item-after-attribute.rs` test to the correct directory
JonathanBrouwer Sep 8, 2026
74e7a0e
Rollup merge of #157738 - TaKO8Ki:move-expr-coroutine-closures, r=nik…
JonathanBrouwer Sep 8, 2026
629a3ed
Rollup merge of #160219 - valentynkit:thread-os-id, r=nia-e
JonathanBrouwer Sep 8, 2026
0e6f997
Rollup merge of #162449 - chenyukang:yukang-fix-133685-remove-redunda…
JonathanBrouwer Sep 8, 2026
1c50723
Rollup merge of #162494 - JonathanBrouwer:ignore-parallel, r=petroche…
JonathanBrouwer Sep 8, 2026
25e9fa5
Rollup merge of #162238 - Dnreikronos:trait_solver/implied_outlives_a…
JonathanBrouwer Sep 8, 2026
eaadb7a
Rollup merge of #162473 - Kobzol:x-perf, r=JonathanBrouwer
JonathanBrouwer Sep 8, 2026
425d19e
Rollup merge of #162489 - chenyukang:yukang-refactor-upvar_tys, r=Jon…
JonathanBrouwer Sep 8, 2026
2cbdc87
Rollup merge of #162500 - JonathanBrouwer:move-test, r=GuillaumeGomez
JonathanBrouwer Sep 8, 2026
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
13 changes: 11 additions & 2 deletions compiler/rustc_ast_lowering/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,17 @@ pub(crate) struct ClosureCannotBeStatic {
}

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

#[derive(Diagnostic)]
#[diag(
"nested `move(expr)` requires another enclosing closure, `async`, `gen`, or `async gen` block"
)]
pub(crate) struct NestedMoveExprWithoutEnclosingContext {
#[primary_span]
pub span: Span,
}
Expand Down
204 changes: 141 additions & 63 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ mod closure;
use crate::diagnostics::{
AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks,
FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd,
InvalidLegacyConstGenericArg, MatchArmWithNoBody, MoveExprOnlyInPlainClosures,
NeverPatternWithBody, NeverPatternWithGuard, UnderscoreExprLhsAssign, UseConstGenericArg,
YieldInClosure,
InvalidLegacyConstGenericArg, MatchArmWithNoBody, MoveExprOnlyInSupportedContexts,
NestedMoveExprWithoutEnclosingContext, NeverPatternWithBody, NeverPatternWithGuard,
UnderscoreExprLhsAssign, UseConstGenericArg, YieldInClosure,
};
use crate::{
AllowReturnTypeNotation, GenericArgsMode, ImplTraitContext, ImplTraitPosition, LoweringContext,
Expand All @@ -36,30 +36,20 @@ pub(super) struct WillCreateDefIdsVisitor;
struct MoveExprInitializer<'a> {
/// The `NodeId` of the outer `move(...)` expression.
id: NodeId,
/// Span of the `move` token, used for the generated binding name.
move_kw_span: Span,
/// The expression inside `move(...)`; e.g. `foo.bar` in `move(foo.bar)`.
expr: &'a Expr,
}

/// State for `move(...)` expressions found while lowering one plain closure body.
/// State for `move(...)` expressions found while lowering one closure-like body.
#[derive(Default)]
pub(super) struct MoveExprState<'hir> {
pub(super) bindings: NodeMap<(Ident, HirId)>,
pub(super) occurrences: Vec<MoveExprOccurrence<'hir>>,
}

impl<'hir> Default for MoveExprState<'hir> {
fn default() -> Self {
Self { bindings: NodeMap::default(), occurrences: Vec::new() }
}
}

pub(super) struct MoveExprOccurrence<'hir> {
id: NodeId,
ident: Ident,
pat: &'hir hir::Pat<'hir>,
binding: HirId,
explicit_capture: bool,
}

/// Looks up the initializer expression for each `move(...)` occurrence.
Expand All @@ -73,20 +63,22 @@ impl<'a> MoveExprInitializerFinder<'a> {
this.visit_expr(expr);
this.initializers
}

fn collect_block(block: &'a Block) -> Vec<MoveExprInitializer<'a>> {
let mut this = Self { initializers: Vec::new() };
this.visit_block(block);
this.initializers
}
}

impl<'a> Visitor<'a> for MoveExprInitializerFinder<'a> {
fn visit_expr(&mut self, expr: &'a Expr) {
match &expr.kind {
ExprKind::Move(inner, move_kw_span) => {
ExprKind::Move(inner, _) => {
self.visit_expr(inner);
self.initializers.push(MoveExprInitializer {
id: expr.id,
move_kw_span: *move_kw_span,
expr: inner,
});
self.initializers.push(MoveExprInitializer { id: expr.id, expr: inner });
}
ExprKind::Closure(..) | ExprKind::Gen(..) | ExprKind::ConstBlock(..) => {}
ExprKind::ConstBlock(..) => {}
_ => walk_expr(self, expr),
}
}
Expand Down Expand Up @@ -129,13 +121,15 @@ impl<'hir> LoweringContext<'_, 'hir> {
(result, state)
}

fn record_move_expr(
&mut self,
id: NodeId,
inner: &Expr,
move_kw_span: Span,
explicit_capture: bool,
) -> (Ident, HirId) {
fn with_move_expr_initializer<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
let old = self.lowering_move_expr_initializer;
self.lowering_move_expr_initializer = true;
let result = f(self);
self.lowering_move_expr_initializer = old;
result
}

fn record_move_expr(&mut self, id: NodeId, inner: &Expr, move_kw_span: Span) -> (Ident, HirId) {
let index = self
.move_expr_bindings
.last()
Expand All @@ -145,13 +139,74 @@ impl<'hir> LoweringContext<'_, 'hir> {
let (pat, binding) = self.pat_ident(inner.span, ident);
let Some(state) = self.move_expr_bindings.last_mut().and_then(|state| state.as_mut())
else {
span_bug!(move_kw_span, "`move(...)` lowered without a plain closure body state");
span_bug!(move_kw_span, "`move(...)` lowered without a closure-like body state");
};
state.bindings.insert(id, (ident, binding));
state.occurrences.push(MoveExprOccurrence { id, ident, pat, binding, explicit_capture });
state.occurrences.push(MoveExprOccurrence { id, pat, binding });
(ident, binding)
}

fn lower_expr_with_move_exprs(
&mut self,
expr: hir::Expr<'hir>,
move_expr_state: MoveExprState<'hir>,
body: &Expr,
whole_span: Span,
) -> hir::Expr<'hir> {
let initializers = MoveExprInitializerFinder::collect(body);
self.lower_expr_with_move_expr_initializers(expr, move_expr_state, initializers, whole_span)
}

fn lower_expr_with_move_exprs_in_block(
&mut self,
expr: hir::Expr<'hir>,
move_expr_state: MoveExprState<'hir>,
body: &Block,
whole_span: Span,
) -> hir::Expr<'hir> {
let initializers = MoveExprInitializerFinder::collect_block(body);
self.lower_expr_with_move_expr_initializers(expr, move_expr_state, initializers, whole_span)
}

fn lower_expr_with_move_expr_initializers(
&mut self,
expr: hir::Expr<'hir>,
move_expr_state: MoveExprState<'hir>,
initializers: Vec<MoveExprInitializer<'_>>,
whole_span: Span,
) -> hir::Expr<'hir> {
if move_expr_state.occurrences.is_empty() {
return expr;
}

let initializers = initializers
.into_iter()
.map(|initializer| (initializer.id, initializer.expr))
.collect::<NodeMap<_>>();
let mut stmts = Vec::with_capacity(move_expr_state.occurrences.len());
for occurrence in &move_expr_state.occurrences {
// Evaluate the expression inside `move(...)` before creating the
// closure/coroutine and store it in a synthetic local:
// `|| move(foo).bar` becomes roughly
// `let __move_expr_0 = foo; || __move_expr_0.bar`.
let expr = initializers[&occurrence.id];
// This state has already been popped, so a nested `move(...)` in
// the initializer is recorded by the immediately enclosing
// closure-like body instead of this one.
let init = self.with_move_expr_initializer(|this| this.lower_expr(expr));
stmts.push(self.stmt_let_pat(
None,
expr.span,
Some(init),
occurrence.pat,
hir::LocalSource::Normal,
));
}

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

fn lower_exprs(&mut self, exprs: &[Box<Expr>]) -> &'hir [hir::Expr<'hir>] {
self.arena.alloc_from_iter(exprs.iter().map(|x| self.lower_expr_mut(x)))
}
Expand Down Expand Up @@ -305,19 +360,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
if !self.tcx.features().move_expr() {
return self.expr_err(*move_kw_span, self.dcx().has_errors().unwrap());
}
if let Some(state) = self.move_expr_bindings.last().and_then(Option::as_ref) {
let existing = state.bindings.get(&e.id).copied();
let (ident, binding) = existing.unwrap_or_else(|| {
for nested in MoveExprInitializerFinder::collect(inner) {
self.record_move_expr(
nested.id,
nested.expr,
nested.move_kw_span,
false,
);
}
self.record_move_expr(e.id, inner, *move_kw_span, true)
});
if self.move_expr_bindings.last().is_some_and(Option::is_some) {
let (ident, binding) = self.record_move_expr(e.id, inner, *move_kw_span);
hir::ExprKind::Path(hir::QPath::Resolved(
None,
self.arena.alloc(hir::Path {
Expand All @@ -333,9 +377,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
],
}),
))
} else if self.lowering_move_expr_initializer && self.move_expr_bindings.is_empty()
{
let guar = self
.dcx()
.emit_err(NestedMoveExprWithoutEnclosingContext { span: *move_kw_span });
hir::ExprKind::Err(guar)
} else {
let guar =
self.dcx().emit_err(MoveExprOnlyInPlainClosures { span: *move_kw_span });
let guar = self
.dcx()
.emit_err(MoveExprOnlyInSupportedContexts { span: *move_kw_span });
hir::ExprKind::Err(guar)
}
}
Expand All @@ -346,22 +397,34 @@ impl<'hir> LoweringContext<'_, 'hir> {
CoroutineKind::Gen => hir::CoroutineDesugaring::Gen,
CoroutineKind::AsyncGen => hir::CoroutineDesugaring::AsyncGen,
};
self.make_desugared_coroutine_expr(
*capture_clause,
e.id,
None,
*decl_span,
let (kind, move_expr_state) =
self.with_move_expr_bindings(Some(MoveExprState::default()), |this| {
this.make_desugared_coroutine_expr(
*capture_clause,
e.id,
None,
*decl_span,
e.span,
desugaring_kind,
hir::CoroutineSource::Block,
|this| {
this.with_new_scopes(e.span, |this| this.lower_block_expr(block))
},
)
});
let Some(move_expr_state) = move_expr_state else {
span_bug!(
*decl_span,
"coroutine block lowering did not return `move(...)` state"
);
};
let expr = hir::Expr { hir_id: expr_hir_id, kind, span };
return self.lower_expr_with_move_exprs_in_block(
expr,
move_expr_state,
block,
e.span,
desugaring_kind,
hir::CoroutineSource::Block,
|this| {
this.with_new_scopes(e.span, |this| {
let (expr, _) = this
.with_move_expr_bindings(None, |this| this.lower_block_expr(block));
expr
})
},
)
);
}
ExprKind::Block(blk, opt_label) => {
// Different from loops, label of block resolves to block id rather than
Expand Down Expand Up @@ -865,6 +928,21 @@ impl<'hir> LoweringContext<'_, 'hir> {
(params, res)
});

let explicit_captures: &'hir [hir::ExplicitCapture] = match coroutine_source {
hir::CoroutineSource::Block
if let Some(move_expr_state) =
self.move_expr_bindings.last().and_then(Option::as_ref) =>
{
self.arena.alloc_from_iter(
move_expr_state
.occurrences
.iter()
.map(|occurrence| hir::ExplicitCapture { var_hir_id: occurrence.binding }),
)
}
_ => &[],
};

// `static |<_task_context?>| -> <return_ty> { <body> }`:
hir::ExprKind::Closure(self.arena.alloc(hir::Closure {
def_id: closure_def_id,
Expand All @@ -877,7 +955,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
fn_arg_span: None,
kind: hir::ClosureKind::Coroutine(coroutine_kind),
constness: hir::Constness::NotConst,
explicit_captures: &[],
explicit_captures,
}))
}

Expand Down
Loading
Loading