diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 71777e0117ab8..1d5448a01f1e6 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1621,6 +1621,7 @@ impl Expr { | ExprKind::While(..) | ExprKind::Yield(YieldKind::Postfix(..)) | ExprKind::DirectConstArg(..) + | ExprKind::Rescope(..) | ExprKind::Err(_) | ExprKind::Dummy => prefix_attrs_precedence(&self.attrs), } @@ -1920,6 +1921,9 @@ pub enum ExprKind { /// An mGCA `direct_const_arg!()` expression. DirectConstArg(Box), + /// `scope!('l => e)` or `extend!('l => e)`. + Rescope(RescopeKind, Label, Box), + /// Placeholder for an expression that wasn't syntactically well formed in some way. Err(ErrorGuaranteed), @@ -1968,6 +1972,13 @@ pub enum UnsafeBinderCastKind { Unwrap, } +/// Differentiates between the re-scoping constructs `scope!` and `extend!` +#[derive(Clone, Copy, Encodable, Decodable, Debug, PartialEq, Eq, StableHash, Walkable)] +pub enum RescopeKind { + Scope, + Extend, +} + /// The explicit `Self` type in a "qualified path". /// /// The actual path, including the trait and the associated item, is stored diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index e799f73ff544f..9a494b3f9cd5e 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -159,6 +159,7 @@ pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool { | Yield(..) | UnsafeBinderCast(..) | DirectConstArg(..) + | Rescope(..) | Err(..) | Dummy => return false, } @@ -245,6 +246,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option> { | Yeet(None) | UnsafeBinderCast(..) | DirectConstArg(..) + | Rescope(..) | Err(_) | Dummy => { break None; diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index a768935f38fc3..09864ff94964a 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -467,6 +467,7 @@ macro_rules! common_visitor_and_walkers { RangeEnd, RangeSyntax, Recovered, + RescopeKind, RestrictionKind, Safety, StaticItem, @@ -1074,6 +1075,8 @@ macro_rules! common_visitor_and_walkers { visit_visitable!($($mut)? vis, kind, expr, ty), ExprKind::DirectConstArg(expr) => visit_visitable!($($mut)? vis, expr), + ExprKind::Rescope(kind, label, expr) => + visit_visitable!($($mut)? vis, kind, label, expr), ExprKind::Err(_guar) => {} ExprKind::Dummy => {} } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 06673379a5e9f..71bb9aadd99e5 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -487,6 +487,17 @@ impl<'hir> LoweringContext<'_, 'hir> { }), ), + ExprKind::Rescope(kind, label, expr) => { + if let Some(target_id) = self.label_target_id(e.id) { + let target = hir::RescopeTarget { label: *label, target_id }; + hir::ExprKind::Rescope(*kind, target, self.lower_expr(expr)) + } else { + // FIXME(super_let): we could recover here to at least do type-checking + let guar = self.dcx().span_err(label.ident.span, "label not found"); + hir::ExprKind::Err(guar) + } + } + ExprKind::Dummy => { span_bug!(e.span, "lowered ExprKind::Dummy") } @@ -1549,17 +1560,15 @@ impl<'hir> LoweringContext<'_, 'hir> { Some(Label { ident: self.lower_ident(label.ident) }) } + fn label_target_id(&mut self, source: NodeId) -> Option { + let target_id = self.owner.get_label_res(source)?; + let local_id = self.ident_and_label_to_local_id[&target_id]; + Some(HirId { owner: self.current_hir_id_owner, local_id }) + } + fn lower_loop_destination(&mut self, destination: Option<(NodeId, Label)>) -> hir::Destination { let target_id = match destination { - Some((id, _)) => { - if let Some(loop_id) = self.owner.get_label_res(id) { - let local_id = self.ident_and_label_to_local_id[&loop_id]; - let loop_hir_id = HirId { owner: self.current_hir_id_owner, local_id }; - Ok(loop_hir_id) - } else { - Err(hir::LoopIdError::UnresolvedLabel) - } - } + Some((id, _)) => self.label_target_id(id).ok_or(hir::LoopIdError::UnresolvedLabel), None => { self.loop_scope.map(|id| Ok(id)).unwrap_or(Err(hir::LoopIdError::OutsideLoopScope)) } diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 238e349ab5acf..63f9eab47a8dc 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -873,6 +873,24 @@ impl<'a> State<'a> { self.end(ib); self.pclose(); } + ast::ExprKind::Rescope(kind, label, expr) => { + let mac_str = match kind { + ast::RescopeKind::Scope => "core::scope!", + ast::RescopeKind::Extend => "core::extend!", + }; + self.word(mac_str); + self.popen(); + let ib = self.ibox(0); + + self.print_ident(label.ident); + self.nbsp(); + self.word("=>"); + self.space(); + self.print_expr(expr, FixupContext::default()); + + self.end(ib); + self.pclose(); + } ast::ExprKind::Err(_) => { self.popen(); self.word("/*ERROR*/"); diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 1bc2bc8342559..5344972705bcf 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -324,6 +324,7 @@ impl<'cx, 'a> Context<'cx, 'a> { | ExprKind::Become(_) | ExprKind::Yield(_) | ExprKind::DirectConstArg(_) + | ExprKind::Rescope(_, _, _) | ExprKind::UnsafeBinderCast(..) => {} } } diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index a465c1d95f6c8..dbaa51fe5471a 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -14,7 +14,7 @@ use rustc_ast::{ pub use rustc_ast::{ AssignOp, AssignOpKind, AttrId, AttrStyle, BinOp, BinOpKind, BindingMode, BorrowKind, BoundConstness, BoundPolarity, ByRef, CaptureBy, DelimArgs, ImplPolarity, IsAuto, - MetaItemInner, MetaItemLit, Movability, Mutability, Pinnedness, UnOp, + MetaItemInner, MetaItemLit, Movability, Mutability, Pinnedness, RescopeKind, UnOp, }; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::FxIndexSet; @@ -2617,6 +2617,7 @@ impl Expr<'_> { | ExprKind::OffsetOf(..) | ExprKind::Path(..) | ExprKind::Repeat(..) + | ExprKind::Rescope(..) | ExprKind::Struct(..) | ExprKind::Tup(_) | ExprKind::Type(..) @@ -2649,10 +2650,10 @@ impl Expr<'_> { // Type ascription inherits its place expression kind from its // operand. See: // https://github.com/rust-lang/rfcs/blob/master/text/0803-type-ascription.md#type-ascription-and-temporaries - ExprKind::Type(ref e, _) => e.is_place_expr(allow_projections_from), - - // Unsafe binder cast preserves place-ness of the sub-expression. - ExprKind::UnsafeBinderCast(_, e, _) => e.is_place_expr(allow_projections_from), + // The same applies to unsafe binder casts and re-scoping expressions. + ExprKind::Type(e, _) + | ExprKind::UnsafeBinderCast(_, e, _) + | ExprKind::Rescope(_, _, e) => e.is_place_expr(allow_projections_from), ExprKind::Unary(UnOp::Deref, _) => true, @@ -2757,7 +2758,8 @@ impl Expr<'_> { | ExprKind::Index(base, _, _) | ExprKind::AddrOf(.., base) | ExprKind::Cast(base, _) - | ExprKind::UnsafeBinderCast(_, base, _) => { + | ExprKind::UnsafeBinderCast(_, base, _) + | ExprKind::Rescope(_, _, base) => { // This isn't exactly true for `Index` and all `Unary`, but we are using this // method exclusively for diagnostics and there's a *cultural* pressure against // them being used only for its side-effects. @@ -3051,6 +3053,9 @@ pub enum ExprKind<'hir> { /// e.g. `unsafe<'a> &'a i32` <=> `&i32`. UnsafeBinderCast(UnsafeBinderCastKind, &'hir Expr<'hir>, Option<&'hir Ty<'hir>>), + /// `scope!('l => e)` or `extend!('l => e)`. + Rescope(RescopeKind, RescopeTarget, &'hir Expr<'hir>), + /// A placeholder for an expression that wasn't syntactically well formed in some way. Err(rustc_span::ErrorGuaranteed), } @@ -3223,6 +3228,12 @@ pub struct Destination { pub target_id: Result, } +#[derive(Copy, Clone, Debug, PartialEq, StableHash)] +pub struct RescopeTarget { + pub label: Label, + pub target_id: HirId, +} + /// The yield kind that caused an `ExprKind::Yield`. #[derive(Copy, Clone, Debug, StableHash)] pub enum YieldSource { diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 25a6bdea3afe2..52f8227821917 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -941,6 +941,10 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) visit_opt!(visitor, visit_ty_unambig, ty); } ExprKind::Lit(lit) => try_visit!(visitor.visit_lit(*hir_id, lit, false)), + ExprKind::Rescope(_kind, ref target, ref expr) => { + try_visit!(visitor.visit_label(&target.label)); + try_visit!(visitor.visit_expr(expr)); + } ExprKind::Err(_) => {} } V::Result::output() diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index e60318756d5c7..244c2367588ae 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -28,8 +28,30 @@ struct Context { /// Region parent of expressions, etc. parent: Option, + + /// Scope of lifetime-extended temporaries. If `None`, extendable expressions have their usual + /// temporary scopes. + extended_parent: Option, +} + +/// Determines the scopes of subexpressions' temporaries. +#[derive(Debug, Copy, Clone)] +struct NodeInfo { + /// If `true`, this node is a temporary scope; non-extended temporaries do not live past it. + /// If `false`, temporaries live past its evaluation to the enclosing temporary scope. + drop_temps: bool, + /// If `true`, borrow operators' operands and `super let` bindings in extending sub-expressions + /// are subject to [lifetime extension]. + /// If `false`, sub-expressions' temporary lifetimes will not be extended. + /// + /// [lifetime extension]: https://doc.rust-lang.org/nightly/reference/destructors.html#temporary-lifetime-extension + extending: bool, } +/// Scope of lifetime-extended temporaries. If the field is `None`, no drop is scheduled for them. +#[derive(Debug, Copy, Clone)] +struct ExtendedScope(Option); + struct ScopeResolutionVisitor<'tcx> { tcx: TyCtxt<'tcx>, @@ -38,7 +60,10 @@ struct ScopeResolutionVisitor<'tcx> { cx: Context, - extended_super_lets: FxHashMap>, + /// Tracks labeled expressions in extending positions. If the expression at `'l` is in an + /// extending context, the `e` in `extend!('l => e)` is too, using the same scope for + /// lifetime-extended temporaries. + extending_labels: FxHashMap, } /// Records the lifetime of a local variable as `cx.var_parent` @@ -56,7 +81,7 @@ fn record_var_lifetime(visitor: &mut ScopeResolutionVisitor<'_>, var_id: hir::It fn resolve_block<'tcx>( visitor: &mut ScopeResolutionVisitor<'tcx>, blk: &'tcx hir::Block<'tcx>, - terminating: bool, + node_info: NodeInfo, ) { debug!("resolve_block(blk.hir_id={:?})", blk.hir_id); @@ -87,7 +112,7 @@ fn resolve_block<'tcx>( // `other_argument()` has run and also the call to `quux(..)` // itself has returned. - visitor.enter_node_scope_with_dtor(blk.hir_id.local_id, terminating); + visitor.enter_node_scope(blk.hir_id.local_id, node_info); visitor.cx.var_parent = visitor.cx.parent; { @@ -116,7 +141,7 @@ fn resolve_block<'tcx>( // the sequence of visits agree with the order in the default // `hir::intravisit` visitor. mem::swap(&mut prev_cx, &mut visitor.cx); - resolve_block(visitor, els, true); + resolve_block(visitor, els, NodeInfo { drop_temps: true, extending: false }); // From now on, we continue normally. visitor.cx = prev_cx; } @@ -144,8 +169,8 @@ fn resolve_block<'tcx>( if let Some(tail_expr) = blk.expr { let local_id = tail_expr.hir_id.local_id; let edition = blk.span.edition(); - let terminating = edition.at_least_rust_2024(); - if !terminating + let drop_temps = edition.at_least_rust_2024(); + if !drop_temps && !visitor .tcx .skippable_lints(()) @@ -160,7 +185,7 @@ fn resolve_block<'tcx>( .backwards_incompatible_scope .insert(local_id, Scope { local_id, data: ScopeData::Node }); } - resolve_expr(visitor, tail_expr, terminating); + resolve_expr(visitor, tail_expr, NodeInfo { drop_temps, extending: true }); } } @@ -170,7 +195,7 @@ fn resolve_block<'tcx>( /// Resolve a condition from an `if` expression or match guard so that it is a terminating scope /// if it doesn't contain `let` expressions. fn resolve_cond<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, cond: &'tcx hir::Expr<'tcx>) { - let terminate = match cond.kind { + let drop_temps = match cond.kind { // Temporaries for `let` expressions must live into the success branch. hir::ExprKind::Let(_) => false, // Logical operator chains are handled in `resolve_expr`. Since logical operator chains in @@ -188,13 +213,13 @@ fn resolve_cond<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, cond: &'tcx hi // Otherwise, conditions should always drop their temporaries. _ => true, }; - resolve_expr(visitor, cond, terminate); + resolve_expr(visitor, cond, NodeInfo { drop_temps, extending: false }); } fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir::Arm<'tcx>) { let prev_cx = visitor.cx; - visitor.enter_node_scope_with_dtor(arm.hir_id.local_id, true); + visitor.enter_node_scope(arm.hir_id.local_id, NodeInfo { drop_temps: true, extending: true }); visitor.cx.var_parent = visitor.cx.parent; resolve_pat(visitor, arm.pat); @@ -206,7 +231,7 @@ fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir: visitor.cx.var_parent = visitor.cx.parent; resolve_cond(visitor, guard); } - resolve_expr(visitor, arm.body, false); + resolve_expr(visitor, arm.body, NodeInfo { drop_temps: false, extending: true }); visitor.cx = prev_cx; } @@ -242,24 +267,98 @@ fn resolve_stmt<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, stmt: &'tcx hi // regions referenced by the destructors need to survive. let prev_parent = visitor.cx.parent; - visitor.enter_node_scope_with_dtor(stmt_id, true); + let prev_extended_parent = visitor.cx.extended_parent; + visitor.enter_node_scope(stmt_id, NodeInfo { drop_temps: true, extending: false }); intravisit::walk_stmt(visitor, stmt); visitor.cx.parent = prev_parent; + visitor.cx.extended_parent = prev_extended_parent; } } +fn resolve_rescopes<'tcx>( + visitor: &mut ScopeResolutionVisitor<'tcx>, + mut expr: &'tcx hir::Expr<'tcx>, +) -> &'tcx hir::Expr<'tcx> { + while let hir::ExprKind::Rescope(kind, target, oprnd) = &expr.kind { + match kind { + // `scope!('l => oprnd)`: the parent scope of `oprnd` is the scope of the expr at `'l` + hir::RescopeKind::Scope => { + // FIXME(super_let): if we allow labels on non-blocks for convenience, we'll need to + // be careful with labled re-scoping operators + visitor.cx.parent = + Some(Scope { local_id: target.target_id.local_id, data: ScopeData::Node }); + // Don't implicitly lifetime-extend through `scope!`. + visitor.cx.extended_parent = None; + } + // `extend!('l => oprnd)`: the extended parent of `oprnd` is that of the expr at `'l` + hir::RescopeKind::Extend => { + // This sets the scope of temporaries borrowed with `&` to be the same as they would + // be at `'l`. As such, if `'l` wasn't recorded as "extending", we use the temporary + // scope enclosing it. + visitor.cx.extended_parent = + visitor.extending_labels.get(&target.target_id.local_id).copied().or_else( + || { + Some(ExtendedScope(Some( + visitor + .scope_tree + .default_temporary_scope(Scope { + local_id: target.target_id.local_id, + data: ScopeData::Node, + }) + .0, + ))) + }, + ); + } + } + expr = oprnd; + } + expr +} + #[tracing::instrument(level = "debug", skip(visitor))] fn resolve_expr<'tcx>( visitor: &mut ScopeResolutionVisitor<'tcx>, expr: &'tcx hir::Expr<'tcx>, - terminating: bool, + mut node_info: NodeInfo, ) { let prev_cx = visitor.cx; - visitor.enter_node_scope_with_dtor(expr.hir_id.local_id, terminating); + // Re-scoping operators don't have scopes of their own. We don't create `ScopeTree` nodes for + // them so we can't mistakenly take their temporary scopes instead of their operands'. + if matches!(expr.kind, hir::ExprKind::Rescope(..)) { + // `resolve_rescopes` will set `visitor.cx.extending_parent`. Keep whatever it does. + node_info.extending = true; + } + let expr = resolve_rescopes(visitor, expr); + visitor.enter_node_scope(expr.hir_id.local_id, node_info); + // Expressions matching the `E&` grammar are marked as "extending". Temporaries borrowed within + // extending expressions have their lifetimes extended. See + // + // + // E& = & E& + // | StructName { ..., f: E&, ... } + // | StructName(..., E&, ...) + // | [ ..., E&, ... ] + // | ( ..., E&, ... ) + // | {...; E&} + // | { super let ... = E&; ... } + // | if _ { ...; E& } else { ...; E& } + // | match _ { ..., _ => E&, ... } + // | E& as ... match expr.kind { + hir::ExprKind::AddrOf(_, _, subexpr) => { + // Resolve rescopes on `subexpr` first, so that `&extend!('l => value)` lifetime-extends + // `value` to the extended parent recorded at `'l`. + let subexpr = resolve_rescopes(visitor, subexpr); + if let Some(ExtendedScope(lifetime)) = visitor.cx.extended_parent { + record_subexpr_extended_temp_scopes(&mut visitor.scope_tree, subexpr, lifetime); + } + resolve_expr(visitor, subexpr, NodeInfo { drop_temps: false, extending: true }); + } + // Conditional or repeating scopes are always terminating // scopes, meaning that temporaries cannot outlive them. // This ensures fixed size stacks. @@ -304,8 +403,8 @@ fn resolve_expr<'tcx>( // should live beyond the immediate expression let terminate_rhs = !matches!(right.kind, hir::ExprKind::Let(_)); - resolve_expr(visitor, left, terminate_lhs); - resolve_expr(visitor, right, terminate_rhs); + resolve_expr(visitor, left, NodeInfo { drop_temps: terminate_lhs, extending: false }); + resolve_expr(visitor, right, NodeInfo { drop_temps: terminate_rhs, extending: false }); } // Manually recurse over closures, because they are nested bodies // that share the parent environment. We handle const blocks in @@ -352,6 +451,40 @@ fn resolve_expr<'tcx>( visitor.visit_expr(left_expr); } + hir::ExprKind::Struct(_, fields, opt_base) => { + for field in fields { + resolve_expr(visitor, field.expr, NodeInfo { drop_temps: false, extending: true }); + } + if let hir::StructTailExpr::Base(base) = opt_base { + visitor.visit_expr(base); + } + } + + // Lifetime-extend tuple constructors' arguments, such as `Some(&temp())`. + // + // That way, there is no difference between `Some(..)` and `Some { 0: .. }`, + // even though the former is syntactically a function call. + hir::ExprKind::Call(func, args) + if let hir::ExprKind::Path(path) = &func.kind + && let hir::QPath::Resolved(None, path) = path + && let Res::SelfCtor(_) | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) = + path.res => + { + for arg in args { + resolve_expr(visitor, arg, NodeInfo { drop_temps: false, extending: true }); + } + } + + hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => { + for subexpr in subexprs { + resolve_expr(visitor, subexpr, NodeInfo { drop_temps: false, extending: true }); + } + } + + hir::ExprKind::Cast(subexpr, _) => { + resolve_expr(visitor, subexpr, NodeInfo { drop_temps: false, extending: true }); + } + hir::ExprKind::If(cond, then, Some(otherwise)) => { let expr_cx = visitor.cx; let data = if expr.span.at_least_rust_2024() { @@ -362,9 +495,9 @@ fn resolve_expr<'tcx>( visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data }); visitor.cx.var_parent = visitor.cx.parent; resolve_cond(visitor, cond); - resolve_expr(visitor, then, true); + resolve_expr(visitor, then, NodeInfo { drop_temps: true, extending: true }); visitor.cx = expr_cx; - resolve_expr(visitor, otherwise, true); + resolve_expr(visitor, otherwise, NodeInfo { drop_temps: true, extending: true }); } hir::ExprKind::If(cond, then, None) => { @@ -377,18 +510,36 @@ fn resolve_expr<'tcx>( visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data }); visitor.cx.var_parent = visitor.cx.parent; resolve_cond(visitor, cond); - resolve_expr(visitor, then, true); + resolve_expr(visitor, then, NodeInfo { drop_temps: true, extending: true }); visitor.cx = expr_cx; } - hir::ExprKind::Loop(body, _, _, _) => { - resolve_block(visitor, body, true); + hir::ExprKind::Block(block, opt_label) => { + if opt_label.is_some() + && let Some(extended_parent) = visitor.cx.extended_parent + { + visitor.extending_labels.insert(block.hir_id.local_id, extended_parent); + } + visitor.visit_block(block); + } + + hir::ExprKind::Loop(body, opt_label, _, _) => { + if opt_label.is_some() + && let Some(extended_parent) = visitor.cx.extended_parent + { + visitor.extending_labels.insert(expr.hir_id.local_id, extended_parent); + } + resolve_block(visitor, body, NodeInfo { drop_temps: true, extending: false }); } hir::ExprKind::DropTemps(expr) => { // `DropTemps(expr)` does not denote a conditional scope. // Rather, we want to achieve the same behavior as `{ let _t = expr; _t }`. - resolve_expr(visitor, expr, true); + resolve_expr(visitor, expr, NodeInfo { drop_temps: true, extending: false }); + } + + hir::ExprKind::Rescope(..) => { + unreachable!("temporary scopes for re-scoping operators are handled specially") } _ => intravisit::walk_expr(visitor, expr), @@ -467,69 +618,62 @@ fn resolve_local<'tcx>( // A, but the inner rvalues `a()` and `b()` have an extended lifetime // due to rule C. - let extend_initializer = match let_kind { - LetKind::Regular => true, - LetKind::Super - if let Some(scope) = - visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) => + if let_kind == LetKind::Super { + // Use the extended parent scope (normally used for borrow expressions' operands) as the + // scope of bindings for `super let`. This allows for e.g. + // + // `let a = { super let b = temp(); &b };` === `let a = &temp();` + // + // and + // + // `identity({ super let x = temp(); &x }).method()` === `identity(&temp()).method()` + // + // to both hold. + // + // NB(super_let): This is not sufficient for `{ super let x = &$EXPR; x } === &$EXPR` to + // always hold; e.g. `let _ = &*{ super let x = &temp(); x };` =/= `let _ = &*&temp();`. + // See + // + // FIXME(super_let): This ignores backward-incompatible drop hints. Implementing BIDs for + // `super let` bindings could improve `tail_expr_drop_order` with regard to `pin!`, etc. + + visitor.cx.var_parent = match visitor.cx.extended_parent { + // If the extended parent scope was set, use it. + Some(ExtendedScope(lifetime)) => lifetime, + // Otherwise, like temporaries, bindings are dropped in the enclosing temporary scope. + None => visitor + .cx + .var_parent + .map(|block| visitor.scope_tree.default_temporary_scope(block).0), + }; + } + + if let Some(expr) = init { + if let Some(pat) = pat + && is_binding_pat(pat) { - // This expression was lifetime-extended by a parent let binding. E.g. - // - // let a = { - // super let b = temp(); - // &b - // }; - // - // (Which needs to behave exactly as: let a = &temp();) - // - // Processing of `let a` will have already decided to extend the lifetime of this - // `super let` to its own var_scope. We use that scope. - visitor.cx.var_parent = scope; - // Extend temporaries to live in the same scope as the parent `let`'s bindings. - true - } - LetKind::Super => { - // This `super let` is not subject to lifetime extension from a parent let binding. E.g. - // - // identity({ super let x = temp(); &x }).method(); - // - // (Which needs to behave exactly as: identity(&temp()).method();) - // - // Iterate up to the enclosing destruction scope to find the same scope that will also - // be used for the result of the block itself. - if let Some(inner_scope) = visitor.cx.var_parent { - visitor.cx.var_parent = - Some(visitor.scope_tree.default_temporary_scope(inner_scope).0) - } - // Don't lifetime-extend child `super let`s or block tail expressions' temporaries in - // the initializer when this `super let` is not itself extended by a parent `let` - // (#145784). Block tail expressions are temporary drop scopes in Editions 2024 and - // later, their temps shouldn't outlive the block in e.g. `f(pin!({ &temp() }))`. - false + record_subexpr_extended_temp_scopes( + &mut visitor.scope_tree, + expr, + visitor.cx.var_parent, + ); } - }; - if let Some(expr) = init - && extend_initializer - { - record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent); - - if let Some(pat) = pat { - if is_binding_pat(pat) { - record_subexpr_extended_temp_scopes( - &mut visitor.scope_tree, - expr, - visitor.cx.var_parent, - ); - } + let prev_extended_parent = visitor.cx.extended_parent; + + if let_kind == LetKind::Regular { + // When visiting the initializer, extend borrows and `super let`s accessible through + // extending subexpressions to live in the current variable scope (or in the case of + // statics and consts, for the whole program). + visitor.cx.extended_parent = Some(ExtendedScope(visitor.cx.var_parent)); } - } - // Make sure we visit the initializer first. - // The correct order, as shared between drop_ranges and intravisitor, - // is to walk initializer, followed by pattern bindings, finally followed by the `else` block. - if let Some(expr) = init { - visitor.visit_expr(expr); + // Make sure we visit the initializer first. + // The correct order, as shared between drop_ranges and intravisitor, is + // to walk initializer, followed by pattern bindings, finally followed by the `else` block. + resolve_expr(visitor, expr, NodeInfo { drop_temps: false, extending: true }); + + visitor.cx.extended_parent = prev_extended_parent; } if let Some(pat) = pat { @@ -601,85 +745,6 @@ fn resolve_local<'tcx>( | PatKind::Err(_) => false, } } - - /// If `expr` matches the `E&` grammar, then records an extended temporary scope as appropriate: - /// - /// ```text - /// E& = & ET - /// | StructName { ..., f: E&, ... } - /// | [ ..., E&, ... ] - /// | ( ..., E&, ... ) - /// | {...; E&} - /// | { super let ... = E&; ... } - /// | if _ { ...; E& } else { ...; E& } - /// | match _ { ..., _ => E&, ... } - /// | box E& - /// | E& as ... - /// | ( E& ) - /// ``` - fn record_rvalue_scope_if_borrow_expr<'tcx>( - visitor: &mut ScopeResolutionVisitor<'tcx>, - expr: &hir::Expr<'_>, - blk_id: Option, - ) { - match expr.kind { - hir::ExprKind::AddrOf(_, _, subexpr) => { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); - record_subexpr_extended_temp_scopes(&mut visitor.scope_tree, subexpr, blk_id); - } - hir::ExprKind::Struct(_, fields, _) => { - for field in fields { - record_rvalue_scope_if_borrow_expr(visitor, field.expr, blk_id); - } - } - hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => { - for subexpr in subexprs { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); - } - } - hir::ExprKind::Cast(subexpr, _) => { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id) - } - hir::ExprKind::Block(block, _) => { - if let Some(subexpr) = block.expr { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); - } - for stmt in block.stmts { - if let hir::StmtKind::Let(local) = stmt.kind - && let Some(_) = local.super_ - { - visitor.extended_super_lets.insert(local.pat.hir_id.local_id, blk_id); - } - } - } - hir::ExprKind::If(_, then_block, else_block) => { - record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id); - if let Some(else_block) = else_block { - record_rvalue_scope_if_borrow_expr(visitor, else_block, blk_id); - } - } - hir::ExprKind::Match(_, arms, _) => { - for arm in arms { - record_rvalue_scope_if_borrow_expr(visitor, arm.body, blk_id); - } - } - hir::ExprKind::Call(func, args) => { - // Recurse into tuple constructors, such as `Some(&temp())`. - // - // That way, there is no difference between `Some(..)` and `Some { 0: .. }`, - // even though the former is syntactically a function call. - if let hir::ExprKind::Path(path) = &func.kind - && let hir::QPath::Resolved(None, path) = path - && let Res::SelfCtor(_) | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) = path.res - { - for arg in args { - record_rvalue_scope_if_borrow_expr(visitor, arg, blk_id); - } - } - } - _ => {} - } - } } /// Applied to an expression `expr` if `expr` -- or something owned or partially owned by @@ -737,15 +802,20 @@ impl<'tcx> ScopeResolutionVisitor<'tcx> { self.cx.parent = Some(child_scope); } - fn enter_node_scope_with_dtor(&mut self, id: hir::ItemLocalId, terminating: bool) { + fn enter_node_scope(&mut self, id: hir::ItemLocalId, node_info: NodeInfo) { // If node was previously marked as a terminating scope during the // recursive visit of its parent node in the HIR, then we need to // account for the destruction scope representing the scope of // the destructors that run immediately after it completes. - if terminating { + if node_info.drop_temps { self.enter_scope(Scope { local_id: id, data: ScopeData::Destruction }); } self.enter_scope(Scope { local_id: id, data: ScopeData::Node }); + // If this scope corresponds to a non-extending subexpression, limit the scopes of + // temporaries to the enclosing temporary scope. + if !node_info.extending { + self.cx.extended_parent = None; + } } fn enter_body(&mut self, hir_id: hir::HirId, f: impl FnOnce(&mut Self)) { @@ -763,7 +833,7 @@ impl<'tcx> ScopeResolutionVisitor<'tcx> { impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { fn visit_block(&mut self, b: &'tcx Block<'tcx>) { - resolve_block(self, b, false); + resolve_block(self, b, NodeInfo { drop_temps: false, extending: true }); } fn visit_body(&mut self, body: &hir::Body<'tcx>) { @@ -787,7 +857,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { } // The body of the every fn is a root scope. - resolve_expr(this, body.value, true); + resolve_expr(this, body.value, NodeInfo { drop_temps: true, extending: false }); } else { // All bodies have an outer temporary drop scope, but temporaries // and `super let` bindings in constant initializers may be extended @@ -827,7 +897,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { resolve_stmt(self, s); } fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - resolve_expr(self, ex, false); + resolve_expr(self, ex, NodeInfo { drop_temps: false, extending: false }); } fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) { let let_kind = match l.super_ { @@ -859,8 +929,8 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &ScopeTr let mut visitor = ScopeResolutionVisitor { tcx, scope_tree: ScopeTree::default(), - cx: Context { parent: None, var_parent: None }, - extended_super_lets: Default::default(), + cx: Context { parent: None, var_parent: None, extended_parent: None }, + extending_labels: Default::default(), }; visitor.scope_tree.root_body = Some(body.value.hir_id); diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 393e238712960..4b4d9e3d0c8a4 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -1778,6 +1778,18 @@ impl<'a> State<'a> { self.word_space("yield"); self.print_expr_cond_paren(expr, self.precedence(expr) < ExprPrecedence::Jump); } + hir::ExprKind::Rescope(kind, target, expr) => { + match kind { + hir::RescopeKind::Scope => self.word("scope!("), + hir::RescopeKind::Extend => self.word("extend!("), + } + self.print_ident(target.label.ident); + self.space(); + self.word("=>"); + self.space(); + self.print_expr(expr); + self.word(")"); + } hir::ExprKind::Err(_) => { self.popen(); self.word("/*ERROR*/"); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index c5fb813274992..b6aa3c5045e70 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -399,6 +399,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ExprKind::UnsafeBinderCast(kind, inner_expr, ty) => { self.check_expr_unsafe_binder_cast(expr.span, kind, inner_expr, ty, expected) } + ExprKind::Rescope(_, _, oprnd) => self.check_expr_with_expectation(oprnd, expected), ExprKind::Err(guar) => Ty::new_error(tcx, guar), } } diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index 96ede89664fea..741d0e6f7463a 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -402,6 +402,10 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx self.walk_expr(subexpr)?; } + hir::ExprKind::Rescope(_, _, subexpr) => { + self.walk_expr(subexpr)?; + } + hir::ExprKind::Unary(hir::UnOp::Deref, base) => { // *base self.walk_expr(base)?; @@ -1351,8 +1355,8 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx self.cat_res(expr.hir_id, expr.span, expr_ty, res) } - // type ascription doesn't affect the place-ness of the subexpression. - hir::ExprKind::Type(e, _) => self.cat_expr(e), + // type ascription and re-scoping don't affect the place-ness of the subexpression. + hir::ExprKind::Type(e, _) | hir::ExprKind::Rescope(_, _, e) => self.cat_expr(e), hir::ExprKind::UnsafeBinderCast(UnsafeBinderCastKind::Unwrap, e, _) => { let base = self.cat_expr(e)?; diff --git a/compiler/rustc_hir_typeck/src/naked_functions.rs b/compiler/rustc_hir_typeck/src/naked_functions.rs index ddeec25acad7a..b674de79e9f89 100644 --- a/compiler/rustc_hir_typeck/src/naked_functions.rs +++ b/compiler/rustc_hir_typeck/src/naked_functions.rs @@ -142,6 +142,7 @@ impl CheckInlineAssembly { | ExprKind::Cast(..) | ExprKind::Type(..) | ExprKind::UnsafeBinderCast(..) + | ExprKind::Rescope(..) | ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::If(..) diff --git a/compiler/rustc_lint/src/dangling.rs b/compiler/rustc_lint/src/dangling.rs index 88161e99b1759..9906ba636d334 100644 --- a/compiler/rustc_lint/src/dangling.rs +++ b/compiler/rustc_lint/src/dangling.rs @@ -327,6 +327,10 @@ fn is_temporary_rvalue(expr: &Expr<'_>) -> bool { | ExprKind::DropTemps(..) | ExprKind::Let(..) => false, + // Whether `scope!` and `extend!` produce a short-lived temporary depends on their + // subexpression, and in the case of `scope!`, also the label. For simplicity, don't lint. + ExprKind::Rescope(..) => false, + ExprKind::UnsafeBinderCast(..) => false, // Not applicable diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index 13bda2991fa92..c0c795479d3b4 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -246,7 +246,7 @@ impl<'tcx> TyCtxt<'tcx> { // Place-preserving expressions only constitute reads if their // parent expression constitutes a read. - ExprKind::Type(..) | ExprKind::UnsafeBinderCast(..) => { + ExprKind::Type(..) | ExprKind::UnsafeBinderCast(..) | ExprKind::Rescope(..) => { self.expr_guaranteed_to_constitute_read_for_never(parent_expr) } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 2f39be4214e46..f1feb64cb6b96 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -60,7 +60,17 @@ impl<'tcx> ThirBuildCx<'tcx> { } #[instrument(level = "trace", skip(self, hir_expr))] - pub(super) fn mirror_expr_inner(&mut self, hir_expr: &'tcx hir::Expr<'tcx>) -> ExprId { + pub(super) fn mirror_expr_inner(&mut self, mut hir_expr: &'tcx hir::Expr<'tcx>) -> ExprId { + // Peel off any re-scoping operators (`scope!`, `extend!`). Their changes to temporary + // scopes are only relevant when building the `ScopeTree`; they are otherwise transparent. + // We keep track of them to apply their adjustments, if present, so we don't have to be + // picky about not putting adjustments on them. + let mut rescoping_ops = vec![]; + while let hir::ExprKind::Rescope(_, _, subexpr) = &hir_expr.kind { + rescoping_ops.push(hir_expr); + hir_expr = subexpr; + } + let expr_scope = region::Scope { local_id: hir_expr.hir_id.local_id, data: region::ScopeData::Node }; @@ -95,7 +105,10 @@ impl<'tcx> ThirBuildCx<'tcx> { // Now apply adjustments, if any. if self.apply_adjustments { - for adjustment in self.typeck_results.expr_adjustments(hir_expr) { + for adjustment in std::iter::once(hir_expr) + .chain(rescoping_ops.into_iter().rev()) + .flat_map(|e| self.typeck_results.expr_adjustments(e)) + { trace!(?expr, ?adjustment); let span = expr.span; expr = self.apply_adjustment(hir_expr, expr, adjustment, span); @@ -1151,6 +1164,8 @@ impl<'tcx> ThirBuildCx<'tcx> { hir::ExprKind::Tup(fields) => ExprKind::Tuple { fields: self.mirror_exprs(fields) }, hir::ExprKind::Yield(v, _) => ExprKind::Yield { value: self.mirror_expr(v) }, + // `ExprKind::Rescope`s are peeled off in `mirror_expr_inner` + hir::ExprKind::Rescope(..) => unreachable!("re-scoping operators do not produce THIR"), hir::ExprKind::Err(_) => unreachable!("cannot lower a `hir::ExprKind::Err` to THIR"), }; diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index 238eebd73a0fa..bb8cd73f1a24e 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -3825,6 +3825,13 @@ pub(crate) struct ExpectedLabelFoundIdent { pub start: Span, } +#[derive(Diagnostic)] +#[diag("expected a label")] +pub(crate) struct ExpectedLabel { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("{$article} {$descr} cannot be `default`")] #[note("only associated `fn`, `const`, and `type` items can be `default`")] diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 34044e72ab92b..989f05e19cf66 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -14,8 +14,8 @@ use rustc_ast::visit::{Visitor, walk_expr}; use rustc_ast::{ self as ast, AnonConst, Arm, AssignOp, AssignOpKind, AttrStyle, AttrVec, BinOp, BinOpKind, BlockCheckMode, CaptureBy, ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl, - FnRetTy, ForLoop, Guard, Label, MacCall, MetaItemLit, Movability, Param, RangeLimits, StmtKind, - Ty, TyKind, UnOp, UnsafeBinderCastKind, YieldKind, + FnRetTy, ForLoop, Guard, Label, MacCall, MetaItemLit, Movability, Param, RangeLimits, + RescopeKind, StmtKind, Ty, TyKind, UnOp, UnsafeBinderCastKind, YieldKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -2049,6 +2049,8 @@ impl<'a> Parser<'a> { sym::unwrap_binder => { Some(this.parse_expr_unsafe_binder_cast(lo, UnsafeBinderCastKind::Unwrap)?) } + sym::scope => Some(this.parse_expr_rescope(lo, RescopeKind::Scope)?), + sym::extend => Some(this.parse_expr_rescope(lo, RescopeKind::Extend)?), _ => None, }) }) @@ -2134,6 +2136,20 @@ impl<'a> Parser<'a> { Ok(self.mk_expr(span, ExprKind::UnsafeBinderCast(kind, expr, ty))) } + /// Built-in macros `scope!` and `extend!` for temporary scoping. + fn parse_expr_rescope(&mut self, lo: Span, kind: RescopeKind) -> PResult<'a, Box> { + // FIXME(super_let): we may be able to recover identifiers into labels here, or generally + // provide better diagnostics + let Some(label) = self.eat_label() else { + let err = self.dcx().create_err(diagnostics::ExpectedLabel { span: self.token.span }); + return Err(err); + }; + self.expect(exp!(FatArrow))?; + let expr = self.parse_expr()?; + let span = lo.to(self.token.span); + Ok(self.mk_expr(span, ExprKind::Rescope(kind, label, expr))) + } + /// Returns a string literal if the next token is a string literal. /// In case of error returns `Some(lit)` if the next token is a literal with a wrong kind, /// and returns `None` if the next token is not literal at all. @@ -4490,7 +4506,8 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::Call(_, _) | ExprKind::MethodCall(_) | ExprKind::Tup(_) - | ExprKind::Paren(_) => { + | ExprKind::Paren(_) + | ExprKind::Rescope(_, _, _) => { let forbid_let_reason = self.forbid_let_reason; self.forbid_let_reason = Some(diagnostics::ForbiddenLetReason::OtherForbidden); mut_visit::walk_expr(self, e); diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index 87193b73a1a95..250e6c4642a33 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -380,6 +380,7 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { Repeat, Yield, UnsafeBinderCast, + Rescope, Err ] ); @@ -661,7 +662,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { If, While, ForLoop, Loop, Match, Closure, Block, Await, Move, Use, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, - Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy, DirectConstArg + Become, IncludedBytes, Gen, UnsafeBinderCast, Rescope, Err, Dummy, DirectConstArg ] ); ast_visit::walk_expr(self, e) diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 535d11d00d718..d416f49d7015e 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -5213,7 +5213,9 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } } - ExprKind::Break(Some(label), _) | ExprKind::Continue(Some(label)) => { + ExprKind::Break(Some(label), _) + | ExprKind::Continue(Some(label)) + | ExprKind::Rescope(_, label, _) => { match self.resolve_label(label.ident) { Ok((node_id, _)) => { // Since this res is a label, it is never read. @@ -5225,7 +5227,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } } - // visit `break` argument if any + // visit `break` argument if any, or the operand for `scope!` and `extend!` visit::walk_expr(self, expr); } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 39e6f7445b09c..aebd4a264255d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -924,6 +924,7 @@ symbols! { expr, expr_2021, expr_fragment_specifier_2024, + extend, extended_key_value_attributes, extended_varargs_abi_support, extendedl32r, @@ -1885,6 +1886,7 @@ symbols! { sanitizer_runtime, saturating_add, saturating_sub, + scope, sdylib, search_unbox, section, diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index b58d3b7f1f539..80224e67d67ab 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1965,4 +1965,28 @@ pub(crate) mod builtin { pub macro eii_declaration($item:item) { /* compiler built-in */ } + + /// `scope!('l => $expr)` sets the [enclosing temporary scope] of `$expr` to that of the + /// expression labeled with `'l`. + /// + /// [enclosing temporary scope]: https://doc.rust-lang.org/reference/destructors.html#r-destructors.scope.temporary.enclosing + // FIXME(super_let): add documentation, ideally using Reference terminology + #[allow_internal_unstable(builtin_syntax)] + #[unstable(feature = "super_let", issue = "139076")] + #[diagnostic::opaque] + pub macro scope($($arg:tt)*) { + builtin # scope($($arg)*) + } + + /// `extend!('l => $expr)` sets the [lifetime-extension context] of `$expr` to that of the + /// expression labeled with `'l`. + /// + /// [lifetime-extension context]: https://doc.rust-lang.org/reference/destructors.html#temporary-lifetime-extension + // FIXME(super_let): add documentation, ideally using Reference terminology + #[allow_internal_unstable(builtin_syntax)] + #[unstable(feature = "super_let", issue = "139076")] + #[diagnostic::opaque] + pub macro extend($($arg:tt)*) { + builtin # extend($($arg)*) + } } diff --git a/library/core/src/prelude/v1.rs b/library/core/src/prelude/v1.rs index 6122ab12ec351..01e9ed91004dc 100644 --- a/library/core/src/prelude/v1.rs +++ b/library/core/src/prelude/v1.rs @@ -158,6 +158,9 @@ pub use crate::macros::builtin::type_ascribe; )] pub use crate::macros::builtin::deref; +#[unstable(feature = "super_let", issue = "139076")] +pub use crate::macros::builtin::{scope, extend}; + #[unstable( feature = "type_alias_impl_trait", issue = "63063", diff --git a/tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr b/tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr new file mode 100644 index 0000000000000..635f301b9a16b --- /dev/null +++ b/tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr @@ -0,0 +1,39 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:40:90 + | +LL | println!("{:?}{:?}", (), match true { true => &"" as &dyn std::fmt::Debug, false => &temp() }); + | ------------------------------------------------------------^^^^^--- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:33:41 + | +LL | println!("{:?}{:?}", (), if true { &format!("") } else { "" }); + | -----------^^^^^^^^^^^-------------- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:36:64 + | +LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); + | ----------------------------------^^^^^^^^^^^--------------- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr b/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr index 506fc6e0965f7..58d8e64878b9d 100644 --- a/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr +++ b/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr @@ -1,5 +1,5 @@ error[E0716]: temporary value dropped while borrowed - --> $DIR/format-args-temporary-scopes.rs:13:25 + --> $DIR/format-args-temporary-scopes.rs:12:25 | LL | println!("{:?}", { &temp() }); | ---^^^^^--- @@ -11,7 +11,19 @@ LL | println!("{:?}", { &temp() }); = note: consider using a `let` binding to create a longer lived value error[E0716]: temporary value dropped while borrowed - --> $DIR/format-args-temporary-scopes.rs:19:29 + --> $DIR/format-args-temporary-scopes.rs:17:48 + | +LL | println!("{:?}", { std::convert::identity(&temp()) }); + | --------------------------^^^^^^--- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:23:29 | LL | println!("{:?}{:?}", { &temp() }, ()); | ---^^^^^--- @@ -22,6 +34,54 @@ LL | println!("{:?}{:?}", { &temp() }, ()); | = note: consider using a `let` binding to create a longer lived value -error: aborting due to 2 previous errors +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:26:52 + | +LL | println!("{:?}{:?}", { std::convert::identity(&temp()) }, ()); + | --------------------------^^^^^^--- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:40:90 + | +LL | println!("{:?}{:?}", (), match true { true => &"" as &dyn std::fmt::Debug, false => &temp() }); + | ------------------------------------------------------------^^^^^--- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:33:41 + | +LL | println!("{:?}{:?}", (), if true { &format!("") } else { "" }); + | -^^^^^^^^^^- + | || | + | || temporary value is freed at the end of this statement + | |creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:36:64 + | +LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); + | ------------------------^^^^^^^^^^^- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error: aborting due to 7 previous errors For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/borrowck/format-args-temporary-scopes.rs b/tests/ui/borrowck/format-args-temporary-scopes.rs index 2641058accb31..6f9a7a22e2de8 100644 --- a/tests/ui/borrowck/format-args-temporary-scopes.rs +++ b/tests/ui/borrowck/format-args-temporary-scopes.rs @@ -1,7 +1,6 @@ //! Test for #145784 as it relates to format arguments: arguments to macros such as `println!` //! should obey normal temporary scoping rules. //@ revisions: e2021 e2024 -//@ [e2021] check-pass //@ [e2021] edition: 2021 //@ [e2024] edition: 2024 @@ -13,9 +12,31 @@ fn main() { println!("{:?}", { &temp() }); //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] + // Arguments to function calls aren't extending expressions, so `temp()` is dropped at the end + // of the block in Rust 2024. + println!("{:?}", { std::convert::identity(&temp()) }); + //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] + // In Rust 1.89, `format_args!` extended the lifetime of all extending expressions in its // arguments when provided with two or more arguments. This caused the result of `temp()` to // outlive the result of the block, making this compile. println!("{:?}{:?}", { &temp() }, ()); //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] + + println!("{:?}{:?}", { std::convert::identity(&temp()) }, ()); + //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] + + // In real-world projects, this typically appeared in `if` expressions with a `&str` in one + // branch and a reference to a `String` temporary in the other. Since the consequent and `else` + // blocks of `if` expressions are temporary scopes in all editions, this affects Rust 2021 and + // earlier as well. + println!("{:?}{:?}", (), if true { &format!("") } else { "" }); + //~^ ERROR: temporary value dropped while borrowed [E0716] + + println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); + //~^ ERROR: temporary value dropped while borrowed [E0716] + + // This has likewise occurred with `match`, affecting all editions. + println!("{:?}{:?}", (), match true { true => &"" as &dyn std::fmt::Debug, false => &temp() }); + //~^ ERROR: temporary value dropped while borrowed [E0716] } diff --git a/tests/ui/borrowck/super-let-in-if-block.rs b/tests/ui/borrowck/super-let-in-if-block.rs new file mode 100644 index 0000000000000..9e0ca6fa2f313 --- /dev/null +++ b/tests/ui/borrowck/super-let-in-if-block.rs @@ -0,0 +1,28 @@ +//! Test that `super let` bindings in `if` expressions' blocks have the same scope as the result +//! of the block. +#![feature(super_let)] + +fn main() { + // For `super let` in an extending `if`, the binding `temp` should live in the scope of the + // outer `let` statement. + let x = if true { + super let temp = (); + &temp + } else { + super let temp = (); + &temp + }; + x; + + // For `super let` in non-extending `if`, the binding `temp` should live in the temporary scope + // the `if` expression is in. + std::convert::identity(if true { + super let temp = (); + &temp + //~^ ERROR `temp` does not live long enough + } else { + super let temp = (); + &temp + //~^ ERROR `temp` does not live long enough + }); +} diff --git a/tests/ui/borrowck/super-let-in-if-block.stderr b/tests/ui/borrowck/super-let-in-if-block.stderr new file mode 100644 index 0000000000000..9d0519dcc78c7 --- /dev/null +++ b/tests/ui/borrowck/super-let-in-if-block.stderr @@ -0,0 +1,30 @@ +error[E0597]: `temp` does not live long enough + --> $DIR/super-let-in-if-block.rs:21:9 + | +LL | std::convert::identity(if true { + | ---------------------- borrow later used by call +LL | super let temp = (); + | ---- binding `temp` declared here +LL | &temp + | ^^^^^ borrowed value does not live long enough +LL | +LL | } else { + | - `temp` dropped here while still borrowed + +error[E0597]: `temp` does not live long enough + --> $DIR/super-let-in-if-block.rs:25:9 + | +LL | std::convert::identity(if true { + | ---------------------- borrow later used by call +... +LL | super let temp = (); + | ---- binding `temp` declared here +LL | &temp + | ^^^^^ borrowed value does not live long enough +LL | +LL | }); + | - `temp` dropped here while still borrowed + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/borrowck/super-let-projection-extension.rs b/tests/ui/borrowck/super-let-projection-extension.rs new file mode 100644 index 0000000000000..5d3a7f98c8c37 --- /dev/null +++ b/tests/ui/borrowck/super-let-projection-extension.rs @@ -0,0 +1,25 @@ +//! Demonstrates a case where `{ super let x = temp(); &x }` =/= `&temp()`, a variant of which is +//! observable on stable Rust via the `pin!` macro: `pin!($EXPR)` and `&mut $EXPR` may use different +//! scopes for their temporaries. + +#![feature(super_let)] + +use std::pin::pin; + +fn temp() {} + +fn main() { + // This is fine, since the temporary is extended to the end of the block: + let a = &*&temp(); + a; + let b = &mut *&mut temp(); + b; + + // The temporary is dropped at the end of the outer `let` initializer: + let c = &*{ super let x = temp(); &x }; + //~^ ERROR `x` does not live long enough + c; + let d = &mut *pin!(temp()); + //~^ ERROR temporary value dropped while borrowed + d; +} diff --git a/tests/ui/borrowck/super-let-projection-extension.stderr b/tests/ui/borrowck/super-let-projection-extension.stderr new file mode 100644 index 0000000000000..9b6229cf52fd4 --- /dev/null +++ b/tests/ui/borrowck/super-let-projection-extension.stderr @@ -0,0 +1,29 @@ +error[E0597]: `x` does not live long enough + --> $DIR/super-let-projection-extension.rs:19:39 + | +LL | let c = &*{ super let x = temp(); &x }; + | - ^^ - `x` dropped here while still borrowed + | | | + | | borrowed value does not live long enough + | binding `x` declared here +LL | +LL | c; + | - borrow later used here + +error[E0716]: temporary value dropped while borrowed + --> $DIR/super-let-projection-extension.rs:22:19 + | +LL | let d = &mut *pin!(temp()); + | ^^^^^^^^^^^^- temporary value is freed at the end of this statement + | | + | creates a temporary value which is freed while still in use +LL | +LL | d; + | - borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0597, E0716. +For more information about an error, try `rustc --explain E0597`. diff --git a/tests/ui/drop/destructuring-assignments.rs b/tests/ui/drop/destructuring-assignments.rs new file mode 100644 index 0000000000000..52d787b2b3ede --- /dev/null +++ b/tests/ui/drop/destructuring-assignments.rs @@ -0,0 +1,68 @@ +// Test drop order for destructuring assignments against +// other expressions they should be consistent with. +// +// See: +// +// - https://github.com/rust-lang/rust/pull/145838 +// +// Original author: TC +// Date: 2025-08-30 +//@ edition: 2024 +//@ run-pass + +#![allow(unused_must_use)] + +fn main() { + assert_drop_order(1..=3, |e| { + &({ &raw const *&e.log(1) }, drop(e.log(2))); + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + { let _x; _x = &({ &raw const *&e.log(1) }, drop(e.log(2))); } + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + _ = &({ &raw const *&e.log(2) }, drop(e.log(1))); + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + { let _ = &({ &raw const *&e.log(2) }, drop(e.log(1))); } + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + let _x; let _y; + (_x, _y) = ({ &raw const *&e.log(2) }, drop(e.log(1))); + drop(e.log(3)); + }); +} + +// # Test scaffolding... + +use core::cell::RefCell; + +struct DropOrder(RefCell>); +struct LogDrop<'o>(&'o DropOrder, u64); + +impl DropOrder { + fn log(&self, n: u64) -> LogDrop<'_> { + LogDrop(self, n) + } +} + +impl<'o> Drop for LogDrop<'o> { + fn drop(&mut self) { + self.0 .0.borrow_mut().push(self.1); + } +} + +#[track_caller] +fn assert_drop_order( + ex: impl IntoIterator, + f: impl Fn(&DropOrder), +) { + let order = DropOrder(RefCell::new(Vec::new())); + f(&order); + let order = order.0.into_inner(); + let expected: Vec = ex.into_iter().collect(); + assert_eq!(order, expected); +} diff --git a/tests/ui/drop/scope-extend.rs b/tests/ui/drop/scope-extend.rs new file mode 100644 index 0000000000000..44036d86cac80 --- /dev/null +++ b/tests/ui/drop/scope-extend.rs @@ -0,0 +1,120 @@ +// TODO: systematically add more tests, clean up tests, split up tests, reframe comments +//@ edition: 2024 +//@ run-pass + +#![feature(super_let)] +#![allow(unused, dropping_references)] + +fn main() { + // `scope!` sets the enclosing temporary scope to that of the labeled expression, affecting its + // operand's temporary scope and potentially the temporary scopes of its subexpressions. + assert_drop_order(1..=7, |e| { + ( + 'l: { + &scope!('l => e.log(6)); + scope!('l => &e.log(5)); + match scope!('l => e.log(4)) { + _ => {} + } + scope!('l => match e.log(3) { + _ => {} + }); + // Long-lived temporaries aren't created here. + scope!('l => e.log(1)); + }, + drop(e.log(2)), + ); + drop(e.log(7)); + }); + + // `extend!` sets the temporary lifetime used by the `&` operator to that of a parent context. + // Absent lifetime extension, it will be the temporary scope enclosing that parent. + assert_drop_order(1..=6, |e| { + { + let x = 'l: { + // Lifetime extension is interrupted for function arguments. + let y = extend!('l => (&e.log(5), drop(&e.log(1)))); + e.log(2); + // `extend!` can be used under `&`, like `scope!`. In general, I'd like re-scoping + // operators to annotate the exact expression we want to modify the scope of. + let z = &extend!('l => e.log(4)); + (y, z) + }; + x; + e.log(3); + } + e.log(6); + }); + assert_drop_order(1..=6, |e| { + ( + 'l: { + // The temporary scope used by the operand to a borrow operator at `'l` would be the + // tuple expression. As such, `extend!('l => ...)` extends borrows to that scope. + extend!('l => (&e.log(5), drop(&e.log(1)))); + e.log(2); + &extend!('l => e.log(4)); + }, + drop(e.log(3)), + ); + e.log(6); + }); + + // `scope!`'s operand is non-extending. Combine it with `extend!` for lifetime-extension. + assert_drop_order(1..=7, |e| { + { + let x = 'l: { + // These temporaries will be dropped at the end of the parent `let` statement. + let _ = &scope!('l => e.log(3)); + let _ = scope!('l => &e.log(2)); + // These lifetime-extended temporaries will live to the end of the parent block. + let _ = &scope!('l => extend!('l => e.log(6))); + let _ = scope!('l => extend!('l => &e.log(5))); + e.log(1); + }; + e.log(4); + } + e.log(7); + }); + + // We currently lack a way to lifetime-extend without using `&` or `&mut`. This means we can't + // lifetime-extend match scrutinees or method recievers. If that's something we want, we could + // have an operator that extends an expression's temporary scope (to the same scope that would + // be used by `&` or `&mut`). However, there's not yet a known use for this. + assert_drop_order(1..=2, |e| { + 'l: { + // This `extend!` does nothing. We'd need another operator to apply lifetime-extension. + match extend!('l => e.log(1)) { + _ => {} + } + e.log(2); + } + }); +} + +// Test scaffolding + +use core::cell::RefCell; + +struct DropOrder(RefCell>); +struct LogDrop<'o>(&'o DropOrder, u64); + +impl DropOrder { + fn log(&self, n: u64) -> LogDrop<'_> { + LogDrop(self, n) + } +} + +impl<'o> Drop for LogDrop<'o> { + fn drop(&mut self) { + self.0.0.borrow_mut().push(self.1); + } +} + +#[track_caller] +fn assert_drop_order(ex: impl IntoIterator, f: impl Fn(&DropOrder)) { + let order = DropOrder(RefCell::new(Vec::new())); + f(&order); + let order = order.0.into_inner(); + let expected: Vec = ex.into_iter().collect(); + assert_eq!(order, expected); +}