diff --git a/compiler/rustc_ast_lowering/src/diagnostics.rs b/compiler/rustc_ast_lowering/src/diagnostics.rs index b0fada9d3cd9e..2a468cb60546d 100644 --- a/compiler/rustc_ast_lowering/src/diagnostics.rs +++ b/compiler/rustc_ast_lowering/src/diagnostics.rs @@ -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, } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 0a4a2ae7145e3..1c14c645d474c 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -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, @@ -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>, } -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. @@ -73,20 +63,22 @@ impl<'a> MoveExprInitializerFinder<'a> { this.visit_expr(expr); this.initializers } + + fn collect_block(block: &'a Block) -> Vec> { + 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), } } @@ -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(&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() @@ -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>, + 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::>(); + 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]) -> &'hir [hir::Expr<'hir>] { self.arena.alloc_from_iter(exprs.iter().map(|x| self.lower_expr_mut(x))) } @@ -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 { @@ -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) } } @@ -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 @@ -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?>| -> { }`: hir::ExprKind::Closure(self.arena.alloc(hir::Closure { def_id: closure_def_id, @@ -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, })) } diff --git a/compiler/rustc_ast_lowering/src/expr/closure.rs b/compiler/rustc_ast_lowering/src/expr/closure.rs index 2831fb4fa8352..8505d39a718c4 100644 --- a/compiler/rustc_ast_lowering/src/expr/closure.rs +++ b/compiler/rustc_ast_lowering/src/expr/closure.rs @@ -1,19 +1,18 @@ -use rustc_ast::node_id::NodeMap; use rustc_ast::*; use rustc_hir as hir; use rustc_hir::{HirId, Target, find_attr}; use rustc_middle::span_bug; use rustc_span::Span; -use super::{LoweringContext, MoveExprInitializerFinder, MoveExprState}; +use super::{LoweringContext, MoveExprState}; use crate::FnDeclKind; use crate::diagnostics::{ClosureCannotBeStatic, CoroutineTooManyParameters}; impl<'hir> LoweringContext<'_, 'hir> { // Entry point for `ExprKind::Closure`. Plain closures go through // `lower_expr_plain_closure_with_move_exprs`, which can wrap the lowered - // closure in `let` initializers for `move(...)`. Coroutine closures keep the - // existing coroutine-specific path and reject `move(...)` for now. + // closure in `let` initializers for `move(...)`. Coroutine closures use the + // same wrapper after building their coroutine-specific body shape. pub(super) fn lower_expr_closure_expr( &mut self, e: &Expr, @@ -23,25 +22,20 @@ impl<'hir> LoweringContext<'_, 'hir> { let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); match closure.coroutine_marker { - // FIXME(TaKO8Ki): Support `move(expr)` in coroutine closures too. - // For the first step, we only support plain closures. - Some(coroutine_marker) => hir::Expr { - hir_id: expr_hir_id, - kind: self.lower_expr_coroutine_closure( - &closure.binder, - closure.capture_clause, - e.id, - expr_hir_id, - coroutine_marker, - closure.constness, - &closure.fn_decl, - &closure.body, - closure.fn_decl_span, - closure.fn_arg_span, - attrs, - ), - span: self.lower_span(e.span), - }, + Some(coroutine_marker) => self.lower_expr_coroutine_closure_with_move_exprs( + expr_hir_id, + attrs, + &closure.binder, + closure.capture_clause, + e.id, + coroutine_marker, + closure.constness, + &closure.fn_decl, + &closure.body, + closure.fn_decl_span, + closure.fn_arg_span, + e.span, + ), None => self.lower_expr_plain_closure_with_move_exprs( expr_hir_id, attrs, @@ -59,36 +53,65 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + fn lower_expr_coroutine_closure_with_move_exprs( + &mut self, + expr_hir_id: HirId, + attrs: &[hir::Attribute], + binder: &ClosureBinder, + capture_clause: CaptureBy, + closure_id: NodeId, + coroutine_marker: CoroutineMarker, + constness: Const, + decl: &FnDecl, + body: &Expr, + fn_decl_span: Span, + fn_arg_span: Span, + whole_span: Span, + ) -> hir::Expr<'hir> { + let (kind, move_expr_state) = + self.with_move_expr_bindings(Some(MoveExprState::default()), |this| { + this.lower_expr_coroutine_closure( + binder, + capture_clause, + closure_id, + expr_hir_id, + coroutine_marker, + constness, + decl, + body, + fn_decl_span, + fn_arg_span, + attrs, + ) + }); + let Some(move_expr_state) = move_expr_state else { + span_bug!(fn_decl_span, "coroutine closure lowering did not return `move(...)` state"); + }; + let closure_expr = + hir::Expr { hir_id: expr_hir_id, kind, span: self.lower_span(whole_span) }; + + self.lower_expr_with_move_exprs(closure_expr, move_expr_state, body, whole_span) + } + /// Lowers a plain closure expression and wraps it in an outer block if the /// closure body used `move(...)`. /// /// The lowering is split this way because `move(...)` initializers must be /// evaluated before the closure is created, but the closure body must still /// lower each `move(...)` occurrence as a use of the synthetic local that - /// will be introduced by that outer block. For example: - /// - /// ```ignore (illustrative) - /// || (move(move(foo.clone()))).len() - /// ``` - /// - /// first lowers the closure body roughly as `|| __move_expr_1.len()` while - /// recording two occurrences: - /// - /// ```ignore (illustrative) - /// move(foo.clone()) -> __move_expr_0 - /// move(move(foo.clone())) -> __move_expr_1 - /// ``` - /// - /// This method then lowers the recorded initializers in order and builds the - /// surrounding block: + /// will be introduced by that outer block. For example, + /// `|| move(foo.clone()).len()` becomes roughly: /// /// ```ignore (illustrative) /// { /// let __move_expr_0 = foo.clone(); - /// let __move_expr_1 = __move_expr_0; - /// || __move_expr_1.len() + /// || __move_expr_0.len() /// } /// ``` + /// + /// If the initializer contains another `move(...)`, it is lowered after + /// this closure's state is popped and therefore belongs to the immediately + /// enclosing closure-like body. fn lower_expr_plain_closure_with_move_exprs( &mut self, expr_hir_id: HirId, @@ -117,60 +140,13 @@ impl<'hir> LoweringContext<'_, 'hir> { fn_arg_span, ); - if move_expr_state.occurrences.is_empty() { - return hir::Expr { - hir_id: expr_hir_id, - kind: closure_kind, - span: self.lower_span(whole_span), - }; - } - - let initializers = MoveExprInitializerFinder::collect(body) - .into_iter() - .map(|initializer| (initializer.id, initializer.expr)) - .collect::>(); - let mut stmts = Vec::with_capacity(move_expr_state.occurrences.len()); - let mut initializer_bindings = NodeMap::default(); - for occurrence in &move_expr_state.occurrences { - // Evaluate the expression inside `move(...)` before creating the - // closure 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]; - let init = if initializer_bindings.is_empty() { - self.lower_expr(expr) - } else { - // Earlier entries cover nested `move(...)` expressions that - // appear inside this initializer, as in - // `move(move(foo.clone()))`. - let (init, _) = self.with_move_expr_bindings( - Some(MoveExprState { - bindings: initializer_bindings.clone(), - occurrences: Vec::new(), - }), - |this| this.lower_expr(expr), - ); - init - }; - stmts.push(self.stmt_let_pat( - None, - expr.span, - Some(init), - occurrence.pat, - hir::LocalSource::Normal, - )); - initializer_bindings.insert(occurrence.id, (occurrence.ident, occurrence.binding)); - } - - let closure_expr = self.arena.alloc(hir::Expr { + let closure_expr = hir::Expr { hir_id: expr_hir_id, kind: closure_kind, span: self.lower_span(whole_span), - }); + }; - 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)) + self.lower_expr_with_move_exprs(closure_expr, move_expr_state, body, whole_span) } // Lowers the actual plain closure node and body. The body is lowered while a @@ -220,11 +196,10 @@ impl<'hir> LoweringContext<'_, 'hir> { span_bug!(fn_decl_span, "plain closure lowering did not return `move(...)` state"); }; let explicit_captures: &'hir [hir::ExplicitCapture] = self.arena.alloc_from_iter( - move_expr_state.occurrences.iter().filter_map(|occurrence| { - occurrence - .explicit_capture - .then_some(hir::ExplicitCapture { var_hir_id: occurrence.binding }) - }), + move_expr_state + .occurrences + .iter() + .map(|occurrence| hir::ExplicitCapture { var_hir_id: occurrence.binding }), ); let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params); @@ -294,9 +269,9 @@ impl<'hir> LoweringContext<'_, 'hir> { } // Coroutine closures are lowered separately because they build a different - // body shape. This path pushes `None` for `move_expr_bindings`, so any - // `move(...)` in the coroutine body gets a targeted unsupported-position - // error instead of being collected like a plain closure occurrence. + // body shape. The source body is lowered with the caller's `MoveExprState` + // active, so `move(...)` occurrences are collected and hoisted into a block + // around the outer closure expression. fn lower_expr_coroutine_closure( &mut self, binder: &ClosureBinder, @@ -332,16 +307,14 @@ impl<'hir> LoweringContext<'_, 'hir> { // Transform `async |x: u8| -> X { ... }` into // `|x: u8| || -> X { ... }`. let body_id = this.lower_body(|this| { - let ((parameters, expr), _) = this.with_move_expr_bindings(None, |this| { - this.lower_coroutine_body_with_moved_arguments( - &inner_decl, - |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)), - fn_decl_span, - body.span, - coroutine_marker, - hir::CoroutineSource::Closure, - ) - }); + let (parameters, expr) = this.lower_coroutine_body_with_moved_arguments( + &inner_decl, + |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)), + fn_decl_span, + body.span, + coroutine_marker, + hir::CoroutineSource::Closure, + ); this.maybe_forward_track_caller(closure_hir_id, expr.hir_id); @@ -361,6 +334,15 @@ impl<'hir> LoweringContext<'_, 'hir> { self.dcx().span_err(span, "const coroutines are not supported"); } + let explicit_captures: &'hir [hir::ExplicitCapture] = self.arena.alloc_from_iter( + self.move_expr_bindings + .last() + .and_then(Option::as_ref) + .into_iter() + .flat_map(|state| &state.occurrences) + .map(|occurrence| hir::ExplicitCapture { var_hir_id: occurrence.binding }), + ); + let c = self.arena.alloc(hir::Closure { def_id: closure_def_id, binder: binder_clause, @@ -375,7 +357,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // "coroutine that returns &str", rather than directly returning a `&str`. kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring), constness: self.lower_constness(attrs, constness), - explicit_captures: &[], + explicit_captures, }); hir::ExprKind::Closure(c) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index ef6995d9c11d6..43eb398e38e56 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -320,11 +320,14 @@ struct LoweringContext<'a, 'hir> { allow_for_await: Arc<[Symbol]>, allow_async_fn_traits: Arc<[Symbol]>, - /// Stack of `move(...)` collection states. A plain closure body pushes + /// Stack of `move(...)` collection states. A closure-like body pushes /// `Some`, so `move(...)` expressions can record the generated locals they /// should lower to. Nested bodies that cannot use `move(...)` push `None`. move_expr_bindings: Vec>>, + /// Whether an initializer for a recorded `move(...)` is currently being lowered. + lowering_move_expr_initializer: bool, + attribute_parser: AttributeParser<'hir>, } @@ -371,6 +374,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { allow_async_iterator: [sym::gen_future, sym::async_iterator].into(), move_expr_bindings: Vec::new(), + lowering_move_expr_initializer: false, attribute_parser: AttributeParser::new( tcx.sess, tcx.features(), diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 38839c598f913..167cb1f272533 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -290,18 +290,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // moved, and so on. let _ = euv::ExprUseVisitor::new(&closure_fcx, &mut delegate).consume_body(body); - // `consume_body` only sees how the lowered closure body uses those - // places. For `move(foo).clone()`, the body may only borrow the - // synthetic local for `foo`, but the source `move(...)` still requires - // capturing that local by value. + // Save the captures that must be upgraded to by-value after inferring + // the closure kind from the operations in the body. let explicit_captures = match self.tcx.hir_node(closure_hir_id).expect_expr().kind { hir::ExprKind::Closure(closure) => closure.explicit_captures, _ => bug!("expected closure expr for {:?}", closure_hir_id), }; - for capture in explicit_captures { - let place = closure_fcx.place_for_root_variable(closure_def_id, capture.var_hir_id); - delegate.consume(&PlaceWithHirId { hir_id: capture.var_hir_id, place }, closure_hir_id); - } // There are several curious situations with coroutine-closures where // analysis is too aggressive with borrows when the coroutine-closure is @@ -400,9 +394,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.log_capture_analysis_first_pass(closure_def_id, &delegate.capture_information, span); - let (capture_information, closure_kind, origin) = self + let (mut capture_information, closure_kind, origin) = self .process_collected_capture_information(capture_clause, &delegate.capture_information); + // `move(expr)` requires its synthetic local to be captured by value, + // regardless of how the closure body uses it. Apply that requirement + // after closure-kind inference so capturing a value does not by itself + // make the closure `FnOnce`. + for capture in explicit_captures { + let place = closure_fcx.place_for_root_variable(closure_def_id, capture.var_hir_id); + capture_information.push(( + place, + ty::CaptureInfo { + capture_kind_expr_id: Some(closure_hir_id), + path_expr_id: Some(closure_hir_id), + capture_kind: UpvarCapture::ByValue, + }, + )); + } + self.compute_min_captures(closure_def_id, capture_information, span); let closure_hir_id = self.tcx.local_def_id_to_hir_id(closure_def_id); diff --git a/tests/ui/move-expr/async-blocks.rs b/tests/ui/move-expr/async-blocks.rs new file mode 100644 index 0000000000000..4f211cc961572 --- /dev/null +++ b/tests/ui/move-expr/async-blocks.rs @@ -0,0 +1,77 @@ +//@ edition: 2021 +//@ run-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +use std::cell::Cell; +use std::future::Future; +use std::sync::Arc; +use std::task::{Context, Poll, Waker}; + +fn block_on(future: F) -> F::Output { + let mut future = Box::pin(future); + let cx = &mut Context::from_waker(Waker::noop()); + loop { + match future.as_mut().poll(cx) { + Poll::Ready(output) => return output, + Poll::Pending => {} + } + } +} + +fn main() { + let created = Cell::new(0); + let fut = async { + let n = move({ + created.set(created.get() + 1); + created.get() + }); + n + }; + assert_eq!(created.get(), 1); + drop(fut); + + let x = Arc::new(String::from("hello")); + assert_eq!(Arc::strong_count(&x), 1); + let fut = async { move(x.clone()) }; + assert_eq!(Arc::strong_count(&x), 2); + drop(fut); + assert_eq!(Arc::strong_count(&x), 1); + + let y = Arc::new(String::from("nested once")); + let weak = Arc::downgrade(&y); + let fut = async { + let inner = async { + drop(move(y.clone())); + }; + assert_eq!(weak.strong_count(), 2); + inner.await; + assert_eq!(weak.strong_count(), 1); + drop(y); + }; + assert_eq!(weak.strong_count(), 1); + block_on(fut); + assert_eq!(weak.strong_count(), 0); + + let y = Arc::new(String::from("nested twice")); + let weak = Arc::downgrade(&y); + let fut = async { + let inner = async { + drop(move(move(y.clone()))); + }; + assert_eq!(weak.strong_count(), 2); + inner.await; + assert_eq!(weak.strong_count(), 1); + }; + assert_eq!(weak.strong_count(), 2); + block_on(fut); + assert_eq!(weak.strong_count(), 1); + assert_eq!(&*y, "nested twice"); + + let z = Arc::new(String::from("async move")); + assert_eq!(Arc::strong_count(&z), 1); + let fut = async move { move(z.clone()) }; + assert_eq!(Arc::strong_count(&z), 2); + drop(fut); + assert_eq!(Arc::strong_count(&z), 1); +} diff --git a/tests/ui/move-expr/async-closures.rs b/tests/ui/move-expr/async-closures.rs index eea93f02b807a..b467248048367 100644 --- a/tests/ui/move-expr/async-closures.rs +++ b/tests/ui/move-expr/async-closures.rs @@ -1,11 +1,59 @@ //@ edition: 2021 +//@ run-pass #![allow(incomplete_features)] #![feature(move_expr)] +use std::cell::Cell; +use std::future::Future; +use std::pin::pin; +use std::sync::Arc; +use std::task::{Context, Poll, Waker}; + +fn block_on(future: impl Future) -> T { + let mut future = pin!(future); + let context = &mut Context::from_waker(Waker::noop()); + + loop { + match future.as_mut().poll(context) { + Poll::Ready(value) => return value, + Poll::Pending => {} + } + } +} + +async fn call_once(closure: impl AsyncFnOnce() -> T) -> T { + closure().await +} + fn main() { - let s = String::from("hello"); - let _ = async || { - move(s); - //~^ ERROR `move(expr)` is only supported in plain closures + let created = Cell::new(0); + let c = async || { + let n = move({ + created.set(created.get() + 1); + created.get() + }); + n + }; + assert_eq!(created.get(), 1); + assert_eq!(block_on(c()), 1); + assert_eq!(block_on(c()), 1); + assert_eq!(created.get(), 1); + + let x = Arc::new(String::from("hello")); + assert_eq!(Arc::strong_count(&x), 1); + + let c = async || move(x.clone()); + assert_eq!(Arc::strong_count(&x), 2); + let fut = c(); + assert_eq!(Arc::strong_count(&x), 2); + drop(fut); + assert_eq!(Arc::strong_count(&x), 1); + + let a = String::from("a"); + let b = String::from("bbb"); + let c = async || { + let moved = move(a.clone()); + (moved, b.len()) }; + assert_eq!(block_on(call_once(c)), (String::from("a"), 3)); } diff --git a/tests/ui/move-expr/async-closures.stderr b/tests/ui/move-expr/async-closures.stderr deleted file mode 100644 index d0fd5c8ee7df0..0000000000000 --- a/tests/ui/move-expr/async-closures.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: `move(expr)` is only supported in plain closures - --> $DIR/async-closures.rs:8:9 - | -LL | move(s); - | ^^^^ - -error: aborting due to 1 previous error - diff --git a/tests/ui/move-expr/async-gen-blocks.rs b/tests/ui/move-expr/async-gen-blocks.rs new file mode 100644 index 0000000000000..f77123751e1d5 --- /dev/null +++ b/tests/ui/move-expr/async-gen-blocks.rs @@ -0,0 +1,104 @@ +//@ edition: 2024 +//@ run-pass +#![allow(incomplete_features)] +#![feature(async_iterator, gen_blocks, move_expr)] + +use std::async_iter::AsyncIterator; +use std::cell::Cell; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll, Waker}; + +struct PendingOnce { + pending: bool, +} + +impl PendingOnce { + fn new() -> Self { + Self { pending: true } + } +} + +impl Future for PendingOnce { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + if self.pending { + self.pending = false; + cx.waker().wake_by_ref(); + Poll::Pending + } else { + Poll::Ready(()) + } + } +} + +fn poll_next(iter: Pin<&mut I>) -> Poll> { + let cx = &mut Context::from_waker(Waker::noop()); + AsyncIterator::poll_next(iter, cx) +} + +fn ready_next(iter: Pin<&mut I>) -> Option { + match poll_next(iter) { + Poll::Ready(item) => item, + Poll::Pending => panic!("async iterator unexpectedly returned pending"), + } +} + +fn main() { + let created = Cell::new(0); + let mut iter = Box::pin(async gen { + let n = move({ + created.set(created.get() + 1); + created.get() + }); + yield n; + yield n + 1; + }); + assert_eq!(created.get(), 1); + assert_eq!(ready_next(iter.as_mut()), Some(1)); + assert_eq!(ready_next(iter.as_mut()), Some(2)); + assert_eq!(ready_next(iter.as_mut()), None); + + let x = Arc::new(String::from("hello")); + assert_eq!(Arc::strong_count(&x), 1); + let mut iter = Box::pin(async gen { + let value = move(x.clone()); + yield Arc::strong_count(&value); + PendingOnce::new().await; + yield Arc::strong_count(&value); + }); + assert_eq!(Arc::strong_count(&x), 2); + assert_eq!(ready_next(iter.as_mut()), Some(2)); + assert!(matches!(poll_next(iter.as_mut()), Poll::Pending)); + assert_eq!(ready_next(iter.as_mut()), Some(2)); + drop(iter); + assert_eq!(Arc::strong_count(&x), 1); + + let y = Arc::new(String::from("nested")); + let weak = Arc::downgrade(&y); + let mut iter = Box::pin(async gen { + let mut inner = Box::pin(async gen { + let value = move(move(y.clone())); + yield Arc::strong_count(&value); + }); + yield ready_next(inner.as_mut()).unwrap(); + }); + assert_eq!(weak.strong_count(), 2); + assert_eq!(ready_next(iter.as_mut()), Some(2)); + drop(iter); + assert_eq!(weak.strong_count(), 1); + assert_eq!(&*y, "nested"); + + let z = Arc::new(String::from("async gen move")); + assert_eq!(Arc::strong_count(&z), 1); + let mut iter = Box::pin(async gen move { + let value = move(z.clone()); + yield Arc::strong_count(&value); + }); + assert_eq!(Arc::strong_count(&z), 2); + assert_eq!(ready_next(iter.as_mut()), Some(2)); + drop(iter); + assert_eq!(Arc::strong_count(&z), 1); +} diff --git a/tests/ui/move-expr/gen-blocks.rs b/tests/ui/move-expr/gen-blocks.rs new file mode 100644 index 0000000000000..b38313b83a8f7 --- /dev/null +++ b/tests/ui/move-expr/gen-blocks.rs @@ -0,0 +1,62 @@ +//@ edition: 2024 +//@ run-pass +#![allow(incomplete_features)] +#![feature(gen_blocks, move_expr)] + +use std::cell::Cell; +use std::sync::Arc; + +fn main() { + let created = Cell::new(0); + let mut iter = gen { + let n = move({ + created.set(created.get() + 1); + created.get() + }); + yield n; + yield n + 1; + }; + assert_eq!(created.get(), 1); + assert_eq!(iter.next(), Some(1)); + assert_eq!(iter.next(), Some(2)); + assert_eq!(iter.next(), None); + + let x = Arc::new(String::from("hello")); + assert_eq!(Arc::strong_count(&x), 1); + let mut iter = gen { + let value = move(x.clone()); + yield Arc::strong_count(&value); + yield Arc::strong_count(&value); + }; + assert_eq!(Arc::strong_count(&x), 2); + assert_eq!(iter.next(), Some(2)); + assert_eq!(iter.next(), Some(2)); + drop(iter); + assert_eq!(Arc::strong_count(&x), 1); + + let y = Arc::new(String::from("nested")); + let weak = Arc::downgrade(&y); + let mut iter = gen { + let mut inner = gen { + let value = move(move(y.clone())); + yield Arc::strong_count(&value); + }; + yield inner.next().unwrap(); + }; + assert_eq!(weak.strong_count(), 2); + assert_eq!(iter.next(), Some(2)); + drop(iter); + assert_eq!(weak.strong_count(), 1); + assert_eq!(&*y, "nested"); + + let z = Arc::new(String::from("gen move")); + assert_eq!(Arc::strong_count(&z), 1); + let mut iter = gen move { + let value = move(z.clone()); + yield Arc::strong_count(&value); + }; + assert_eq!(Arc::strong_count(&z), 2); + assert_eq!(iter.next(), Some(2)); + drop(iter); + assert_eq!(Arc::strong_count(&z), 1); +} diff --git a/tests/ui/move-expr/gen-closures.rs b/tests/ui/move-expr/gen-closures.rs new file mode 100644 index 0000000000000..5e74089710adf --- /dev/null +++ b/tests/ui/move-expr/gen-closures.rs @@ -0,0 +1,46 @@ +//@ run-pass + +#![allow(incomplete_features)] +#![feature(iter_macro, move_expr, yield_expr)] + +use std::cell::Cell; +use std::iter::iter; +use std::sync::Arc; + +fn main() { + let created = Cell::new(0); + let closure = iter! { || { + let n = move({ + created.set(created.get() + 1); + created.get() + }); + yield n; + }}; + assert_eq!(created.get(), 1); + assert_eq!(closure().next(), Some(1)); + assert_eq!(closure().next(), Some(1)); + assert_eq!(created.get(), 1); + + let x = Arc::new(String::from("hello")); + assert_eq!(Arc::strong_count(&x), 1); + + let closure = iter! { || { + yield move(x.clone()); + }}; + assert_eq!(Arc::strong_count(&x), 2); + let mut generator = closure(); + assert_eq!(Arc::strong_count(&x), 2); + let yielded = generator.next().unwrap(); + assert_eq!(Arc::strong_count(&x), 2); + assert_eq!(generator.next(), None); + drop(yielded); + assert_eq!(Arc::strong_count(&x), 1); + + let a = String::from("a"); + let b = String::from("bbb"); + let closure = iter! { || { + let moved = move(a.clone()); + yield (moved, b.len()); + }}; + assert_eq!(closure().next(), Some((String::from("a"), 3))); +} diff --git a/tests/ui/move-expr/nested-async-block-ownership.rs b/tests/ui/move-expr/nested-async-block-ownership.rs new file mode 100644 index 0000000000000..32dbd808861f8 --- /dev/null +++ b/tests/ui/move-expr/nested-async-block-ownership.rs @@ -0,0 +1,17 @@ +//@ edition: 2021 +#![allow(incomplete_features)] +#![feature(move_expr)] + +use std::sync::Arc; + +fn main() { + let c = Arc::new(String::new()); + let _future = async { + let f = async { + drop(move(c.clone())); + }; + f.await; + drop(c); + }; + println!("{c}"); //~ ERROR the type `Arc` does not implement `Copy` +} diff --git a/tests/ui/move-expr/nested-async-block-ownership.stderr b/tests/ui/move-expr/nested-async-block-ownership.stderr new file mode 100644 index 0000000000000..fb3030d995230 --- /dev/null +++ b/tests/ui/move-expr/nested-async-block-ownership.stderr @@ -0,0 +1,23 @@ +error[E0382]: the type `Arc` does not implement `Copy` + --> $DIR/nested-async-block-ownership.rs:16:16 + | +LL | let c = Arc::new(String::new()); + | - this move could be avoided by cloning the original `Arc`, which is inexpensive +LL | let _future = async { + | ----- value moved here +... +LL | drop(c); + | - variable moved due to use in coroutine +LL | }; +LL | println!("{c}"); + | ^ value borrowed here after move + | + = note: consider using `Arc::clone` +help: clone the value to increment its reference count + | +LL | drop(c.clone()); + | ++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`. diff --git a/tests/ui/move-expr/nested-move-exhausted.rs b/tests/ui/move-expr/nested-move-exhausted.rs new file mode 100644 index 0000000000000..8508cc1c756b5 --- /dev/null +++ b/tests/ui/move-expr/nested-move-exhausted.rs @@ -0,0 +1,20 @@ +//@ edition: 2024 +#![allow(incomplete_features)] +#![feature(async_iterator, gen_blocks, move_expr)] + +fn main() { + let _ = || move(move(0)); + //~^ ERROR nested `move(expr)` requires another enclosing closure + + let _ = async || move(move(0)); + //~^ ERROR nested `move(expr)` requires another enclosing closure + + let _ = async { move(move(0)) }; + //~^ ERROR nested `move(expr)` requires another enclosing closure + + let _ = gen { move(move(0)) }; + //~^ ERROR nested `move(expr)` requires another enclosing closure + + let _ = async gen { move(move(0)) }; + //~^ ERROR nested `move(expr)` requires another enclosing closure +} diff --git a/tests/ui/move-expr/nested-move-exhausted.stderr b/tests/ui/move-expr/nested-move-exhausted.stderr new file mode 100644 index 0000000000000..2c919666c75ae --- /dev/null +++ b/tests/ui/move-expr/nested-move-exhausted.stderr @@ -0,0 +1,32 @@ +error: nested `move(expr)` requires another enclosing closure, `async`, `gen`, or `async gen` block + --> $DIR/nested-move-exhausted.rs:6:21 + | +LL | let _ = || move(move(0)); + | ^^^^ + +error: nested `move(expr)` requires another enclosing closure, `async`, `gen`, or `async gen` block + --> $DIR/nested-move-exhausted.rs:9:27 + | +LL | let _ = async || move(move(0)); + | ^^^^ + +error: nested `move(expr)` requires another enclosing closure, `async`, `gen`, or `async gen` block + --> $DIR/nested-move-exhausted.rs:12:26 + | +LL | let _ = async { move(move(0)) }; + | ^^^^ + +error: nested `move(expr)` requires another enclosing closure, `async`, `gen`, or `async gen` block + --> $DIR/nested-move-exhausted.rs:15:24 + | +LL | let _ = gen { move(move(0)) }; + | ^^^^ + +error: nested `move(expr)` requires another enclosing closure, `async`, `gen`, or `async gen` block + --> $DIR/nested-move-exhausted.rs:18:30 + | +LL | let _ = async gen { move(move(0)) }; + | ^^^^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/move-expr/nested-move-expr.rs b/tests/ui/move-expr/nested-move-expr.rs index cf3364c50aad7..b6e0f70b355d7 100644 --- a/tests/ui/move-expr/nested-move-expr.rs +++ b/tests/ui/move-expr/nested-move-expr.rs @@ -1,12 +1,21 @@ -//@ check-pass +//@ run-pass #![allow(incomplete_features)] #![feature(move_expr)] +use std::sync::Arc; + fn main() { - let v = "Hello, Ferris".to_string(); - let r = || { - || (move(move(v.clone()))).len() - }; + let v = Arc::new("Hello, Ferris".to_string()); + let outer = || || (move(move(v.clone()))).len(); + + assert_eq!(Arc::strong_count(&v), 2); + let inner = outer(); + assert_eq!(Arc::strong_count(&v), 2); + assert_eq!(inner(), v.len()); + assert_eq!(inner(), v.len()); + assert_eq!(Arc::strong_count(&v), 2); + drop(inner); + assert_eq!(Arc::strong_count(&v), 1); - assert_eq!(r()(), v.len()); + println!("{v}"); } diff --git a/tests/ui/move-expr/outside-plain-closure.rs b/tests/ui/move-expr/outside-plain-closure.rs index c4aa6551119fe..881c00d32aa11 100644 --- a/tests/ui/move-expr/outside-plain-closure.rs +++ b/tests/ui/move-expr/outside-plain-closure.rs @@ -3,5 +3,5 @@ fn main() { let _ = move(String::from("nope")); - //~^ ERROR `move(expr)` is only supported in plain closures + //~^ ERROR `move(expr)` is only supported in closures, `async`, `gen`, and `async gen` blocks } diff --git a/tests/ui/move-expr/outside-plain-closure.stderr b/tests/ui/move-expr/outside-plain-closure.stderr index 68c4223641304..8654f52bf4ac4 100644 --- a/tests/ui/move-expr/outside-plain-closure.stderr +++ b/tests/ui/move-expr/outside-plain-closure.stderr @@ -1,4 +1,4 @@ -error: `move(expr)` is only supported in plain closures +error: `move(expr)` is only supported in closures, `async`, `gen`, and `async gen` blocks --> $DIR/outside-plain-closure.rs:5:13 | LL | let _ = move(String::from("nope")); diff --git a/tests/ui/move-expr/parse-ambiguity-errors.rs b/tests/ui/move-expr/parse-ambiguity-errors.rs index c2927373cb8a7..c9428770538f7 100644 --- a/tests/ui/move-expr/parse-ambiguity-errors.rs +++ b/tests/ui/move-expr/parse-ambiguity-errors.rs @@ -5,7 +5,7 @@ fn main() { let x: bool = true; let y: bool = true; let _ = move(x) || y; - //~^ ERROR `move(expr)` is only supported in plain closures + //~^ ERROR `move(expr)` is only supported in closures, `async`, `gen`, and `async gen` blocks let x: bool = true; let y: bool = true; diff --git a/tests/ui/move-expr/parse-ambiguity-errors.stderr b/tests/ui/move-expr/parse-ambiguity-errors.stderr index c4dc929eac36c..17a397cc26900 100644 --- a/tests/ui/move-expr/parse-ambiguity-errors.stderr +++ b/tests/ui/move-expr/parse-ambiguity-errors.stderr @@ -4,7 +4,7 @@ error: expected one of `async`, `|`, or `||`, found `[` LL | let _ = move[x] || y; | ^ expected one of `async`, `|`, or `||` -error: `move(expr)` is only supported in plain closures +error: `move(expr)` is only supported in closures, `async`, `gen`, and `async gen` blocks --> $DIR/parse-ambiguity-errors.rs:7:13 | LL | let _ = move(x) || y; diff --git a/tests/ui/move-expr/plain-closure.rs b/tests/ui/move-expr/plain-closure.rs index 788c631cf5fdf..3f58142f7ea9c 100644 --- a/tests/ui/move-expr/plain-closure.rs +++ b/tests/ui/move-expr/plain-closure.rs @@ -1,8 +1,23 @@ -//@ check-pass +//@ run-pass #![allow(incomplete_features)] #![feature(move_expr)] +use std::cell::Cell; + fn main() { + let created = Cell::new(0); + let c = || { + let n = move({ + created.set(created.get() + 1); + created.get() + }); + n + }; + assert_eq!(created.get(), 1); + assert_eq!(c(), 1); + assert_eq!(c(), 1); + assert_eq!(created.get(), 1); + let s = String::from("hello"); let c = || { let t = move(s); @@ -18,5 +33,4 @@ fn main() { println!("{} {}", x, y); }; c(); - }