diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index 5642cdf87fded..e85945d633b66 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -21,6 +21,7 @@ use rustc_middle::ty::print::Print; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_mir_dataflow::move_paths::{InitLocation, LookupResult, MoveOutIndex}; +use rustc_session::lint::builtin::MACRO_EXTENDED_TEMPORARY_SCOPES; use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Spanned; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym}; @@ -1580,4 +1581,32 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { pub(crate) fn local_excluded_from_unused_mut_lint(&self, index: Local) -> bool { self.local_name(index).is_none_or(|name| name.as_str().starts_with('_')) } + + /// Report a temporary whose scope will shorten in Rust 1.92 due to #145838. + pub(crate) fn lint_macro_extended_temporary_scope( + &self, + future_drop: Location, + borrow: &BorrowData<'tcx>, + ) { + let tcx = self.infcx.tcx; + let temp_decl = &self.body.local_decls[borrow.borrowed_place.local]; + let temp_span = temp_decl.source_info.span; + let lint_root = self.body.source_scopes[temp_decl.source_info.scope] + .local_data + .as_ref() + .unwrap_crate_local() + .lint_root; + + let mut labels = MultiSpan::from_span(temp_span); + labels.push_span_label(temp_span, "this expression creates a temporary value..."); + labels.push_span_label( + self.body.source_info(future_drop).span, + "...which will be dropped at the end of this block in Rust 1.92", + ); + + tcx.node_span_lint(MACRO_EXTENDED_TEMPORARY_SCOPES, lint_root, labels, |diag| { + diag.primary_message("temporary lifetime will be shortened in Rust 1.92"); + diag.note("consider using a `let` binding to create a longer lived value"); + }); + } } diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 5d2dda8b0e7cc..c00538755756c 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -843,8 +843,8 @@ impl<'a, 'tcx> ResultsVisitor<'tcx, Borrowck<'a, 'tcx>> for MirBorrowckCtxt<'a, | StatementKind::ConstEvalCounter | StatementKind::StorageLive(..) => {} // This does not affect borrowck - StatementKind::BackwardIncompatibleDropHint { place, reason: BackwardIncompatibleDropReason::Edition2024 } => { - self.check_backward_incompatible_drop(location, **place, state); + StatementKind::BackwardIncompatibleDropHint { place, reason } => { + self.check_backward_incompatible_drop(location, **place, state, *reason); } StatementKind::StorageDead(local) => { self.access_place( @@ -1386,6 +1386,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { location: Location, place: Place<'tcx>, state: &BorrowckDomain, + reason: BackwardIncompatibleDropReason, ) { let tcx = self.infcx.tcx; // If this type does not need `Drop`, then treat it like a `StorageDead`. @@ -1412,21 +1413,29 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { if matches!(borrow.kind, BorrowKind::Fake(_)) { return ControlFlow::Continue(()); } - let borrowed = this.retrieve_borrow_spans(borrow).var_or_use_path_span(); - let explain = this.explain_why_borrow_contains_point( - location, - borrow, - Some((WriteKind::StorageDeadOrDrop, place)), - ); - this.infcx.tcx.node_span_lint( - TAIL_EXPR_DROP_ORDER, - CRATE_HIR_ID, - borrowed, - |diag| { - session_diagnostics::TailExprDropOrder { borrowed }.decorate_lint(diag); - explain.add_explanation_to_diagnostic(&this, diag, "", None, None); - }, - ); + match reason { + BackwardIncompatibleDropReason::Edition2024 => { + let borrowed = this.retrieve_borrow_spans(borrow).var_or_use_path_span(); + let explain = this.explain_why_borrow_contains_point( + location, + borrow, + Some((WriteKind::StorageDeadOrDrop, place)), + ); + this.infcx.tcx.node_span_lint( + TAIL_EXPR_DROP_ORDER, + CRATE_HIR_ID, + borrowed, + |diag| { + session_diagnostics::TailExprDropOrder { borrowed } + .decorate_lint(diag); + explain.add_explanation_to_diagnostic(&this, diag, "", None, None); + }, + ); + } + BackwardIncompatibleDropReason::MacroExtendedScope => { + this.lint_macro_extended_temporary_scope(location, borrow); + } + } // We may stop at the first case ControlFlow::Break(()) }, diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 2ba7ed46f92c4..eb5ce80cc0ea6 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -24,7 +24,7 @@ use tracing::debug; #[derive(Debug, Copy, Clone)] struct Context { /// The scope that contains any new variables declared. - var_parent: Option, + var_parent: (Option, ScopeCompatibility), /// Region parent of expressions, etc. parent: Option, @@ -38,12 +38,26 @@ struct ScopeResolutionVisitor<'tcx> { cx: Context, - extended_super_lets: FxHashMap>, + extended_super_lets: FxHashMap, +} + +#[derive(Copy, Clone)] +struct ExtendedTemporaryScope { + /// The scope of extended temporaries. + scope: Option, + /// Whether this lifetime originated from a regular `let` or a `super let` initializer. In the + /// latter case, this scope may shorten after #145838 if applied to temporaries within block + /// tail expressions. + let_kind: LetKind, + /// Whether this scope will shorten after #145838. If this is applied to a temporary value, + /// we'll emit the `macro_extended_temporary_scopes` lint. + compat: ScopeCompatibility, } /// Records the lifetime of a local variable as `cx.var_parent` fn record_var_lifetime(visitor: &mut ScopeResolutionVisitor<'_>, var_id: hir::ItemLocalId) { - match visitor.cx.var_parent { + let (var_parent_scope, var_parent_compat) = visitor.cx.var_parent; + match var_parent_scope { None => { // this can happen in extern fn declarations like // @@ -51,6 +65,9 @@ fn record_var_lifetime(visitor: &mut ScopeResolutionVisitor<'_>, var_id: hir::It } Some(parent_scope) => visitor.scope_tree.record_var_scope(var_id, parent_scope), } + if let ScopeCompatibility::FutureIncompatible { shortens_to } = var_parent_compat { + visitor.scope_tree.record_future_incompatible_var_scope(var_id, shortens_to); + } } fn resolve_block<'tcx>( @@ -88,7 +105,7 @@ fn resolve_block<'tcx>( // itself has returned. visitor.enter_node_scope_with_dtor(blk.hir_id.local_id, terminating); - visitor.cx.var_parent = visitor.cx.parent; + visitor.cx.var_parent = (visitor.cx.parent, ScopeCompatibility::FutureCompatible); { // This block should be kept approximately in sync with @@ -107,7 +124,8 @@ fn resolve_block<'tcx>( local_id: blk.hir_id.local_id, data: ScopeData::Remainder(FirstStatementIndex::new(i)), }); - visitor.cx.var_parent = visitor.cx.parent; + visitor.cx.var_parent = + (visitor.cx.parent, ScopeCompatibility::FutureCompatible); visitor.visit_stmt(statement); // We need to back out temporarily to the last enclosing scope // for the `else` block, so that even the temporaries receiving @@ -131,7 +149,8 @@ fn resolve_block<'tcx>( local_id: blk.hir_id.local_id, data: ScopeData::Remainder(FirstStatementIndex::new(i)), }); - visitor.cx.var_parent = visitor.cx.parent; + visitor.cx.var_parent = + (visitor.cx.parent, ScopeCompatibility::FutureCompatible); visitor.visit_stmt(statement) } hir::StmtKind::Item(..) => { @@ -195,7 +214,7 @@ fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir: let prev_cx = visitor.cx; visitor.enter_node_scope_with_dtor(arm.hir_id.local_id, true); - visitor.cx.var_parent = visitor.cx.parent; + visitor.cx.var_parent = (visitor.cx.parent, ScopeCompatibility::FutureCompatible); resolve_pat(visitor, arm.pat); if let Some(guard) = arm.guard { @@ -203,7 +222,7 @@ fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir: // ensure they're dropped before the arm's pattern's bindings. This extends to the end of // the arm body and is the scope of its locals as well. visitor.enter_scope(Scope { local_id: arm.hir_id.local_id, data: ScopeData::MatchGuard }); - visitor.cx.var_parent = visitor.cx.parent; + visitor.cx.var_parent = (visitor.cx.parent, ScopeCompatibility::FutureCompatible); resolve_cond(visitor, guard); } resolve_expr(visitor, arm.body, false); @@ -360,7 +379,7 @@ fn resolve_expr<'tcx>( ScopeData::IfThen }; visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data }); - visitor.cx.var_parent = visitor.cx.parent; + visitor.cx.var_parent = (visitor.cx.parent, ScopeCompatibility::FutureCompatible); resolve_cond(visitor, cond); resolve_expr(visitor, then, true); visitor.cx = expr_cx; @@ -375,7 +394,7 @@ fn resolve_expr<'tcx>( ScopeData::IfThen }; visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data }); - visitor.cx.var_parent = visitor.cx.parent; + visitor.cx.var_parent = (visitor.cx.parent, ScopeCompatibility::FutureCompatible); resolve_cond(visitor, cond); resolve_expr(visitor, then, true); visitor.cx = expr_cx; @@ -467,37 +486,57 @@ fn resolve_local<'tcx>( // A, but the inner rvalues `a()` and `b()` have an extended lifetime // due to rule C. - if let_kind == LetKind::Super { - if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) { - // 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; - } else { - // 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, _) = visitor.scope_tree.default_temporary_scope(inner_scope) + let (source_let_kind, compat) = match let_kind { + // Normal `let` initializers are unaffected by #145838. + LetKind::Regular => { + (LetKind::Regular, ScopeCompatibility::FutureCompatible) + } + LetKind::Super => { + if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) { + // 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.scope, scope.compat); + // Inherit compatibility from the original `let` statement. If the original `let` + // was regular, lifetime extension should apply as normal. If the original `let` was + // `super`, blocks within the initializer will be affected by #145838. + (scope.let_kind, scope.compat) + } else { + // 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 { + // NB(@dianne): This could use the incompatibility reported by + // `default_temporary_scope` to make `tail_expr_drop_order` more comprehensive. + visitor.cx.var_parent = + (visitor.scope_tree.default_temporary_scope(inner_scope).0, ScopeCompatibility::FutureCompatible); + } + // Blocks within the initializer will be affected by #145838. + (LetKind::Super, ScopeCompatibility::FutureCompatible) } } - } + }; if let Some(expr) = init { - record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent); + let scope = ExtendedTemporaryScope { + scope: visitor.cx.var_parent.0, + let_kind: source_let_kind, + compat, + }; + record_rvalue_scope_if_borrow_expr(visitor, expr, scope); if let Some(pat) = pat { if is_binding_pat(pat) { @@ -505,7 +544,8 @@ fn resolve_local<'tcx>( expr.hir_id, RvalueCandidate { target: expr.hir_id.local_id, - lifetime: visitor.cx.var_parent, + lifetime: visitor.cx.var_parent.0, + compat: visitor.cx.var_parent.1, }, ); } @@ -607,50 +647,106 @@ fn resolve_local<'tcx>( fn record_rvalue_scope_if_borrow_expr<'tcx>( visitor: &mut ScopeResolutionVisitor<'tcx>, expr: &hir::Expr<'_>, - blk_id: Option, + scope: ExtendedTemporaryScope, ) { match expr.kind { hir::ExprKind::AddrOf(_, _, subexpr) => { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); + record_rvalue_scope_if_borrow_expr(visitor, subexpr, scope); visitor.scope_tree.record_rvalue_candidate( subexpr.hir_id, - RvalueCandidate { target: subexpr.hir_id.local_id, lifetime: blk_id }, + RvalueCandidate { + target: subexpr.hir_id.local_id, + lifetime: scope.scope, + compat: scope.compat, + }, ); } hir::ExprKind::Struct(_, fields, _) => { for field in fields { - record_rvalue_scope_if_borrow_expr(visitor, field.expr, blk_id); + record_rvalue_scope_if_borrow_expr(visitor, field.expr, scope); } } hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => { for subexpr in subexprs { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); + record_rvalue_scope_if_borrow_expr(visitor, subexpr, scope); } } hir::ExprKind::Cast(subexpr, _) => { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id) + record_rvalue_scope_if_borrow_expr(visitor, subexpr, scope) } hir::ExprKind::Block(block, _) => { if let Some(subexpr) = block.expr { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); + let tail_expr_scope = + if scope.let_kind == LetKind::Super && block.span.at_least_rust_2024() { + // The tail expression will no longer be extending after #145838. + // Since tail expressions are temporary scopes in Rust 2024, lint on + // temporaries that acquire this (longer) lifetime. + ExtendedTemporaryScope { + compat: ScopeCompatibility::FutureIncompatible { + shortens_to: Scope { + local_id: subexpr.hir_id.local_id, + data: ScopeData::Node, + }, + }, + ..scope + } + } else { + // This is extended by a regular `let`, so it won't be changed. + scope + }; + record_rvalue_scope_if_borrow_expr(visitor, subexpr, tail_expr_scope); } 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); + visitor.extended_super_lets.insert(local.pat.hir_id.local_id, scope); } } } hir::ExprKind::If(_, then_block, else_block) => { - record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id); + let then_scope = if scope.let_kind == LetKind::Super { + // The then and else blocks will no longer be extending after #145838. + // Since `if` blocks are temporary scopes in all editions, lint on temporaries + // that acquire this (longer) lifetime. + ExtendedTemporaryScope { + compat: ScopeCompatibility::FutureIncompatible { + shortens_to: Scope { + local_id: then_block.hir_id.local_id, + data: ScopeData::Node, + }, + }, + ..scope + } + } else { + // This is extended by a regular `let`, so it won't be changed. + scope + }; + record_rvalue_scope_if_borrow_expr(visitor, then_block, then_scope); if let Some(else_block) = else_block { - record_rvalue_scope_if_borrow_expr(visitor, else_block, blk_id); + let else_scope = if scope.let_kind == LetKind::Super { + // The then and else blocks will no longer be extending after #145838. + // Since `if` blocks are temporary scopes in all editions, lint on temporaries + // that acquire this (longer) lifetime. + ExtendedTemporaryScope { + compat: ScopeCompatibility::FutureIncompatible { + shortens_to: Scope { + local_id: else_block.hir_id.local_id, + data: ScopeData::Node, + }, + }, + ..scope + } + } else { + // This is extended by a regular `let`, so it won't be changed. + scope + }; + record_rvalue_scope_if_borrow_expr(visitor, else_block, else_scope); } } hir::ExprKind::Match(_, arms, _) => { for arm in arms { - record_rvalue_scope_if_borrow_expr(visitor, arm.body, blk_id); + record_rvalue_scope_if_borrow_expr(visitor, arm.body, scope); } } hir::ExprKind::Call(func, args) => { @@ -663,7 +759,7 @@ fn resolve_local<'tcx>( && 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); + record_rvalue_scope_if_borrow_expr(visitor, arg, scope); } } } @@ -730,7 +826,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { self.enter_body(body.value.hir_id, |this| { if this.tcx.hir_body_owner_kind(owner_id).is_fn_or_closure() { // The arguments and `self` are parented to the fn. - this.cx.var_parent = this.cx.parent; + this.cx.var_parent = (this.cx.parent, ScopeCompatibility::FutureCompatible); for param in body.params { this.visit_pat(param.pat); } @@ -756,7 +852,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { // would *not* let the `f()` temporary escape into an outer scope // (i.e., `'static`), which means that after `g` returns, it drops, // and all the associated destruction scope rules apply. - this.cx.var_parent = None; + this.cx.var_parent = (None, ScopeCompatibility::FutureCompatible); this.enter_scope(Scope { local_id: body.value.hir_id.local_id, data: ScopeData::Destruction, @@ -808,7 +904,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree { let mut visitor = ScopeResolutionVisitor { tcx, scope_tree: ScopeTree::default(), - cx: Context { parent: None, var_parent: None }, + cx: Context { parent: None, var_parent: (None, ScopeCompatibility::FutureCompatible) }, extended_super_lets: Default::default(), }; diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index c8f6c06b720dc..559f354a3a7cf 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -441,8 +441,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Check scope of binding. fn is_sub_scope(&self, sub_id: hir::ItemLocalId, super_id: hir::ItemLocalId) -> bool { let scope_tree = self.fcx.tcx.region_scope_tree(self.fcx.body_id); - if let Some(sub_var_scope) = scope_tree.var_scope(sub_id) - && let Some(super_var_scope) = scope_tree.var_scope(super_id) + if let (Some(sub_var_scope), _) = scope_tree.var_scope(sub_id) + && let (Some(super_var_scope), _) = scope_tree.var_scope(super_id) && scope_tree.is_subscope_of(sub_var_scope, super_var_scope) { return true; diff --git a/compiler/rustc_hir_typeck/src/rvalue_scopes.rs b/compiler/rustc_hir_typeck/src/rvalue_scopes.rs index 973dc7141e64d..9f5055f967f0e 100644 --- a/compiler/rustc_hir_typeck/src/rvalue_scopes.rs +++ b/compiler/rustc_hir_typeck/src/rvalue_scopes.rs @@ -2,7 +2,9 @@ use hir::Node; use hir::def_id::DefId; use rustc_hir as hir; use rustc_middle::bug; -use rustc_middle::middle::region::{RvalueCandidate, Scope, ScopeTree}; +use rustc_middle::middle::region::{ + ScopeCompatibility, RvalueCandidate, Scope, ScopeTree, +}; use rustc_middle::ty::RvalueScopes; use tracing::debug; @@ -29,6 +31,7 @@ fn record_rvalue_scope_rec( rvalue_scopes: &mut RvalueScopes, mut expr: &hir::Expr<'_>, lifetime: Option, + compat: ScopeCompatibility, ) { loop { // Note: give all the expressions matching `ET` with the @@ -37,7 +40,7 @@ fn record_rvalue_scope_rec( // into a temporary, we request the temporary scope of the // outer expression. - rvalue_scopes.record_rvalue_scope(expr.hir_id.local_id, lifetime); + rvalue_scopes.record_rvalue_scope(expr.hir_id.local_id, lifetime, compat); match expr.kind { hir::ExprKind::AddrOf(_, _, subexpr) @@ -58,7 +61,7 @@ fn record_rvalue_scope( candidate: &RvalueCandidate, ) { debug!("resolve_rvalue_scope(expr={expr:?}, candidate={candidate:?})"); - record_rvalue_scope_rec(rvalue_scopes, expr, candidate.lifetime) + record_rvalue_scope_rec(rvalue_scopes, expr, candidate.lifetime, candidate.compat) // FIXME(@dingxiangfei2009): handle the candidates in the function call arguments } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index b79075ac09b9c..1036d85bacfbb 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -62,6 +62,7 @@ declare_lint_pass! { LONG_RUNNING_CONST_EVAL, LOSSY_PROVENANCE_CASTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, + MACRO_EXTENDED_TEMPORARY_SCOPES, MACRO_USE_EXTERN_CRATE, MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, @@ -5195,3 +5196,54 @@ declare_lint! { Warn, r#"detects when a function annotated with `#[inline(always)]` and `#[target_feature(enable = "..")]` is inlined into a caller without the required target feature"#, } + +declare_lint! { + /// The `macro_extended_temporary_scopes` lint detects borrowed temporary + /// values in arguments to `pin!` and formatting macros which have longer + /// lifetimes than intended due to a bug in the compiler. For more + /// information on temporary scopes and lifetime extension, see the + /// [Rust Reference]. + /// + /// [Rust Reference]: https://doc.rust-lang.org/reference/destructors.html#temporary-scopes + /// + /// ### Example + /// + /// ```rust + /// # fn cond() -> bool { true } + /// # fn build_string() -> String { String::new() } + /// fn main() { + /// println!("{:?}{}", (), if cond() { &build_string() } else { "" }); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Recommended fix + /// + /// To extend the lifetimes of temporaries borrowed in macro arguments, + /// create separate definitions for them with `let` statements. + /// + /// ```rust + /// # fn cond() -> bool { true } + /// # fn build_string() -> String { String::new() } + /// fn main() { + /// let string = if cond() { &build_string() } else { "" }; + /// println!("{:?}{}", (), string); + /// } + /// ``` + /// + /// ### Explanation + /// + /// Due to a compiler bug, `pin!` and formatting macros were able to extend + /// the lifetimes of temporaries borrowed in their arguments past their + /// usual scopes. The bug is fixed in future Rust versions, so we issue this + /// future-incompatibility warning for code that may stop compiling or may + /// change in behavior thereafter. + pub MACRO_EXTENDED_TEMPORARY_SCOPES, + Warn, + "detects when a lifetime-extended temporary borrowed in a macro argument has a future-incompatible scope.", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseError, + reference: "", + }; +} diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 5367e5edd496a..0c881bc3a3b54 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -16,6 +16,7 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable}; use rustc_span::{DUMMY_SP, Span}; use tracing::debug; +use crate::mir::BackwardIncompatibleDropReason; use crate::ty::TyCtxt; /// Represents a statically-describable scope that can be used to @@ -221,6 +222,10 @@ pub struct ScopeTree { /// variable is declared. var_map: FxIndexMap, + /// Maps from bindings to their future scopes after #145838 for the + /// `macro_extended_temporary_scopes` lint. + var_compatibility_map: FxIndexMap, + /// Identifies expressions which, if captured into a temporary, ought to /// have a temporary whose lifetime extends to the end of the enclosing *block*, /// and not the enclosing *statement*. Expressions that are not present in this @@ -242,6 +247,19 @@ pub struct ScopeTree { pub struct RvalueCandidate { pub target: hir::ItemLocalId, pub lifetime: Option, + pub compat: ScopeCompatibility, +} + +/// Marks extended temporary scopes that will be shortened by #145838 and thus need to be linted on +/// by the `macro_extended_temporary_scopes` future-incompatibility warning. +#[derive(TyEncodable, TyDecodable, Clone, Copy, Debug, Eq, PartialEq, HashStable)] +pub enum ScopeCompatibility { + /// Marks a scope that was extended past a temporary destruction scope by a non-extending + /// `super let` initializer. + FutureIncompatible { shortens_to: Scope }, + /// Marks an extended temporary scope that was not extended by a non-extending `super let` + /// initializer. + FutureCompatible, } impl ScopeTree { @@ -260,6 +278,11 @@ impl ScopeTree { self.var_map.insert(var, lifetime); } + pub fn record_future_incompatible_var_scope(&mut self, var: hir::ItemLocalId, lifetime: Scope) { + assert!(var != lifetime.local_id); + self.var_compatibility_map.insert(var, lifetime); + } + pub fn record_rvalue_candidate(&mut self, var: HirId, candidate: RvalueCandidate) { debug!("record_rvalue_candidate(var={var:?}, candidate={candidate:?})"); if let Some(lifetime) = &candidate.lifetime { @@ -273,9 +296,14 @@ impl ScopeTree { self.parent_map.get(&id).cloned() } - /// Returns the lifetime of the local variable `var_id`, if any. - pub fn var_scope(&self, var_id: hir::ItemLocalId) -> Option { - self.var_map.get(&var_id).cloned() + /// Returns the lifetime of the local variable `var_id`, if any, as well as whether it is + /// shortening after #145838. + pub fn var_scope(&self, var_id: hir::ItemLocalId) -> (Option, ScopeCompatibility) { + let compat = match self.var_compatibility_map.get(&var_id) { + Some(&shortens_to) => ScopeCompatibility::FutureIncompatible { shortens_to }, + None => ScopeCompatibility::FutureCompatible, + }; + (self.var_map.get(&var_id).cloned(), compat) } /// Returns `true` if `subscope` is equal to or is lexically nested inside `superscope`, and @@ -303,7 +331,10 @@ impl ScopeTree { /// Returns the scope of non-lifetime-extended temporaries within a given scope, as well as /// whether we've recorded a potential backwards-incompatible change to lint on. /// Returns `None` when no enclosing temporary scope is found, such as for static items. - pub fn default_temporary_scope(&self, inner: Scope) -> (Option, Option) { + pub fn default_temporary_scope( + &self, + inner: Scope, + ) -> (Option, Option<(Scope, BackwardIncompatibleDropReason)>) { let mut id = inner; let mut backwards_incompatible = None; @@ -327,8 +358,10 @@ impl ScopeTree { // This is for now only working for cases where a temporary lifetime is // *shortened*. if backwards_incompatible.is_none() { - backwards_incompatible = - self.backwards_incompatible_scope.get(&p.local_id).copied(); + backwards_incompatible = self + .backwards_incompatible_scope + .get(&p.local_id) + .map(|&s| (s, BackwardIncompatibleDropReason::Edition2024)); } id = p } diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 3b8def67f92d7..09a9102086597 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -989,17 +989,21 @@ pub enum TerminatorKind<'tcx> { #[derive( Clone, + Copy, Debug, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, + Eq, TypeFoldable, TypeVisitable )] pub enum BackwardIncompatibleDropReason { Edition2024, + /// Used by the `macro_extended_temporary_scopes` lint. + MacroExtendedScope, } #[derive(Debug, Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)] diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index f1d19800a7891..9fd5ee42fba7e 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -27,7 +27,9 @@ use tracing::instrument; use crate::middle::region; use crate::mir::interpret::AllocId; -use crate::mir::{self, AssignOp, BinOp, BorrowKind, FakeReadCause, UnOp}; +use crate::mir::{ + self, AssignOp, BackwardIncompatibleDropReason, BinOp, BorrowKind, FakeReadCause, UnOp, +}; use crate::thir::visit::for_each_immediate_subpat; use crate::ty::adjustment::PointerCoercion; use crate::ty::layout::IntegerExt; @@ -272,7 +274,7 @@ pub struct TempLifetime { pub temp_lifetime: Option, /// If `Some(lt)`, indicates that the lifetime of this temporary will change to `lt` in a future edition. /// If `None`, then no changes are expected, or lints are disabled. - pub backwards_incompatible: Option, + pub backwards_incompatible: Option<(region::Scope, BackwardIncompatibleDropReason)>, } #[derive(Clone, Debug, HashStable)] @@ -1125,7 +1127,7 @@ mod size_asserts { use super::*; // tidy-alphabetical-start static_assert_size!(Block, 48); - static_assert_size!(Expr<'_>, 72); + static_assert_size!(Expr<'_>, 80); static_assert_size!(ExprKind<'_>, 40); static_assert_size!(Pat<'_>, 64); static_assert_size!(PatKind<'_>, 48); diff --git a/compiler/rustc_middle/src/ty/rvalue_scopes.rs b/compiler/rustc_middle/src/ty/rvalue_scopes.rs index 8b92e48ed1a07..eef8b6bd26ed2 100644 --- a/compiler/rustc_middle/src/ty/rvalue_scopes.rs +++ b/compiler/rustc_middle/src/ty/rvalue_scopes.rs @@ -3,13 +3,14 @@ use rustc_hir::ItemLocalMap; use rustc_macros::{HashStable, TyDecodable, TyEncodable}; use tracing::debug; -use crate::middle::region::{Scope, ScopeData, ScopeTree}; +use crate::middle::region::{ScopeCompatibility, Scope, ScopeData, ScopeTree}; +use crate::mir::BackwardIncompatibleDropReason; /// `RvalueScopes` is a mapping from sub-expressions to _extended_ lifetime as determined by /// rules laid out in `rustc_hir_analysis::check::rvalue_scopes`. #[derive(TyEncodable, TyDecodable, Clone, Debug, Default, Eq, PartialEq, HashStable)] pub struct RvalueScopes { - map: ItemLocalMap>, + map: ItemLocalMap<(Option, Option<(Scope, BackwardIncompatibleDropReason)>)>, } impl RvalueScopes { @@ -24,11 +25,11 @@ impl RvalueScopes { &self, region_scope_tree: &ScopeTree, expr_id: hir::ItemLocalId, - ) -> (Option, Option) { + ) -> (Option, Option<(Scope, BackwardIncompatibleDropReason)>) { // Check for a designated rvalue scope. - if let Some(&s) = self.map.get(&expr_id) { + if let Some(&(s, future_scope)) = self.map.get(&expr_id) { debug!("temporary_scope({expr_id:?}) = {s:?} [custom]"); - return (s, None); + return (s, future_scope); } // Otherwise, locate the innermost terminating scope @@ -40,11 +41,23 @@ impl RvalueScopes { } /// Make an association between a sub-expression and an extended lifetime - pub fn record_rvalue_scope(&mut self, var: hir::ItemLocalId, lifetime: Option) { + pub fn record_rvalue_scope( + &mut self, + var: hir::ItemLocalId, + lifetime: Option, + compat: ScopeCompatibility, + ) { debug!("record_rvalue_scope(var={var:?}, lifetime={lifetime:?})"); if let Some(lifetime) = lifetime { assert!(var != lifetime.local_id); } - self.map.insert(var, lifetime); + let future_scope = + if let ScopeCompatibility::FutureIncompatible { shortens_to } = compat + { + Some((shortens_to, BackwardIncompatibleDropReason::MacroExtendedScope)) + } else { + None + }; + self.map.insert(var, (lifetime, future_scope)); } } diff --git a/compiler/rustc_mir_build/src/builder/expr/as_temp.rs b/compiler/rustc_mir_build/src/builder/expr/as_temp.rs index b0ce3527d8b3d..b9f09fadc5a74 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_temp.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_temp.rs @@ -129,8 +129,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Value); } - if let Some(backwards_incompatible) = temp_lifetime.backwards_incompatible { - this.schedule_backwards_incompatible_drop(expr_span, backwards_incompatible, temp); + if let Some((backwards_incompatible, reason)) = temp_lifetime.backwards_incompatible { + this.schedule_backwards_incompatible_drop( + expr_span, + backwards_incompatible, + temp, + reason, + ); } block.and(temp) diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index d216c4ecd1155..9ba2f4db4f03a 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -15,7 +15,7 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::{BindingMode, ByRef, LetStmt, LocalSource, Node}; use rustc_middle::bug; -use rustc_middle::middle::region; +use rustc_middle::middle::region::{self, ScopeCompatibility}; use rustc_middle::mir::*; use rustc_middle::thir::{self, *}; use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty, ValTree, ValTreeKind}; @@ -807,10 +807,19 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.push(block, Statement::new(source_info, StatementKind::StorageLive(local_id))); // Although there is almost always scope for given variable in corner cases // like #92893 we might get variable with no scope. - if let Some(region_scope) = self.region_scope_tree.var_scope(var.0.local_id) - && matches!(schedule_drop, ScheduleDrops::Yes) - { - self.schedule_drop(span, region_scope, local_id, DropKind::Storage); + if matches!(schedule_drop, ScheduleDrops::Yes) { + let (var_scope, var_scope_compat) = self.region_scope_tree.var_scope(var.0.local_id); + if let Some(region_scope) = var_scope { + self.schedule_drop(span, region_scope, local_id, DropKind::Storage); + } + if let ScopeCompatibility::FutureIncompatible { shortens_to } = var_scope_compat { + self.schedule_backwards_incompatible_drop( + span, + shortens_to, + local_id, + BackwardIncompatibleDropReason::MacroExtendedScope, + ); + } } Place::from(local_id) } @@ -822,7 +831,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { for_guard: ForGuard, ) { let local_id = self.var_local_id(var, for_guard); - if let Some(region_scope) = self.region_scope_tree.var_scope(var.0.local_id) { + // We can ignore the var scope's future-compatibility information since we've already taken + // it into account when scheduling the storage drop in `storage_live_binding`. + if let (Some(region_scope), _) = self.region_scope_tree.var_scope(var.0.local_id) { self.schedule_drop(span, region_scope, local_id, DropKind::Value); } } diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index 9665987362202..7fbc005967b8a 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -85,7 +85,7 @@ use std::mem; use interpret::ErrorHandled; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::HirId; +use rustc_hir::{self as hir, HirId}; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::middle::region; use rustc_middle::mir::{self, *}; @@ -166,7 +166,7 @@ struct DropData { pub(crate) enum DropKind { Value, Storage, - ForLint, + ForLint(BackwardIncompatibleDropReason), } #[derive(Debug)] @@ -268,7 +268,7 @@ impl Scope { /// use of optimizations in the MIR coroutine transform. fn needs_cleanup(&self) -> bool { self.drops.iter().any(|drop| match drop.kind { - DropKind::Value | DropKind::ForLint => true, + DropKind::Value | DropKind::ForLint(_) => true, DropKind::Storage => false, }) } @@ -432,12 +432,12 @@ impl DropTree { }; cfg.terminate(block, drop_node.data.source_info, terminator); } - DropKind::ForLint => { + DropKind::ForLint(reason) => { let stmt = Statement::new( drop_node.data.source_info, StatementKind::BackwardIncompatibleDropHint { place: Box::new(drop_node.data.local.into()), - reason: BackwardIncompatibleDropReason::Edition2024, + reason, }, ); cfg.push(block, stmt); @@ -1161,14 +1161,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); block = next; } - DropKind::ForLint => { + DropKind::ForLint(reason) => { self.cfg.push( block, Statement::new( source_info, StatementKind::BackwardIncompatibleDropHint { place: Box::new(local.into()), - reason: BackwardIncompatibleDropReason::Edition2024, + reason, }, ), ); @@ -1395,7 +1395,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { drop_kind: DropKind, ) { let needs_drop = match drop_kind { - DropKind::Value | DropKind::ForLint => { + DropKind::Value | DropKind::ForLint(_) => { if !self.local_decls[local].ty.needs_drop(self.tcx, self.typing_env()) { return; } @@ -1492,6 +1492,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { span: Span, region_scope: region::Scope, local: Local, + reason: BackwardIncompatibleDropReason, ) { // Note that we are *not* gating BIDs here on whether they have significant destructor. // We need to know all of them so that we can capture potential borrow-checking errors. @@ -1499,13 +1500,24 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Since we are inserting linting MIR statement, we have to invalidate the caches scope.invalidate_cache(); if scope.region_scope == region_scope { - let region_scope_span = region_scope.span(self.tcx, self.region_scope_tree); + // We'll be using this span in diagnostics, so let's make sure it points to the end + // end of the block, not just the end of the tail expression. + let region_scope_span = if reason + == BackwardIncompatibleDropReason::MacroExtendedScope + && let Some(scope_hir_id) = region_scope.hir_id(self.region_scope_tree) + && let hir::Node::Expr(expr) = self.tcx.hir_node(scope_hir_id) + && let hir::Node::Block(blk) = self.tcx.parent_hir_node(expr.hir_id) + { + blk.span + } else { + region_scope.span(self.tcx, self.region_scope_tree) + }; let scope_end = self.tcx.sess.source_map().end_point(region_scope_span); scope.drops.push(DropData { source_info: SourceInfo { span: scope_end, scope: scope.source_scope }, local, - kind: DropKind::ForLint, + kind: DropKind::ForLint(reason), }); return; @@ -1902,7 +1914,7 @@ where ); block = next; } - DropKind::ForLint => { + DropKind::ForLint(reason) => { // As in the `DropKind::Storage` case below: // normally lint-related drops are not emitted for unwind, // so we can just leave `unwind_to` unmodified, but in some @@ -1931,7 +1943,7 @@ where source_info, StatementKind::BackwardIncompatibleDropHint { place: Box::new(local.into()), - reason: BackwardIncompatibleDropReason::Edition2024, + reason, }, ), ); @@ -1985,7 +1997,7 @@ impl<'a, 'tcx: 'a> Builder<'a, 'tcx> { let mut unwind_indices = IndexVec::from_elem_n(unwind_target, 1); for (drop_idx, drop_node) in drops.drop_nodes.iter_enumerated().skip(1) { match drop_node.data.kind { - DropKind::Storage | DropKind::ForLint => { + DropKind::Storage | DropKind::ForLint(_) => { if is_coroutine { let unwind_drop = self .scopes @@ -2024,7 +2036,7 @@ impl<'a, 'tcx: 'a> Builder<'a, 'tcx> { .coroutine_drops .add_drop(drop_data.data, dropline_indices[drop_data.next]); match drop_data.data.kind { - DropKind::Storage | DropKind::ForLint => {} + DropKind::Storage | DropKind::ForLint(_) => {} DropKind::Value => { if self.is_async_drop(drop_data.data.local) { self.scopes.coroutine_drops.add_entry_point( diff --git a/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs b/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs index 61c9bbe31239e..58bc154fbdfde 100644 --- a/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs +++ b/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs @@ -13,8 +13,8 @@ use rustc_index::{IndexSlice, IndexVec}; use rustc_macros::{LintDiagnostic, Subdiagnostic}; use rustc_middle::bug; use rustc_middle::mir::{ - self, BasicBlock, Body, ClearCrossCrate, Local, Location, MirDumper, Place, StatementKind, - TerminatorKind, + self, BackwardIncompatibleDropReason, BasicBlock, Body, ClearCrossCrate, Local, Location, + MirDumper, Place, StatementKind, TerminatorKind, }; use rustc_middle::ty::significant_drop_order::{ extract_component_with_significant_dtor, ty_dtor_span, @@ -207,7 +207,11 @@ pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body< let mut ty_dropped_components = UnordMap::default(); for (block, data) in body.basic_blocks.iter_enumerated() { for (statement_index, stmt) in data.statements.iter().enumerate() { - if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } = &stmt.kind { + if let StatementKind::BackwardIncompatibleDropHint { + place, + reason: BackwardIncompatibleDropReason::Edition2024, + } = &stmt.kind + { let ty = place.ty(body, tcx).ty; if ty_dropped_components .entry(ty) diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs index 11edb929d70b2..de162a12e47d3 100644 --- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -64,7 +64,7 @@ pub(super) fn check<'tcx>( if let Some(indexed_extent) = indexed_extent { let parent_def_id = cx.tcx.hir_get_parent_item(expr.hir_id); let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); - let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap(); + let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).0.unwrap(); if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { return; } @@ -298,6 +298,7 @@ impl<'tcx> VarVisitor<'_, 'tcx> { .tcx .region_scope_tree(parent_def_id) .var_scope(hir_id.local_id) + .0 .unwrap(); if index_used_directly { self.indexed_directly.insert( diff --git a/src/tools/clippy/clippy_lints/src/shadow.rs b/src/tools/clippy/clippy_lints/src/shadow.rs index 14399867f3181..ee80e75a9d1f5 100644 --- a/src/tools/clippy/clippy_lints/src/shadow.rs +++ b/src/tools/clippy/clippy_lints/src/shadow.rs @@ -180,8 +180,8 @@ impl<'tcx> LateLintPass<'tcx> for Shadow { fn is_shadow(cx: &LateContext<'_>, owner: LocalDefId, first: ItemLocalId, second: ItemLocalId) -> bool { let scope_tree = cx.tcx.region_scope_tree(owner.to_def_id()); - if let Some(first_scope) = scope_tree.var_scope(first) - && let Some(second_scope) = scope_tree.var_scope(second) + if let Some(first_scope) = scope_tree.var_scope(first).0 + && let Some(second_scope) = scope_tree.var_scope(second).0 { return scope_tree.is_subscope_of(second_scope, first_scope); } diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/auxiliary/external-macros.rs b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/auxiliary/external-macros.rs new file mode 100644 index 0000000000000..74717755ca336 --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/auxiliary/external-macros.rs @@ -0,0 +1,12 @@ +//! The macros that are in `../user-defined-macros.rs`, but external to test diagnostics. +//@ edition: 2024 + +#[macro_export] +macro_rules! wrap { + ($arg:expr) => { { &$arg } } +} + +#[macro_export] +macro_rules! print_with_internal_wrap { + () => { println!("{:?}{}", (), $crate::wrap!(String::new())) } +} diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.rs b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.rs new file mode 100644 index 0000000000000..c3f407633dcb8 --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.rs @@ -0,0 +1,12 @@ +//! Some temporaries are implemented as local variables bound with `super let`. These can be +//! lifetime-extended, and as such are subject to shortening after #145838. +//@ edition: 2024 +//@ check-pass + +fn main() { + // The `()` argument to the inner `format_args!` is promoted, but the lifetimes of the internal + // `super let` temporaries in its expansion shorten, making this an error in Rust 1.92. + println!("{:?}{}", (), { format_args!("{:?}", ()) }); + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +} diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.stderr b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.stderr new file mode 100644 index 0000000000000..4dee6f4fc3212 --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/extended-super-let-bindings.stderr @@ -0,0 +1,15 @@ +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/extended-super-let-bindings.rs:9:30 + | +LL | println!("{:?}{}", (), { format_args!("{:?}", ()) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + = note: `#[warn(macro_extended_temporary_scopes)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2021.stderr b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2021.stderr new file mode 100644 index 0000000000000..c7a7e1b3238e9 --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2021.stderr @@ -0,0 +1,92 @@ +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:33:43 + | +LL | println!("{:?}{:?}", (), if cond() { &format!("") } else { "" }); + | ^^^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + = note: `#[warn(macro_extended_temporary_scopes)]` (part of `#[warn(future_incompatible)]`) on by default + = note: this warning originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:36:43 + | +LL | println!("{:?}{:?}", (), if cond() { &"".to_string() } else { "" }); + | ^^^^^^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:39:43 + | +LL | println!("{:?}{:?}", (), if cond() { &("string".to_owned() + "string") } else { "" }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:58:17 + | +LL | &*&*smart_ptr_temp() + | ^^^^^^^^^^^^^^^^ this expression creates a temporary value... +... +LL | } + | - ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:54:14 + | +LL | &struct_temp().field + | ^^^^^^^^^^^^^ this expression creates a temporary value... +... +LL | } else { + | - ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:50:14 + | +LL | &tuple_temp().0 + | ^^^^^^^^^^^^ this expression creates a temporary value... +... +LL | } else if cond() { + | - ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:46:14 + | +LL | &array_temp()[0] + | ^^^^^^^^^^^^ this expression creates a temporary value... +... +LL | } else if cond() { + | - ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: 7 warnings emitted + diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2024.stderr b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2024.stderr new file mode 100644 index 0000000000000..0c61fd8d1b0e1 --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.e2024.stderr @@ -0,0 +1,188 @@ +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:26:29 + | +LL | println!("{:?}{:?}", { &temp() }, ()); + | ^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + = note: `#[warn(macro_extended_temporary_scopes)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:33:43 + | +LL | println!("{:?}{:?}", (), if cond() { &format!("") } else { "" }); + | ^^^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + = note: this warning originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:36:43 + | +LL | println!("{:?}{:?}", (), if cond() { &"".to_string() } else { "" }); + | ^^^^^^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:39:43 + | +LL | println!("{:?}{:?}", (), if cond() { &("string".to_owned() + "string") } else { "" }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:58:17 + | +LL | &*&*smart_ptr_temp() + | ^^^^^^^^^^^^^^^^ this expression creates a temporary value... +... +LL | } + | - ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:54:14 + | +LL | &struct_temp().field + | ^^^^^^^^^^^^^ this expression creates a temporary value... +... +LL | } else { + | - ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:50:14 + | +LL | &tuple_temp().0 + | ^^^^^^^^^^^^ this expression creates a temporary value... +... +LL | } else if cond() { + | - ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:46:14 + | +LL | &array_temp()[0] + | ^^^^^^^^^^^^ this expression creates a temporary value... +... +LL | } else if cond() { + | - ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:65:18 + | +LL | pin!(pin!({ &temp() })); + | ^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:96:13 + | +LL | pin!({ &(1 / 0) }); + | ^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:99:17 + | +LL | pin!({ &mut [()] }); + | ^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:102:13 + | +LL | pin!({ &Some(String::new()) }); + | ^^^^^^^^^^^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:105:13 + | +LL | pin!({ &(|| ())() }); + | ^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:108:13 + | +LL | pin!({ &|| &local }); + | ^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/macro-extended-temporary-scopes.rs:111:13 + | +LL | pin!({ &CONST_STRING }); + | ^^^^^^^^^^^^ - ...which will be dropped at the end of this block in Rust 1.92 + | | + | this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: 15 warnings emitted + diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.rs b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.rs new file mode 100644 index 0000000000000..b6ffa5247e30c --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/macro-extended-temporary-scopes.rs @@ -0,0 +1,117 @@ +//! Future-compatibility warning test for #145838: make sure we catch all expected breakage. +//! Shortening temporaries in the tails of block expressions should warn in Rust 2024, and +//! shortening temporaries in the tails of `if` expressions' blocks should warn in all editions. +//@ revisions: e2021 e2024 +//@ [e2021] edition: 2021 +//@ [e2024] edition: 2024 +//@ check-pass +use std::pin::pin; + +struct Struct { field: () } + +fn cond() -> bool { true } +fn temp() {} +fn array_temp() -> [(); 1] { [()] } +fn tuple_temp() -> ((),) { ((),) } +fn struct_temp() -> Struct { Struct { field: () } } +fn smart_ptr_temp() -> Box<()> { Box::new(()) } + +const CONST_STRING: String = String::new(); +static STATIC_UNIT: () = (); + +fn main() { + let local = String::new(); + + // #145880 doesn't apply here, so this `temp()`'s lifetime is reduced by #145838 in Rust 2024. + println!("{:?}{:?}", { &temp() }, ()); + //[e2024]~^ WARN temporary lifetime will be shortened in Rust 1.92 + //[e2024]~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + + // In real-world projects, this breakage typically appeared in `if` expressions with a reference + // to a `String` temporary in one branch's tail expression. This is edition-independent since + // `if` expressions' blocks are temporary scopes in all editions. + println!("{:?}{:?}", (), if cond() { &format!("") } else { "" }); + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + println!("{:?}{:?}", (), if cond() { &"".to_string() } else { "" }); + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + println!("{:?}{:?}", (), if cond() { &("string".to_owned() + "string") } else { "" }); + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + + // Make sure we catch indexed and dereferenced temporaries. + pin!( + if cond() { + &array_temp()[0] + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + } else if cond() { + &tuple_temp().0 + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + } else if cond() { + &struct_temp().field + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + } else { + &*&*smart_ptr_temp() + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + } + ); + + // Test that `super let` extended by parent `super let`s in non-extending blocks are caught. + pin!(pin!({ &temp() })); + //[e2024]~^ WARN temporary lifetime will be shortened in Rust 1.92 + //[e2024]~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + + // We shouldn't warn when lifetime extension applies. + let _ = format_args!("{:?}{:?}", { &temp() }, if cond() { &temp() } else { &temp() }); + let _ = pin!( + if cond() { + &array_temp()[0] + } else if cond() { + &tuple_temp().0 + } else if cond() { + &struct_temp().field + } else { + &*&*smart_ptr_temp() + } + ); + let _ = pin!(pin!({ &temp() })); + + // We shouldn't warn when borrowing from non-temporary places. + pin!({ &local }); + pin!({ &STATIC_UNIT }); + + // We shouldn't warn for promoted constants. + pin!({ &size_of::<()>() }); + pin!({ &(1 / 1) }); + pin!({ &mut ([] as [(); 0]) }); + pin!({ &None:: }); + pin!({ &|| String::new() }); + + // But we do warn on these temporaries, since they aren't promoted. + pin!({ &(1 / 0) }); + //[e2024]~^ WARN temporary lifetime will be shortened in Rust 1.92 + //[e2024]~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + pin!({ &mut [()] }); + //[e2024]~^ WARN temporary lifetime will be shortened in Rust 1.92 + //[e2024]~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + pin!({ &Some(String::new()) }); + //[e2024]~^ WARN temporary lifetime will be shortened in Rust 1.92 + //[e2024]~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + pin!({ &(|| ())() }); + //[e2024]~^ WARN temporary lifetime will be shortened in Rust 1.92 + //[e2024]~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + pin!({ &|| &local }); + //[e2024]~^ WARN temporary lifetime will be shortened in Rust 1.92 + //[e2024]~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + pin!({ &CONST_STRING }); + //[e2024]~^ WARN temporary lifetime will be shortened in Rust 1.92 + //[e2024]~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + + // This lint only catches future errors. Future dangling pointers do not produce warnings. + pin!({ &raw const *&temp() }); +} diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/non-extended.rs b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/non-extended.rs new file mode 100644 index 0000000000000..ba84489bab23e --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/non-extended.rs @@ -0,0 +1,15 @@ +//! Test that `macro_extended_temporary_scopes` doesn't warn on non-extended temporaries. +//@ edition: 2024 +#![deny(macro_extended_temporary_scopes)] + +fn temp() {} + +fn main() { + // Due to #145880, this argument isn't an extending context. + println!("{:?}", { &temp() }); + //~^ ERROR temporary value dropped while borrowed + + // Subexpressions of function call expressions are not extending. + println!("{:?}{:?}", (), { std::convert::identity(&temp()) }); + //~^ ERROR temporary value dropped while borrowed +} diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/non-extended.stderr b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/non-extended.stderr new file mode 100644 index 0000000000000..9e981874651f9 --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/non-extended.stderr @@ -0,0 +1,27 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/non-extended.rs:9:25 + | +LL | println!("{:?}", { &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/non-extended.rs:13:56 + | +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: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.rs b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.rs new file mode 100644 index 0000000000000..6290b0eab8dac --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.rs @@ -0,0 +1,55 @@ +//! Test that the future-compatibility warning for #145838 doesn't break in the presence of +//! user-defined macros. +//@ build-pass +//@ edition: 2024 +//@ aux-build:external-macros.rs +//@ dont-require-annotations: NOTE + +extern crate external_macros; + +macro_rules! wrap { + ($arg:expr) => { { &$arg } } +} + +macro_rules! print_with_internal_wrap { + () => { println!("{:?}{}", (), wrap!(String::new())) } + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +} + +fn main() { + print!( + "{:?}{}", + (), + format_args!( + "{:?}{:?}", + + // This is promoted; do not warn. + wrap!(None::), + + // This does not promote; warn. + wrap!(String::new()) + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + ) + ); + + print_with_internal_wrap!(); + //~^ NOTE in this expansion of print_with_internal_wrap! + + print!( + "{:?}{:?}", + + // This is promoted; do not warn. + external_macros::wrap!(None::), + + // This does not promote; warn. + external_macros::wrap!(String::new()) + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + ); + + external_macros::print_with_internal_wrap!(); + //~^ WARN temporary lifetime will be shortened in Rust 1.92 + //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +} diff --git a/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.stderr b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.stderr new file mode 100644 index 0000000000000..b4996952f4f11 --- /dev/null +++ b/tests/ui/lifetimes/lint-macro-extended-temporary-scopes/user-defined-macros.stderr @@ -0,0 +1,60 @@ +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/user-defined-macros.rs:31:19 + | +LL | ($arg:expr) => { { &$arg } } + | - ...which will be dropped at the end of this block in Rust 1.92 +... +LL | wrap!(String::new()) + | ^^^^^^^^^^^^^ this expression creates a temporary value... + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + = note: `#[warn(macro_extended_temporary_scopes)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/user-defined-macros.rs:15:42 + | +LL | ($arg:expr) => { { &$arg } } + | - ...which will be dropped at the end of this block in Rust 1.92 +... +LL | () => { println!("{:?}{}", (), wrap!(String::new())) } + | ^^^^^^^^^^^^^ this expression creates a temporary value... +... +LL | print_with_internal_wrap!(); + | --------------------------- in this macro invocation + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + = note: this warning originates in the macro `print_with_internal_wrap` (in Nightly builds, run with -Z macro-backtrace for more info) + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/user-defined-macros.rs:47:32 + | +LL | external_macros::wrap!(String::new()) + | -----------------------^^^^^^^^^^^^^- + | | | + | | this expression creates a temporary value... + | ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + +warning: temporary lifetime will be shortened in Rust 1.92 + --> $DIR/user-defined-macros.rs:52:5 + | +LL | external_macros::print_with_internal_wrap!(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | this expression creates a temporary value... + | ...which will be dropped at the end of this block in Rust 1.92 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see + = note: consider using a `let` binding to create a longer lived value + = note: this warning originates in the macro `external_macros::print_with_internal_wrap` (in Nightly builds, run with -Z macro-backtrace for more info) + +warning: 4 warnings emitted +