diff --git a/compiler/rustc_ast_lowering/src/diagnostics.rs b/compiler/rustc_ast_lowering/src/diagnostics.rs index 2542268712f98..8c298c57040e9 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 ac79703e63c01..a27dc47bf27c3 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -322,11 +322,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>, } @@ -373,6 +376,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_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index ed080c26d60f3..96147fc934df4 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -201,6 +201,7 @@ pub(crate) fn type_check<'tcx>( solver_constraints, &mut converter, typeck.known_type_outlives_obligations, + typeck.region_bound_pairs, universal_region_relations.outlives.clone(), ); } diff --git a/compiler/rustc_data_structures/src/sso/map.rs b/compiler/rustc_data_structures/src/sso/map.rs index 827c82fa46a11..018cbd96317db 100644 --- a/compiler/rustc_data_structures/src/sso/map.rs +++ b/compiler/rustc_data_structures/src/sso/map.rs @@ -356,7 +356,9 @@ impl Extend<(K, V)> for SsoHashMap { where I: IntoIterator, { - for (key, value) in iter.into_iter() { + let iter = iter.into_iter(); + self.reserve(iter.size_hint().0); + for (key, value) in iter { self.insert(key, value); } } diff --git a/compiler/rustc_data_structures/src/sso/set.rs b/compiler/rustc_data_structures/src/sso/set.rs index e3fa1cbf4cc57..cf75ea4537012 100644 --- a/compiler/rustc_data_structures/src/sso/set.rs +++ b/compiler/rustc_data_structures/src/sso/set.rs @@ -168,7 +168,9 @@ impl Extend for SsoHashSet { where I: IntoIterator, { - for val in iter.into_iter() { + let iter = iter.into_iter(); + self.reserve(iter.size_hint().0); + for val in iter { self.insert(val); } } diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 0dee9690737df..34aafd72526a8 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2406,8 +2406,12 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { for &(r1, r2) in &body.region_outlives { builder.add(r1, r2); } - let assumptions = - ty::region_constraint::Assumptions::new(body.type_outlives, builder.freeze()); + // Deliberately unelaborated: the assumptions of a `forall` are exactly the ones + // written down in the test, no extra ones hidden behind the scenes. + let assumptions = ty::region_constraint::Assumptions::new_unelaborated( + body.type_outlives, + builder.freeze(), + ); self.infcx.insert_placeholder_assumptions(u, Some(assumptions)); self.check_test_binder_body(body.value); let solver_region_constraint = self.infcx.get_solver_region_constraint(); 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/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 7e6297c7225e3..a6c610edebf02 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -59,14 +59,14 @@ //! might later infer `?U` to something like `&'b u32`, which would //! imply that `'b: 'a`. -use rustc_data_structures::transitive_relation::TransitiveRelation; +use rustc_data_structures::transitive_relation::{TransitiveRelation, TransitiveRelationBuilder}; use rustc_data_structures::undo_log::UndoLogs; use rustc_middle::bug; use rustc_middle::mir::ConstraintCategory; use rustc_middle::ty::outlives::{Component, push_outlives_components}; use rustc_middle::ty::{ self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesClause, Region, RegionVid, Ty, TyCtxt, - TypeVisitableExt, eager_resolve_vars, + TypeVisitableExt, Upcast, eager_resolve_vars, }; use rustc_span::Span; use rustc_type_ir::region_constraint::{self, LeafRegionConstraint}; @@ -234,9 +234,22 @@ impl<'tcx> InferCtxt<'tcx> { &self, outlives_env: &OutlivesEnvironment<'tcx>, ) { + // `FreeRegionMap::relation` stores `'sub <= 'sup` edges while + // `Assumptions::region_outlives` expects `'longer: 'shorter` ones, so the + // edges have to be inverted here. + let mut region_outlives = TransitiveRelationBuilder::default(); + for (r1, r2) in outlives_env.free_region_map().relation.base_edges() { + region_outlives.add(r2, r1); + } let assumptions = rustc_type_ir::region_constraint::Assumptions::new( - outlives_env.known_type_outlives().into_iter().cloned().collect(), - outlives_env.free_region_map().relation.clone(), + self, + assumed_type_outlives( + self.tcx, + outlives_env.known_type_outlives(), + outlives_env.region_bound_pairs(), + ), + region_outlives.freeze(), + ty::UniverseIndex::ROOT, ); let constraint = self.inner.borrow().solver_region_constraint_storage.get_constraint(); self.destructure_solver_region_constraints(constraint, assumptions, self); @@ -251,11 +264,14 @@ impl<'tcx> InferCtxt<'tcx> { // this is always ConstraintConversion but lol conversion: impl TypeOutlivesDelegate<'tcx>, known_type_outlives: &[PolyTypeOutlivesClause<'tcx>], + region_bound_pairs: &RegionBoundPairs<'tcx>, region_outlives: TransitiveRelation, ) { let assumptions = region_constraint::Assumptions::new( - known_type_outlives.into_iter().cloned().collect(), + self, + assumed_type_outlives(self.tcx, known_type_outlives, region_bound_pairs), region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(), + ty::UniverseIndex::ROOT, ); self.destructure_solver_region_constraints(constraint, assumptions, conversion); } @@ -381,6 +397,28 @@ impl<'tcx> InferCtxt<'tcx> { } } +/// The type outlives assumptions available in the root context, as clauses for +/// [`region_constraint::Assumptions::new`] to elaborate. +/// +/// `known_type_outlives` only contains the explicit `Ty: 'a` where clauses. The implied bounds, +/// e.g. `T: 'a` from a `&'a T` argument, are only tracked in `region_bound_pairs` so we have to +/// pull them in separately. Without them we'd fail to prove `T: 'a` for a `&'a T` argument +/// whenever the only explicit bound on `T` mentions a different region. +fn assumed_type_outlives<'tcx>( + tcx: TyCtxt<'tcx>, + known_type_outlives: &[PolyTypeOutlivesClause<'tcx>], + region_bound_pairs: &RegionBoundPairs<'tcx>, +) -> Vec> { + known_type_outlives + .iter() + .copied() + .chain(region_bound_pairs.iter().map(|&ty::OutlivesClause(kind, r)| { + ty::Binder::dummy(ty::OutlivesClause(kind.to_ty(tcx), r)) + })) + .map(|c| c.map_bound(ty::ClauseKind::TypeOutlives).upcast(tcx)) + .collect() +} + /// The `TypeOutlives` struct has the job of "lowering" a `T: 'a` /// obligation into a series of `'a: 'b` constraints and "verify"s, as /// described on the module comment. The final constraints are emitted diff --git a/compiler/rustc_infer/src/traits/util.rs b/compiler/rustc_infer/src/traits/util.rs index cc29546adb880..0380488caf96f 100644 --- a/compiler/rustc_infer/src/traits/util.rs +++ b/compiler/rustc_infer/src/traits/util.rs @@ -47,6 +47,8 @@ impl<'tcx> PredicateSet<'tcx> { impl<'tcx> Extend> for PredicateSet<'tcx> { fn extend>>(&mut self, iter: I) { + let iter = iter.into_iter(); + self.set.reserve(iter.size_hint().0); for pred in iter { self.insert(pred); } diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 8014a52c9b4e1..6f76bc0ff028b 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -188,15 +188,9 @@ impl<'tcx> UpvarArgs<'tcx> { /// empty iterator is returned. #[inline] pub fn upvar_tys(self) -> &'tcx List> { - let tupled_tys = match self { - UpvarArgs::Closure(args) => args.as_closure().tupled_upvars_ty(), - UpvarArgs::Coroutine(args) => args.as_coroutine().tupled_upvars_ty(), - UpvarArgs::CoroutineClosure(args) => args.as_coroutine_closure().tupled_upvars_ty(), - }; - - match tupled_tys.kind() { + match self.tupled_upvars_ty().kind() { TyKind::Error(_) => ty::List::empty(), - TyKind::Tuple(..) => self.tupled_upvars_ty().tuple_fields(), + TyKind::Tuple(args) => args, TyKind::Infer(_) => bug!("upvar_tys called before capture types are inferred"), ty => bug!("Unexpected representation of upvar types tuple {:?}", ty), } diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 4ee1abe4a1ff4..622666a12ef40 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -345,6 +345,8 @@ impl<'tcx> Extend>> for MonoItems<'tcx> { where I: IntoIterator>>, { + let iter = iter.into_iter(); + self.items.reserve(iter.size_hint().0); for item in iter { self.push(item) } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index 5a4daa5e44fc5..5ecc06b30f33b 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -2,7 +2,6 @@ #[cfg(feature = "nightly")] use rustc_data_structures::transitive_relation::TransitiveRelationBuilder; -use rustc_type_ir::ClauseKind::*; use rustc_type_ir::inherent::*; use rustc_type_ir::outlives::{Component, push_outlives_components}; #[cfg(not(feature = "nightly"))] @@ -12,8 +11,8 @@ use rustc_type_ir::region_constraint::{ propagate_ambiguity, }; use rustc_type_ir::{ - AliasTy, Binder, ClauseKind, InferCtxtLike, Interner, OutlivesClause, Region, TypeVisitable, - TypeVisitableExt, TypeVisitor, UniverseIndex, max_universe, + AliasTy, Binder, ClauseKind, InferCtxtLike, Interner, Region, TypeVisitable, TypeVisitableExt, + TypeVisitor, UniverseIndex, }; use tracing::{debug, instrument}; @@ -82,9 +81,6 @@ where t.visit_with(&mut reqs_builder); let reqs = reqs_builder.out; - let mut region_outlives_builder = TransitiveRelationBuilder::default(); - let mut type_outlives = vec![]; - // If there are inference variables in type outlives then we may not be able // to elaborate to the full set of implied bounds right now. To avoid incorrectly // NoSolution'ing when lifting constraints to a lower universe due to no usable @@ -102,25 +98,17 @@ where // FIXME(-Zassumptions-on-binders): we need to normalize here/somewhere // as we assume the type outlives assumptions only have rigid types :> - let clauses = rustc_type_ir::elaborate::elaborate( - self.cx(), - reqs.into_iter().filter_map(|goal| goal.predicate.as_clause()), - ); - - clauses.filter(move |clause| max_universe(&**self.delegate, *clause) == u).for_each( - |clause| match clause.kind().skip_binder() { - RegionOutlives(OutlivesClause(r1, r2)) => { - assert!(clause.kind().no_bound_vars().is_some()); - region_outlives_builder.add(r1, r2); - } - TypeOutlives(p) => { - type_outlives.push(clause.kind().map_bound(|_| p)); - } - _ => (), - }, - ); - - Some(Assumptions::new(type_outlives, region_outlives_builder.freeze())) + // + // `Assumptions::new` elaborates, restricts the clauses to `u` and picks out the + // outlives ones for us, so we just hand over everything the requirements gave us. + let clauses = reqs.into_iter().filter_map(|goal| goal.predicate.as_clause()); + + Some(Assumptions::new( + &**self.delegate, + clauses, + TransitiveRelationBuilder::default().freeze(), + u, + )) } #[instrument(level = "debug", skip(self), ret)] diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 7788a1bb62a09..d8a22e745bcc2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -478,7 +478,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { leaf_trait_predicate, ); suggested |= - self.suggest_dereferences(&obligation, &mut err, leaf_trait_predicate); + self.suggest_dereferences(&obligation, &mut err, leaf_trait_predicate) + || self.suggest_remove_reference( + &obligation, + &mut err, + leaf_trait_predicate, + ); + suggested |= self.suggest_fn_call(&obligation, &mut err, leaf_trait_predicate); suggested |= self.suggest_cast_to_fn_pointer( @@ -488,11 +494,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { main_trait_predicate, span, ); - suggested |= self.suggest_remove_reference( - &obligation, - &mut err, - leaf_trait_predicate, - ); + suggested |= self.suggest_semicolon_removal( &obligation, &mut err, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 65e589d336500..2fbc6f16c14d4 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -780,6 +780,17 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { if span.in_external_macro(self.tcx.sess.source_map()) { return false; } + + // For a shared reference, prefer removing the outer `&` over suggesting + // `&*reference`. Keep the reborrow for `&mut T` and smart pointers. + if is_under_ref.is_some() + && steps == 1 + && matches!(base_ty.kind(), ty::Ref(_, _, hir::Mutability::Not)) + && !expr.span.from_expansion() + && self.suggest_remove_reference(obligation, err, real_trait_pred) + { + return true; + } let derefs = "*".repeat(steps); let msg = "consider dereferencing here"; diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 9a643b538d93f..815acb11b9955 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -51,14 +51,19 @@ use crate::fold::TypeSuperFoldable; use crate::inherent::*; use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; use crate::{ - AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, DebruijnIndex, InferCtxtLike, - Interner, IsRigid, OutlivesClause, Region, RegionKind, TyKind, TypeFoldable, TypeFolder, - TypingMode, UniverseIndex, Variance, max_universe, set_aliases_to_non_rigid, + AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, ClauseKind, DebruijnIndex, + InferCtxtLike, Interner, IsRigid, OutlivesClause, Region, RegionKind, TyKind, TypeFoldable, + TypeFolder, TypingMode, UniverseIndex, Variance, elaborate, max_universe, + set_aliases_to_non_rigid, }; #[derive_where(Clone, Debug; I: Interner)] pub struct Assumptions { pub type_outlives: Vec>>, + /// Known `'a: 'b` assumptions, stored as an edge from the outliving region to the + /// outlived one, i.e. an edge `('a, 'b)` means `'a: 'b`. Constructors expect a relation + /// with this direction, see [`regions_outlived_by`] and [`regions_outliving`] for how it + /// is consumed. pub region_outlives: TransitiveRelation>, pub inverse_region_outlives: TransitiveRelation>, } @@ -72,7 +77,66 @@ impl Assumptions { } } + /// Builds assumptions from `clauses`, elaborating them and keeping the outlives ones. + /// + /// Callers hand us their clauses straight from the environment, so we have to elaborate + /// here to get at the implied outlives bounds: + /// - a `Ty: 'a` clause tells us that every region component of `Ty` outlives `'a`, e.g. + /// `&'b u8: 'a` implies `'b: 'a`. Without it we'd fail to prove `'b: 'a` when leaving + /// the binder these assumptions belong to. + /// - it also gives us the components as type outlives, e.g. `Vec: 'a` implies `T: 'a`, + /// which we need for placeholder and alias outlives. + /// - trait clauses imply their supertraits, so `T: Bound<'a>` where `trait Bound<'c>: 'c` + /// gives us `T: 'a`. This is why we take clauses rather than just the outlives ones: + /// filtering down to outlives before elaborating would throw those away. + /// + /// Only the clauses whose max universe is exactly `universe` are kept, which is what the + /// solver wants when computing the assumptions of a single binder. This happens after + /// elaboration on purpose, so a clause whose regions live in more than one universe still + /// contributes its implied bounds to each of them: `(&'b u8, &'c u8): 'a` gives us + /// `'c: 'a` in `'c`s universe even though the clause itself is in `'b`s. + /// + /// Use [`Assumptions::new_unelaborated`] when the caller needs the assumptions to be + /// exactly the clauses it passed in. pub fn new( + infcx: &impl InferCtxtLike, + clauses: impl IntoIterator, + region_outlives: TransitiveRelation>, + universe: UniverseIndex, + ) -> Self { + let mut type_outlives = vec![]; + let mut region_outlives_builder = TransitiveRelationBuilder::default(); + for (r1, r2) in region_outlives.base_edges() { + region_outlives_builder.add(r1, r2); + } + + let clauses = elaborate::elaborate(infcx.cx(), clauses) + .filter(|clause| max_universe(infcx, *clause) == universe); + for clause in clauses { + match clause.kind().skip_binder() { + // The type outlives assumptions are kept around as they are required for + // proving placeholder and alias outlives. + ClauseKind::TypeOutlives(_) => { + type_outlives.push(clause.as_type_outlives_clause().unwrap()); + } + ClauseKind::RegionOutlives(OutlivesClause(r1, r2)) => { + // `elaborate` drops the components which are bound inside of the type and + // bails on `for<'a> Ty: 'a`, so both regions here are free even though the + // clause itself may still be under a binder. + debug_assert!(!r1.is_bound() && !r2.is_bound()); + region_outlives_builder.add(r1, r2); + } + // Anything else can't be used as an outlives assumption. + _ => (), + } + } + + Self::new_unelaborated(type_outlives, region_outlives_builder.freeze()) + } + + /// Builds assumptions from exactly the given clauses, see [`Assumptions::new`] for when + /// the clauses should get elaborated instead. + pub fn new_unelaborated( type_outlives: Vec>>, region_outlives: TransitiveRelation>, ) -> Self { diff --git a/library/std/src/thread/current.rs b/library/std/src/thread/current.rs index 508e35cefe88f..3512f04868303 100644 --- a/library/std/src/thread/current.rs +++ b/library/std/src/thread/current.rs @@ -246,7 +246,8 @@ pub(crate) fn current_or_unnamed() -> Thread { (*current).clone() } } else if current == DESTROYED { - Thread::new(id::get_or_init(), None) + let id = id::get_or_init(); + Thread::new_current(id) } else { init_current(current) } @@ -291,7 +292,7 @@ fn init_current(current: *mut ()) -> Thread { CURRENT.set(BUSY); // If the thread ID was initialized already, use it. let id = id::get_or_init(); - let thread = Thread::new(id, None); + let thread = Thread::new_current(id); // Make sure that `crate::rt::thread_cleanup` will be run, which will // call `drop_current`. diff --git a/library/std/src/thread/lifecycle.rs b/library/std/src/thread/lifecycle.rs index 11ab2190c5444..0dec359ccaec6 100644 --- a/library/std/src/thread/lifecycle.rs +++ b/library/std/src/thread/lifecycle.rs @@ -135,6 +135,10 @@ impl ThreadInit { rtabort!("current thread handle already set during thread spawn"); } + // The handle was created by the spawning thread, so only now that we are + // running can the OS id be filled in. + self.handle.set_os_id_to_current(); + if let Some(name) = self.handle.cname() { imp::set_name(name); } diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs index e88ca92218dc8..252524a4cfed7 100644 --- a/library/std/src/thread/tests.rs +++ b/library/std/src/thread/tests.rs @@ -365,6 +365,19 @@ fn test_thread_os_id_not_equal() { assert!(current_id != spawned_id); } +#[test] +fn test_thread_os_id_matches_current() { + assert_eq!(thread::current().os_id(), crate::sys::thread::current_os_id()); +} + +#[test] +fn test_thread_os_id_of_spawned_thread() { + let spawned = thread::spawn(|| thread::current().os_id()); + let handle = spawned.thread().clone(); + let spawned_id = spawned.join().unwrap(); + assert_eq!(handle.os_id(), spawned_id); +} + #[test] fn test_scoped_threads_drop_result_before_join() { let actually_finished = &AtomicBool::new(false); diff --git a/library/std/src/thread/thread.rs b/library/std/src/thread/thread.rs index 7c9c91c3b0c78..d70c244c65d90 100644 --- a/library/std/src/thread/thread.rs +++ b/library/std/src/thread/thread.rs @@ -4,8 +4,9 @@ use crate::alloc::System; use crate::ffi::CStr; use crate::fmt; use crate::pin::Pin; -use crate::sync::Arc; +use crate::sync::{Arc, OnceLock}; use crate::sys::sync::Parker; +use crate::sys::thread as imp; use crate::time::Duration; // This module ensures private fields are kept private, which is necessary to enforce the safety requirements. @@ -49,6 +50,7 @@ use thread_name_string::ThreadNameString; struct Inner { name: Option, id: ThreadId, + os_id: OnceLock, parker: Parker, } @@ -103,6 +105,7 @@ impl Thread { let ptr = Arc::get_mut_unchecked(&mut arc).as_mut_ptr(); (&raw mut (*ptr).name).write(name); (&raw mut (*ptr).id).write(id); + (&raw mut (*ptr).os_id).write(OnceLock::new()); Parker::new_in_place(&raw mut (*ptr).parker); Pin::new_unchecked(arc.assume_init()) }; @@ -110,6 +113,35 @@ impl Thread { Thread { inner } } + /// Creates a handle for the calling thread, recording its OS id. + /// + /// `id` must be the `ThreadId` of the calling thread. + /// + /// Takes no name because passing one into `Thread::new` allocates with the + /// global allocator, which `thread::current` is documented never to use. + pub(crate) fn new_current(id: ThreadId) -> Thread { + let thread = Thread::new(id, None); + thread.set_os_id_to_current(); + thread + } + + /// Records the calling thread's OS id, as reported by + /// `imp::current_os_id`, in this handle. + /// + /// May only be called from the thread to which this handle belongs. A + /// spawned thread does this itself once it starts running, since its handle + /// already exists by then. + /// + /// `imp::current_os_id` must not allocate with the global allocator or call + /// `thread::current`. + pub(crate) fn set_os_id_to_current(&self) { + if let Some(os_id) = imp::current_os_id() { + if self.inner.os_id.set(os_id).is_err() { + rtabort!("thread OS id already set"); + } + } + } + /// Like the public [`park`], but callable on any handle. This is used to /// allow parking in TLS destructors. /// @@ -204,6 +236,40 @@ impl Thread { self.inner.id } + /// Gets the id the operating system gave this thread, if it has one that can + /// be read. + /// + /// This is the id that shows up in tools like `ps` and `top`, debuggers and + /// crash logs, unlike [`ThreadId`], which has no guaranteed relationship to + /// it. On a platform with no OS-visible thread id, such as SGX, the value + /// may be some other per-thread value (there, the thread's address), which + /// such tools will not recognize. `None` means no id could be recorded: the + /// thread has not started running yet, or the platform has no way to read + /// one. + /// + /// The operating system may reuse the id of a thread that has exited, and a + /// `Thread` handle can outlive the thread it refers to. After a `fork`, the + /// id recorded in the child process still refers to the parent's thread; it + /// is not re-read. Use the id only where a reused or stale id is harmless, + /// such as logging. + /// + /// # Examples + /// + /// ``` + /// #![feature(thread_os_id)] + /// use std::thread; + /// + /// let spawned = thread::spawn(|| thread::current().os_id()).join().unwrap(); + /// if spawned.is_some() { + /// assert_ne!(spawned, thread::current().os_id()); + /// } + /// ``` + #[unstable(feature = "thread_os_id", issue = "160215")] + #[must_use] + pub fn os_id(&self) -> Option { + self.inner.os_id.get().copied() + } + /// Gets the thread's name. /// /// For more information about named threads, see diff --git a/src/bootstrap/src/core/build_steps/perf.rs b/src/bootstrap/src/core/build_steps/perf.rs index cc81d9243fe26..930f114f1a70e 100644 --- a/src/bootstrap/src/core/build_steps/perf.rs +++ b/src/bootstrap/src/core/build_steps/perf.rs @@ -95,6 +95,7 @@ pub enum Profile { Check, Debug, Doc, + DocJson, Opt, Clippy, } @@ -105,6 +106,7 @@ impl Display for Profile { Profile::Check => "Check", Profile::Debug => "Debug", Profile::Doc => "Doc", + Profile::DocJson => "DocJson", Profile::Opt => "Opt", Profile::Clippy => "Clippy", }; @@ -134,7 +136,7 @@ impl Display for Scenario { } /// Performs profiling using `rustc-perf` on a built version of the compiler. -pub fn perf(builder: &Builder<'_>, args: &PerfArgs) { +pub fn perf(builder: &Builder<'_>, args: &PerfArgs, trailing_args: &[String]) { let collector = builder.ensure(RustcPerf { compiler: builder.compiler(0, builder.config.host_target), target: builder.config.host_target, @@ -197,6 +199,7 @@ Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#); cmd.arg(prepare_rustc()); apply_shared_opts(&mut cmd, opts); + cmd.args(trailing_args); cmd.run(builder); println!("You can find the results at `{}`", results_dir.display()); @@ -208,6 +211,7 @@ Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#); cmd.arg(prepare_rustc()); apply_shared_opts(&mut cmd, opts); + cmd.args(trailing_args); cmd.run(builder); } PerfCommand::Compare { base, modified } => { @@ -215,6 +219,7 @@ Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#); cmd.arg("--db").arg(&db_path); cmd.arg(base).arg(modified); + cmd.args(trailing_args); cmd.run(builder); } } diff --git a/src/bootstrap/src/core/session.rs b/src/bootstrap/src/core/session.rs index 7207f7dd033ea..4ad04df753d00 100644 --- a/src/bootstrap/src/core/session.rs +++ b/src/bootstrap/src/core/session.rs @@ -586,7 +586,11 @@ impl Session { ); } Subcommand::Perf(args) => { - return crate::core::build_steps::perf::perf(&Builder::new(self), args); + return crate::core::build_steps::perf::perf( + &Builder::new(self), + args, + &self.config.free_args, + ); } _cmd => { debug!(cmd = ?_cmd, "not a hardcoded subcommand; returning to normal handling"); diff --git a/src/etc/completions/x.fish b/src/etc/completions/x.fish index e3cc4ac39d798..58c5e4255caf6 100644 --- a/src/etc/completions/x.fish +++ b/src/etc/completions/x.fish @@ -919,7 +919,7 @@ complete -c x -n "__fish_x_using_subcommand perf; and not __fish_seen_subcommand complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l include -d 'Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed' -r complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l exclude -d 'Select the benchmarks matching a prefix in this comma-separated list that you don\'t want to run' -r complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l scenarios -d 'Select the scenarios that should be benchmarked' -r -f -a "{Full\t'',IncrFull\t'',IncrUnchanged\t'',IncrPatched\t''}" -complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',Opt\t'',Clippy\t''}" +complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',DocJson\t'',Opt\t'',Clippy\t''}" complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l config -d 'TOML configuration file for build' -r -F complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)" complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l build -d 'host target of the stage0 compiler' -r -f @@ -958,7 +958,7 @@ complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_fro complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from samply" -l include -d 'Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed' -r complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from samply" -l exclude -d 'Select the benchmarks matching a prefix in this comma-separated list that you don\'t want to run' -r complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from samply" -l scenarios -d 'Select the scenarios that should be benchmarked' -r -f -a "{Full\t'',IncrFull\t'',IncrUnchanged\t'',IncrPatched\t''}" -complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from samply" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',Opt\t'',Clippy\t''}" +complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from samply" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',DocJson\t'',Opt\t'',Clippy\t''}" complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from samply" -l config -d 'TOML configuration file for build' -r -F complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from samply" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)" complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from samply" -l build -d 'host target of the stage0 compiler' -r -f @@ -997,7 +997,7 @@ complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_fro complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l include -d 'Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed' -r complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l exclude -d 'Select the benchmarks matching a prefix in this comma-separated list that you don\'t want to run' -r complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l scenarios -d 'Select the scenarios that should be benchmarked' -r -f -a "{Full\t'',IncrFull\t'',IncrUnchanged\t'',IncrPatched\t''}" -complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',Opt\t'',Clippy\t''}" +complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',DocJson\t'',Opt\t'',Clippy\t''}" complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l config -d 'TOML configuration file for build' -r -F complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)" complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l build -d 'host target of the stage0 compiler' -r -f @@ -1036,7 +1036,7 @@ complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_fro complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l include -d 'Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed' -r complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l exclude -d 'Select the benchmarks matching a prefix in this comma-separated list that you don\'t want to run' -r complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l scenarios -d 'Select the scenarios that should be benchmarked' -r -f -a "{Full\t'',IncrFull\t'',IncrUnchanged\t'',IncrPatched\t''}" -complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',Opt\t'',Clippy\t''}" +complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',DocJson\t'',Opt\t'',Clippy\t''}" complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l config -d 'TOML configuration file for build' -r -F complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)" complete -c x -n "__fish_x_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l build -d 'host target of the stage0 compiler' -r -f diff --git a/src/etc/completions/x.py.fish b/src/etc/completions/x.py.fish index 2a2aad96c7cd7..292408fbe49f6 100644 --- a/src/etc/completions/x.py.fish +++ b/src/etc/completions/x.py.fish @@ -919,7 +919,7 @@ complete -c x.py -n "__fish_x.py_using_subcommand perf; and not __fish_seen_subc complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l include -d 'Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed' -r complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l exclude -d 'Select the benchmarks matching a prefix in this comma-separated list that you don\'t want to run' -r complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l scenarios -d 'Select the scenarios that should be benchmarked' -r -f -a "{Full\t'',IncrFull\t'',IncrUnchanged\t'',IncrPatched\t''}" -complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',Opt\t'',Clippy\t''}" +complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',DocJson\t'',Opt\t'',Clippy\t''}" complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)" complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from eprintln" -l build -d 'host target of the stage0 compiler' -r -f @@ -958,7 +958,7 @@ complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcomma complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from samply" -l include -d 'Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed' -r complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from samply" -l exclude -d 'Select the benchmarks matching a prefix in this comma-separated list that you don\'t want to run' -r complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from samply" -l scenarios -d 'Select the scenarios that should be benchmarked' -r -f -a "{Full\t'',IncrFull\t'',IncrUnchanged\t'',IncrPatched\t''}" -complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from samply" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',Opt\t'',Clippy\t''}" +complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from samply" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',DocJson\t'',Opt\t'',Clippy\t''}" complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from samply" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from samply" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)" complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from samply" -l build -d 'host target of the stage0 compiler' -r -f @@ -997,7 +997,7 @@ complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcomma complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l include -d 'Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed' -r complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l exclude -d 'Select the benchmarks matching a prefix in this comma-separated list that you don\'t want to run' -r complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l scenarios -d 'Select the scenarios that should be benchmarked' -r -f -a "{Full\t'',IncrFull\t'',IncrUnchanged\t'',IncrPatched\t''}" -complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',Opt\t'',Clippy\t''}" +complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',DocJson\t'',Opt\t'',Clippy\t''}" complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)" complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from cachegrind" -l build -d 'host target of the stage0 compiler' -r -f @@ -1036,7 +1036,7 @@ complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcomma complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l include -d 'Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed' -r complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l exclude -d 'Select the benchmarks matching a prefix in this comma-separated list that you don\'t want to run' -r complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l scenarios -d 'Select the scenarios that should be benchmarked' -r -f -a "{Full\t'',IncrFull\t'',IncrUnchanged\t'',IncrPatched\t''}" -complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',Opt\t'',Clippy\t''}" +complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l profiles -d 'Select the profiles that should be benchmarked' -r -f -a "{Check\t'',Debug\t'',Doc\t'',DocJson\t'',Opt\t'',Clippy\t''}" complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)" complete -c x.py -n "__fish_x.py_using_subcommand perf; and __fish_seen_subcommand_from benchmark" -l build -d 'host target of the stage0 compiler' -r -f diff --git a/src/etc/completions/x.py.sh b/src/etc/completions/x.py.sh index 1266f85addd8d..0c449988b0461 100644 --- a/src/etc/completions/x.py.sh +++ b/src/etc/completions/x.py.sh @@ -3113,7 +3113,7 @@ _x.py() { return 0 ;; --profiles) - COMPREPLY=($(compgen -W "Check Debug Doc Opt Clippy" -- "${cur}")) + COMPREPLY=($(compgen -W "Check Debug Doc DocJson Opt Clippy" -- "${cur}")) return 0 ;; --config) @@ -3311,7 +3311,7 @@ _x.py() { return 0 ;; --profiles) - COMPREPLY=($(compgen -W "Check Debug Doc Opt Clippy" -- "${cur}")) + COMPREPLY=($(compgen -W "Check Debug Doc DocJson Opt Clippy" -- "${cur}")) return 0 ;; --config) @@ -3695,7 +3695,7 @@ _x.py() { return 0 ;; --profiles) - COMPREPLY=($(compgen -W "Check Debug Doc Opt Clippy" -- "${cur}")) + COMPREPLY=($(compgen -W "Check Debug Doc DocJson Opt Clippy" -- "${cur}")) return 0 ;; --config) @@ -3893,7 +3893,7 @@ _x.py() { return 0 ;; --profiles) - COMPREPLY=($(compgen -W "Check Debug Doc Opt Clippy" -- "${cur}")) + COMPREPLY=($(compgen -W "Check Debug Doc DocJson Opt Clippy" -- "${cur}")) return 0 ;; --config) diff --git a/src/etc/completions/x.py.zsh b/src/etc/completions/x.py.zsh index bd199d2ac0f47..9ee5843a5abd7 100644 --- a/src/etc/completions/x.py.zsh +++ b/src/etc/completions/x.py.zsh @@ -1122,7 +1122,7 @@ _arguments "${_arguments_options[@]}" : \ '*--include=[Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed]:INCLUDE:_default' \ '*--exclude=[Select the benchmarks matching a prefix in this comma-separated list that you don'\''t want to run]:EXCLUDE:_default' \ '*--scenarios=[Select the scenarios that should be benchmarked]:SCENARIOS:(Full IncrFull IncrUnchanged IncrPatched)' \ -'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc Opt Clippy)' \ +'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc DocJson Opt Clippy)' \ '--config=[TOML configuration file for build]:FILE:_files' \ '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \ '--build=[host target of the stage0 compiler]:BUILD:' \ @@ -1171,7 +1171,7 @@ _arguments "${_arguments_options[@]}" : \ '*--include=[Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed]:INCLUDE:_default' \ '*--exclude=[Select the benchmarks matching a prefix in this comma-separated list that you don'\''t want to run]:EXCLUDE:_default' \ '*--scenarios=[Select the scenarios that should be benchmarked]:SCENARIOS:(Full IncrFull IncrUnchanged IncrPatched)' \ -'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc Opt Clippy)' \ +'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc DocJson Opt Clippy)' \ '--config=[TOML configuration file for build]:FILE:_files' \ '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \ '--build=[host target of the stage0 compiler]:BUILD:' \ @@ -1220,7 +1220,7 @@ _arguments "${_arguments_options[@]}" : \ '*--include=[Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed]:INCLUDE:_default' \ '*--exclude=[Select the benchmarks matching a prefix in this comma-separated list that you don'\''t want to run]:EXCLUDE:_default' \ '*--scenarios=[Select the scenarios that should be benchmarked]:SCENARIOS:(Full IncrFull IncrUnchanged IncrPatched)' \ -'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc Opt Clippy)' \ +'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc DocJson Opt Clippy)' \ '--config=[TOML configuration file for build]:FILE:_files' \ '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \ '--build=[host target of the stage0 compiler]:BUILD:' \ @@ -1269,7 +1269,7 @@ _arguments "${_arguments_options[@]}" : \ '*--include=[Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed]:INCLUDE:_default' \ '*--exclude=[Select the benchmarks matching a prefix in this comma-separated list that you don'\''t want to run]:EXCLUDE:_default' \ '*--scenarios=[Select the scenarios that should be benchmarked]:SCENARIOS:(Full IncrFull IncrUnchanged IncrPatched)' \ -'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc Opt Clippy)' \ +'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc DocJson Opt Clippy)' \ '--config=[TOML configuration file for build]:FILE:_files' \ '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \ '--build=[host target of the stage0 compiler]:BUILD:' \ diff --git a/src/etc/completions/x.sh b/src/etc/completions/x.sh index 644c656514f6c..f8897cc5f5ec8 100644 --- a/src/etc/completions/x.sh +++ b/src/etc/completions/x.sh @@ -3113,7 +3113,7 @@ _x() { return 0 ;; --profiles) - COMPREPLY=($(compgen -W "Check Debug Doc Opt Clippy" -- "${cur}")) + COMPREPLY=($(compgen -W "Check Debug Doc DocJson Opt Clippy" -- "${cur}")) return 0 ;; --config) @@ -3311,7 +3311,7 @@ _x() { return 0 ;; --profiles) - COMPREPLY=($(compgen -W "Check Debug Doc Opt Clippy" -- "${cur}")) + COMPREPLY=($(compgen -W "Check Debug Doc DocJson Opt Clippy" -- "${cur}")) return 0 ;; --config) @@ -3695,7 +3695,7 @@ _x() { return 0 ;; --profiles) - COMPREPLY=($(compgen -W "Check Debug Doc Opt Clippy" -- "${cur}")) + COMPREPLY=($(compgen -W "Check Debug Doc DocJson Opt Clippy" -- "${cur}")) return 0 ;; --config) @@ -3893,7 +3893,7 @@ _x() { return 0 ;; --profiles) - COMPREPLY=($(compgen -W "Check Debug Doc Opt Clippy" -- "${cur}")) + COMPREPLY=($(compgen -W "Check Debug Doc DocJson Opt Clippy" -- "${cur}")) return 0 ;; --config) diff --git a/src/etc/completions/x.zsh b/src/etc/completions/x.zsh index 29f05ab2fb7cd..f61942a11da4a 100644 --- a/src/etc/completions/x.zsh +++ b/src/etc/completions/x.zsh @@ -1122,7 +1122,7 @@ _arguments "${_arguments_options[@]}" : \ '*--include=[Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed]:INCLUDE:_default' \ '*--exclude=[Select the benchmarks matching a prefix in this comma-separated list that you don'\''t want to run]:EXCLUDE:_default' \ '*--scenarios=[Select the scenarios that should be benchmarked]:SCENARIOS:(Full IncrFull IncrUnchanged IncrPatched)' \ -'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc Opt Clippy)' \ +'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc DocJson Opt Clippy)' \ '--config=[TOML configuration file for build]:FILE:_files' \ '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \ '--build=[host target of the stage0 compiler]:BUILD:' \ @@ -1171,7 +1171,7 @@ _arguments "${_arguments_options[@]}" : \ '*--include=[Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed]:INCLUDE:_default' \ '*--exclude=[Select the benchmarks matching a prefix in this comma-separated list that you don'\''t want to run]:EXCLUDE:_default' \ '*--scenarios=[Select the scenarios that should be benchmarked]:SCENARIOS:(Full IncrFull IncrUnchanged IncrPatched)' \ -'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc Opt Clippy)' \ +'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc DocJson Opt Clippy)' \ '--config=[TOML configuration file for build]:FILE:_files' \ '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \ '--build=[host target of the stage0 compiler]:BUILD:' \ @@ -1220,7 +1220,7 @@ _arguments "${_arguments_options[@]}" : \ '*--include=[Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed]:INCLUDE:_default' \ '*--exclude=[Select the benchmarks matching a prefix in this comma-separated list that you don'\''t want to run]:EXCLUDE:_default' \ '*--scenarios=[Select the scenarios that should be benchmarked]:SCENARIOS:(Full IncrFull IncrUnchanged IncrPatched)' \ -'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc Opt Clippy)' \ +'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc DocJson Opt Clippy)' \ '--config=[TOML configuration file for build]:FILE:_files' \ '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \ '--build=[host target of the stage0 compiler]:BUILD:' \ @@ -1269,7 +1269,7 @@ _arguments "${_arguments_options[@]}" : \ '*--include=[Select the benchmarks that you want to run (separated by commas). If unspecified, all benchmarks will be executed]:INCLUDE:_default' \ '*--exclude=[Select the benchmarks matching a prefix in this comma-separated list that you don'\''t want to run]:EXCLUDE:_default' \ '*--scenarios=[Select the scenarios that should be benchmarked]:SCENARIOS:(Full IncrFull IncrUnchanged IncrPatched)' \ -'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc Opt Clippy)' \ +'*--profiles=[Select the profiles that should be benchmarked]:PROFILES:(Check Debug Doc DocJson Opt Clippy)' \ '--config=[TOML configuration file for build]:FILE:_files' \ '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \ '--build=[host target of the stage0 compiler]:BUILD:' \ diff --git a/tests/rustdoc-ui/expect-item-after-attribute.rs b/tests/rustdoc-ui/doctest/expect-item-after-attribute.rs similarity index 100% rename from tests/rustdoc-ui/expect-item-after-attribute.rs rename to tests/rustdoc-ui/doctest/expect-item-after-attribute.rs diff --git a/tests/rustdoc-ui/expect-item-after-attribute.stdout b/tests/rustdoc-ui/doctest/expect-item-after-attribute.stdout similarity index 100% rename from tests/rustdoc-ui/expect-item-after-attribute.stdout rename to tests/rustdoc-ui/doctest/expect-item-after-attribute.stdout diff --git a/tests/ui/assumptions_on_binders/supertrait-implied-outlives-assumptions.rs b/tests/ui/assumptions_on_binders/supertrait-implied-outlives-assumptions.rs new file mode 100644 index 0000000000000..8c30faecc11b0 --- /dev/null +++ b/tests/ui/assumptions_on_binders/supertrait-implied-outlives-assumptions.rs @@ -0,0 +1,33 @@ +//@ check-pass +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +// A trait clause implies its supertraits, so a `&'x (): Bound<'x>` requirement is also evidence +// for `&'x (): 'static`, which elaborates to the region assumption `'x: 'static`. +// `Assumptions::new` therefore takes clauses and elaborates them itself; handing it only the +// outlives clauses would drop the trait clause before it could imply anything. +// +// The clause has to mention the binder's own `'x` to survive the `max_universe == u` filter, +// while the supertrait outlives is on `'static` so that the assumption can discharge `'x: 'a`. +// +// Keeping the requirement binder-local matters: the `for<'x> Wrap<'x>: 'a` bound is proven at the +// call site below, so failing to discharge `'x: 'a` is a `NoSolution` inside the solver rather +// than a constraint escaping to the root. Constraints reaching the root are still dropped, so a +// shape which lets `'x` escape (e.g. requiring `T: 'x` for an outer `T`) would pass either way. +// Removing the `&'x (): Bound<'x>` clause below makes this fail, as does dropping trait clauses +// before elaborating. + +trait Bound<'c>: 'static {} + +struct Wrap<'x>(&'x ()) +where + &'x (): Bound<'x>; + +fn foo<'a>(_a: &'a u32) +where + for<'x> Wrap<'x>: 'a, +{ +} + +fn main() { + foo(&10); +} diff --git a/tests/ui/assumptions_on_binders/type-outlives-assumptions.rs b/tests/ui/assumptions_on_binders/type-outlives-assumptions.rs new file mode 100644 index 0000000000000..bc3b3a7f2c250 --- /dev/null +++ b/tests/ui/assumptions_on_binders/type-outlives-assumptions.rs @@ -0,0 +1,26 @@ +//@ check-pass +//@ compile-flags: -Zassumptions-on-binders + +// Regression test for rust-lang/project-assumptions-on-binders#19, based on the `syn` failure. +// The receiver gives us an implied `I: 'b` bound and `'b: 'a` lets that satisfy the object +// lifetime. The implied type bound has to be included in the root assumptions for that to work. +trait IterTrait<'a, T: 'a>: Iterator { + fn clone_box<'b>(&'b self) -> Box + 'a> + where + 'b: 'a; +} + +impl<'a, T, I> IterTrait<'a, T> for I +where + T: 'a, + I: Iterator + Clone, +{ + fn clone_box<'b>(&'b self) -> Box + 'a> + where + 'b: 'a, + { + Box::new(self.clone()) + } +} + +fn main() {} 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(); - } diff --git a/tests/ui/suggestions/redundant-shared-reference-issue-133685.fixed b/tests/ui/suggestions/redundant-shared-reference-issue-133685.fixed new file mode 100644 index 0000000000000..2c7d21d7788cc --- /dev/null +++ b/tests/ui/suggestions/redundant-shared-reference-issue-133685.fixed @@ -0,0 +1,46 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/133685. +//! Prefer removing an extra shared borrow over reborrowing an existing shared reference. + +//@ run-rustfix + +#![allow(unused_parens)] + +fn consume<'a>(_: impl IntoIterator) {} + +trait Value {} +struct Source; +impl Value for &Source {} +fn consume_value(_: impl Value) {} + +fn main() { + let a: Vec = Vec::new(); + let ref_a = &a; + let mut b: Vec = Vec::new(); + b.extend(ref_a); + //~^ ERROR is not an iterator + + consume(ref_a); + //~^ ERROR is not an iterator + consume((ref_a)); + //~^ ERROR is not an iterator + + let slice = &a[..]; + consume(slice); + //~^ ERROR is not an iterator + + // These still need a dereference: neither operand has the required shared-reference type. + let boxed = Box::new(a.clone()); + consume(&*boxed); + //~^ ERROR is not an iterator + + let mut values = a.clone(); + let mut_ref = &mut values; + consume(&*mut_ref); + //~^ ERROR is not an iterator + + // Also cover a direct trait bound without the Iterator-to-IntoIterator blanket impl. + let source = Source; + let shared = &source; + consume_value(shared); + //~^ ERROR the trait bound +} diff --git a/tests/ui/suggestions/redundant-shared-reference-issue-133685.rs b/tests/ui/suggestions/redundant-shared-reference-issue-133685.rs new file mode 100644 index 0000000000000..38f699a8dfe85 --- /dev/null +++ b/tests/ui/suggestions/redundant-shared-reference-issue-133685.rs @@ -0,0 +1,46 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/133685. +//! Prefer removing an extra shared borrow over reborrowing an existing shared reference. + +//@ run-rustfix + +#![allow(unused_parens)] + +fn consume<'a>(_: impl IntoIterator) {} + +trait Value {} +struct Source; +impl Value for &Source {} +fn consume_value(_: impl Value) {} + +fn main() { + let a: Vec = Vec::new(); + let ref_a = &a; + let mut b: Vec = Vec::new(); + b.extend(&ref_a); + //~^ ERROR is not an iterator + + consume(&ref_a); + //~^ ERROR is not an iterator + consume((&ref_a)); + //~^ ERROR is not an iterator + + let slice = &a[..]; + consume(&slice); + //~^ ERROR is not an iterator + + // These still need a dereference: neither operand has the required shared-reference type. + let boxed = Box::new(a.clone()); + consume(&boxed); + //~^ ERROR is not an iterator + + let mut values = a.clone(); + let mut_ref = &mut values; + consume(&mut_ref); + //~^ ERROR is not an iterator + + // Also cover a direct trait bound without the Iterator-to-IntoIterator blanket impl. + let source = Source; + let shared = &source; + consume_value(&shared); + //~^ ERROR the trait bound +} diff --git a/tests/ui/suggestions/redundant-shared-reference-issue-133685.stderr b/tests/ui/suggestions/redundant-shared-reference-issue-133685.stderr new file mode 100644 index 0000000000000..e1115c2c91b3d --- /dev/null +++ b/tests/ui/suggestions/redundant-shared-reference-issue-133685.stderr @@ -0,0 +1,143 @@ +error[E0277]: `&&Vec` is not an iterator + --> $DIR/redundant-shared-reference-issue-133685.rs:19:14 + | +LL | b.extend(&ref_a); + | ------ ^^^^^^ `&&Vec` is not an iterator + | | + | required by a bound introduced by this call + | + = help: the trait `Iterator` is not implemented for `&&Vec` + = note: required for `&&Vec` to implement `IntoIterator` +note: required by a bound in `extend` + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL +help: consider removing the leading `&`-reference + | +LL - b.extend(&ref_a); +LL + b.extend(ref_a); + | + +error[E0277]: `&&Vec` is not an iterator + --> $DIR/redundant-shared-reference-issue-133685.rs:22:13 + | +LL | consume(&ref_a); + | ------- ^^^^^^ `&&Vec` is not an iterator + | | + | required by a bound introduced by this call + | + = help: the trait `Iterator` is not implemented for `&&Vec` + = note: required for `&&Vec` to implement `IntoIterator` +note: required by a bound in `consume` + --> $DIR/redundant-shared-reference-issue-133685.rs:8:24 + | +LL | fn consume<'a>(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `consume` +help: consider removing the leading `&`-reference + | +LL - consume(&ref_a); +LL + consume(ref_a); + | + +error[E0277]: `&&Vec` is not an iterator + --> $DIR/redundant-shared-reference-issue-133685.rs:24:13 + | +LL | consume((&ref_a)); + | ------- ^^^^^^^^ `&&Vec` is not an iterator + | | + | required by a bound introduced by this call + | + = help: the trait `Iterator` is not implemented for `&&Vec` + = note: required for `&&Vec` to implement `IntoIterator` +note: required by a bound in `consume` + --> $DIR/redundant-shared-reference-issue-133685.rs:8:24 + | +LL | fn consume<'a>(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `consume` +help: consider removing the leading `&`-reference + | +LL - consume((&ref_a)); +LL + consume((ref_a)); + | + +error[E0277]: `&&[i32]` is not an iterator + --> $DIR/redundant-shared-reference-issue-133685.rs:28:13 + | +LL | consume(&slice); + | ------- ^^^^^^ `&&[i32]` is not an iterator + | | + | required by a bound introduced by this call + | + = help: the trait `Iterator` is not implemented for `&&[i32]` + = note: required for `&&[i32]` to implement `IntoIterator` +note: required by a bound in `consume` + --> $DIR/redundant-shared-reference-issue-133685.rs:8:24 + | +LL | fn consume<'a>(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `consume` +help: consider removing the leading `&`-reference + | +LL - consume(&slice); +LL + consume(slice); + | + +error[E0277]: `&Box>` is not an iterator + --> $DIR/redundant-shared-reference-issue-133685.rs:33:13 + | +LL | consume(&boxed); + | ------- ^^^^^^ `&Box>` is not an iterator + | | + | required by a bound introduced by this call + | + = help: the trait `Iterator` is not implemented for `&Box>` + = note: required for `&Box>` to implement `IntoIterator` +note: required by a bound in `consume` + --> $DIR/redundant-shared-reference-issue-133685.rs:8:24 + | +LL | fn consume<'a>(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `consume` +help: consider dereferencing here + | +LL | consume(&*boxed); + | + + +error[E0277]: `&&mut Vec` is not an iterator + --> $DIR/redundant-shared-reference-issue-133685.rs:38:13 + | +LL | consume(&mut_ref); + | ------- ^^^^^^^^ `&&mut Vec` is not an iterator + | | + | required by a bound introduced by this call + | + = help: the trait `Iterator` is not implemented for `&&mut Vec` + = note: required for `&&mut Vec` to implement `IntoIterator` +note: required by a bound in `consume` + --> $DIR/redundant-shared-reference-issue-133685.rs:8:24 + | +LL | fn consume<'a>(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `consume` +help: consider dereferencing here + | +LL | consume(&*mut_ref); + | + + +error[E0277]: the trait bound `&&Source: Value` is not satisfied + --> $DIR/redundant-shared-reference-issue-133685.rs:44:19 + | +LL | consume_value(&shared); + | ------------- ^^^^^^^ the trait `Value` is not implemented for `&&Source` + | | + | required by a bound introduced by this call + | +note: required by a bound in `consume_value` + --> $DIR/redundant-shared-reference-issue-133685.rs:13:26 + | +LL | fn consume_value(_: impl Value) {} + | ^^^^^ required by this bound in `consume_value` +help: consider removing the leading `&`-reference + | +LL - consume_value(&shared); +LL + consume_value(shared); + | + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/traits/alias/self-in-const-generics.rs b/tests/ui/traits/alias/self-in-const-generics.rs index a7d0ac9cbb4c2..e347e26893741 100644 --- a/tests/ui/traits/alias/self-in-const-generics.rs +++ b/tests/ui/traits/alias/self-in-const-generics.rs @@ -1,3 +1,5 @@ +//@ ignore-parallel-frontend triage https://github.com/rust-lang/rust/issues/162316 + #![allow(incomplete_features)] #![feature(generic_const_exprs)] #![feature(trait_alias)] diff --git a/tests/ui/traits/alias/self-in-const-generics.stderr b/tests/ui/traits/alias/self-in-const-generics.stderr index ea201a2dd977c..7fe45ff0c51b9 100644 --- a/tests/ui/traits/alias/self-in-const-generics.stderr +++ b/tests/ui/traits/alias/self-in-const-generics.stderr @@ -1,12 +1,12 @@ error[E0038]: the trait alias `BB` is not dyn compatible - --> $DIR/self-in-const-generics.rs:9:16 + --> $DIR/self-in-const-generics.rs:11:16 | LL | fn foo(x: &dyn BB) {} | ^^ `BB` is not dyn compatible | note: for a trait to be dyn compatible it needs to allow building a vtable for more information, visit - --> $DIR/self-in-const-generics.rs:7:12 + --> $DIR/self-in-const-generics.rs:9:12 | LL | trait BB = Bar<{ 2 + 1 }>; | -- ^^^^^^^^^^^^^^ ...because it uses `Self` as a type parameter