mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
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:
@@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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>",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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())) }
|
||||
}
|
||||
+12
@@ -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!
|
||||
}
|
||||
+15
@@ -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
|
||||
|
||||
+92
@@ -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
|
||||
|
||||
+188
@@ -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
|
||||
|
||||
+117
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user