Auto merge of #147056 - dianne:fcw-super-let-init-borrow-shortening, r=jackh726

[beta-1.91] Warn on future errors from temporary lifetimes shortening in Rust 1.92

Pursuant to [discussion on Zulip](https://rust-lang.zulipchat.com/#narrow/channel/474880-t-compiler.2Fbackports/topic/.23145838.3A.20beta-nominated/near/540530631), this implements a future-compatibility warning lint `macro_extended_temporary_scopes` for errors in Rust 1.92 caused by rust-lang/rust#145838:

```
warning: temporary lifetime shortening 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
   = note: consider using a `let` binding to create a longer lived value
```

Implementation-wise, this reuses the existing temporary scoping FCW machinery introduced for the `tail_expr_drop_order` edition lint: this adds `BackwardIncompatibleDropHint` statements to the MIR at the end of the shortened scopes for affected temporaries; these are then checked in borrowck to warn if the temporary is used after the future drop hint. There are trade-offs here: on one hand, I believe this gives some assurance over ad-hoc pattern-recognition that there are no false positives[^1]. On the other hand, this fails to lint on future dangling raw pointers and it complicates the potential addition of explanatory diagnostics or suggestions[^2]. I'm hopeful that the limitation around dangling pointers won't be relevant in real code, though; the only real instance we've seen of breakage so far is future errors in formatting macro invocations, which this should be able to catch.

Release logistics notes:
- This PR targets the beta branch directly, since the breakage it's a FCW for is landing in the next Rust version.
- rust-lang/rust#146098 undoes the breakage this is a FCW for. If that behavior is merged and stabilizes in Rust 1.92, this PR should be reverted (or shouldn't be merged) in order to avoid spurious warnings.

cc `@traviscross`

`@rustbot` label +T-lang

[^1]: In particular, more syntactic approaches are complicated by having to avoid warning on promoted constants; they'd either be full of holes, they'd need a lot of extra logic, or they'd need to hack more MIR-to-HIR mapping into `PromoteTemps`.
[^2]: It's definitely possible to add more context and a suggestion, but the ways I've thought of to do so are either too hacky or too complex to feel appropriate for a last-minute direct-to-beta lint.
This commit is contained in:
bors
2025-10-15 01:35:47 +00:00
26 changed files with 986 additions and 119 deletions
@@ -21,6 +21,7 @@
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 @@ fn explain_captures(
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");
});
}
}
+26 -17
View File
@@ -843,8 +843,8 @@ fn visit_after_early_statement_effect(
| 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 @@ fn check_backward_incompatible_drop(
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 @@ fn check_backward_incompatible_drop(
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(())
},
+149 -53
View File
@@ -24,7 +24,7 @@
#[derive(Debug, Copy, Clone)]
struct Context {
/// The scope that contains any new variables declared.
var_parent: Option<Scope>,
var_parent: (Option<Scope>, ScopeCompatibility),
/// Region parent of expressions, etc.
parent: Option<Scope>,
@@ -38,12 +38,26 @@ struct ScopeResolutionVisitor<'tcx> {
cx: Context,
extended_super_lets: FxHashMap<hir::ItemLocalId, Option<Scope>>,
extended_super_lets: FxHashMap<hir::ItemLocalId, ExtendedTemporaryScope>,
}
#[derive(Copy, Clone)]
struct ExtendedTemporaryScope {
/// The scope of extended temporaries.
scope: Option<Scope>,
/// 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 is_binding_pat(pat: &hir::Pat<'_>) -> bool {
fn record_rvalue_scope_if_borrow_expr<'tcx>(
visitor: &mut ScopeResolutionVisitor<'tcx>,
expr: &hir::Expr<'_>,
blk_id: Option<Scope>,
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 record_rvalue_scope_if_borrow_expr<'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 @@ fn visit_body(&mut self, body: &hir::Body<'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 @@ fn visit_body(&mut self, body: &hir::Body<'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(),
};
@@ -441,8 +441,8 @@ impl<'a, 'tcx> LetVisitor<'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;
@@ -2,7 +2,9 @@
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<Scope>,
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
}
+52
View File
@@ -62,6 +62,7 @@
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 @@
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: "<https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>",
};
}
+39 -6
View File
@@ -16,6 +16,7 @@
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<hir::ItemLocalId, Scope>,
/// Maps from bindings to their future scopes after #145838 for the
/// `macro_extended_temporary_scopes` lint.
var_compatibility_map: FxIndexMap<hir::ItemLocalId, Scope>,
/// 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<Scope>,
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 @@ pub fn record_var_scope(&mut self, var: hir::ItemLocalId, lifetime: Scope) {
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 @@ pub fn opt_encl_scope(&self, id: Scope) -> Option<Scope> {
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<Scope> {
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<Scope>, 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 @@ pub fn is_subscope_of(&self, subscope: Scope, superscope: Scope) -> bool {
/// 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<Scope>, Option<Scope>) {
pub fn default_temporary_scope(
&self,
inner: Scope,
) -> (Option<Scope>, Option<(Scope, BackwardIncompatibleDropReason)>) {
let mut id = inner;
let mut backwards_incompatible = None;
@@ -327,8 +358,10 @@ pub fn default_temporary_scope(&self, inner: Scope) -> (Option<Scope>, Option<Sc
// 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
}
+4
View File
@@ -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)]
+5 -3
View File
@@ -27,7 +27,9 @@
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<region::Scope>,
/// 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<region::Scope>,
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);
+20 -7
View File
@@ -3,13 +3,14 @@
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<Option<Scope>>,
map: ItemLocalMap<(Option<Scope>, Option<(Scope, BackwardIncompatibleDropReason)>)>,
}
impl RvalueScopes {
@@ -24,11 +25,11 @@ pub fn temporary_scope(
&self,
region_scope_tree: &ScopeTree,
expr_id: hir::ItemLocalId,
) -> (Option<Scope>, Option<Scope>) {
) -> (Option<Scope>, 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 @@ pub fn temporary_scope(
}
/// Make an association between a sub-expression and an extended lifetime
pub fn record_rvalue_scope(&mut self, var: hir::ItemLocalId, lifetime: Option<Scope>) {
pub fn record_rvalue_scope(
&mut self,
var: hir::ItemLocalId,
lifetime: Option<Scope>,
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));
}
}
@@ -129,8 +129,13 @@ fn as_temp_inner(
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)
@@ -15,7 +15,7 @@
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 @@ pub(crate) fn storage_live_binding(
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 @@ pub(crate) fn schedule_drop_for_binding(
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);
}
}
+26 -14
View File
@@ -85,7 +85,7 @@
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 @@ fn link_blocks<'tcx>(
};
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 @@ pub(crate) fn break_for_tail_call(
);
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 @@ pub(crate) fn schedule_drop(
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 @@ pub(crate) fn schedule_backwards_incompatible_drop(
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 @@ pub(crate) fn schedule_backwards_incompatible_drop(
// 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 @@ fn build_scope_drops<'tcx, F>(
);
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 @@ fn build_scope_drops<'tcx, F>(
source_info,
StatementKind::BackwardIncompatibleDropHint {
place: Box::new(local.into()),
reason: BackwardIncompatibleDropReason::Edition2024,
reason,
},
),
);
@@ -1985,7 +1997,7 @@ fn build_exit_tree(
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 @@ fn build_exit_tree(
.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(
@@ -13,8 +13,8 @@
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)
@@ -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 @@ fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Ex
.tcx
.region_scope_tree(parent_def_id)
.var_scope(hir_id.local_id)
.0
.unwrap();
if index_used_directly {
self.indexed_directly.insert(
+2 -2
View File
@@ -180,8 +180,8 @@ fn check_body_post(&mut self, cx: &LateContext<'_>, body: &Body<'_>) {
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);
}
@@ -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())) }
}
@@ -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!
}
@@ -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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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
@@ -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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= note: consider using a `let` binding to create a longer lived value
warning: 7 warnings emitted
@@ -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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= note: consider using a `let` binding to create a longer lived value
warning: 15 warnings emitted
@@ -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::<String> });
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() });
}
@@ -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
}
@@ -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`.
@@ -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::<String>),
// 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::<String>),
// 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!
}
@@ -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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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 <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes>
= 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