Skip to content

Commit 1dc4b0d

Browse files
committed
warn on future errors from temporary lifetimes shortening in Rust 1.92
1 parent f1fbf1f commit 1dc4b0d

File tree

19 files changed

+713
-121
lines changed

19 files changed

+713
-121
lines changed

compiler/rustc_borrowck/src/diagnostics/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use rustc_middle::ty::print::Print;
2121
use rustc_middle::ty::{self, Ty, TyCtxt};
2222
use rustc_middle::{bug, span_bug};
2323
use rustc_mir_dataflow::move_paths::{InitLocation, LookupResult, MoveOutIndex};
24+
use rustc_session::lint::builtin::MACRO_EXTENDED_TEMPORARY_SCOPES;
2425
use rustc_span::def_id::LocalDefId;
2526
use rustc_span::source_map::Spanned;
2627
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
@@ -1580,4 +1581,32 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
15801581
pub(crate) fn local_excluded_from_unused_mut_lint(&self, index: Local) -> bool {
15811582
self.local_name(index).is_none_or(|name| name.as_str().starts_with('_'))
15821583
}
1584+
1585+
/// Report a temporary whose scope will shorten in Rust 1.92 due to #145838.
1586+
pub(crate) fn lint_macro_extended_temporary_scope(
1587+
&self,
1588+
future_drop: Location,
1589+
borrow: &BorrowData<'tcx>,
1590+
) {
1591+
let tcx = self.infcx.tcx;
1592+
let temp_decl = &self.body.local_decls[borrow.borrowed_place.local];
1593+
let temp_span = temp_decl.source_info.span;
1594+
let lint_root = self.body.source_scopes[temp_decl.source_info.scope]
1595+
.local_data
1596+
.as_ref()
1597+
.unwrap_crate_local()
1598+
.lint_root;
1599+
1600+
let mut labels = MultiSpan::from_span(temp_span);
1601+
labels.push_span_label(temp_span, "this expression creates a temporary value...");
1602+
labels.push_span_label(
1603+
self.body.source_info(future_drop).span,
1604+
"...which will be dropped at the end of this block in Rust 1.92",
1605+
);
1606+
1607+
tcx.node_span_lint(MACRO_EXTENDED_TEMPORARY_SCOPES, lint_root, labels, |diag| {
1608+
diag.primary_message("temporary lifetime shortening in Rust 1.92");
1609+
diag.note("consider using a `let` binding to create a longer lived value");
1610+
});
1611+
}
15831612
}

compiler/rustc_borrowck/src/lib.rs

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -843,8 +843,8 @@ impl<'a, 'tcx> ResultsVisitor<'tcx, Borrowck<'a, 'tcx>> for MirBorrowckCtxt<'a,
843843
| StatementKind::ConstEvalCounter
844844
| StatementKind::StorageLive(..) => {}
845845
// This does not affect borrowck
846-
StatementKind::BackwardIncompatibleDropHint { place, reason: BackwardIncompatibleDropReason::Edition2024 } => {
847-
self.check_backward_incompatible_drop(location, **place, state);
846+
StatementKind::BackwardIncompatibleDropHint { place, reason } => {
847+
self.check_backward_incompatible_drop(location, **place, state, *reason);
848848
}
849849
StatementKind::StorageDead(local) => {
850850
self.access_place(
@@ -1386,6 +1386,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
13861386
location: Location,
13871387
place: Place<'tcx>,
13881388
state: &BorrowckDomain,
1389+
reason: BackwardIncompatibleDropReason,
13891390
) {
13901391
let tcx = self.infcx.tcx;
13911392
// If this type does not need `Drop`, then treat it like a `StorageDead`.
@@ -1412,21 +1413,29 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
14121413
if matches!(borrow.kind, BorrowKind::Fake(_)) {
14131414
return ControlFlow::Continue(());
14141415
}
1415-
let borrowed = this.retrieve_borrow_spans(borrow).var_or_use_path_span();
1416-
let explain = this.explain_why_borrow_contains_point(
1417-
location,
1418-
borrow,
1419-
Some((WriteKind::StorageDeadOrDrop, place)),
1420-
);
1421-
this.infcx.tcx.node_span_lint(
1422-
TAIL_EXPR_DROP_ORDER,
1423-
CRATE_HIR_ID,
1424-
borrowed,
1425-
|diag| {
1426-
session_diagnostics::TailExprDropOrder { borrowed }.decorate_lint(diag);
1427-
explain.add_explanation_to_diagnostic(&this, diag, "", None, None);
1428-
},
1429-
);
1416+
match reason {
1417+
BackwardIncompatibleDropReason::Edition2024 => {
1418+
let borrowed = this.retrieve_borrow_spans(borrow).var_or_use_path_span();
1419+
let explain = this.explain_why_borrow_contains_point(
1420+
location,
1421+
borrow,
1422+
Some((WriteKind::StorageDeadOrDrop, place)),
1423+
);
1424+
this.infcx.tcx.node_span_lint(
1425+
TAIL_EXPR_DROP_ORDER,
1426+
CRATE_HIR_ID,
1427+
borrowed,
1428+
|diag| {
1429+
session_diagnostics::TailExprDropOrder { borrowed }
1430+
.decorate_lint(diag);
1431+
explain.add_explanation_to_diagnostic(&this, diag, "", None, None);
1432+
},
1433+
);
1434+
}
1435+
BackwardIncompatibleDropReason::MacroExtendedScope => {
1436+
this.lint_macro_extended_temporary_scope(location, borrow);
1437+
}
1438+
}
14301439
// We may stop at the first case
14311440
ControlFlow::Break(())
14321441
},

compiler/rustc_hir_analysis/src/check/region.rs

Lines changed: 128 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,20 @@ struct ScopeResolutionVisitor<'tcx> {
3838

3939
cx: Context,
4040

41-
extended_super_lets: FxHashMap<hir::ItemLocalId, Option<Scope>>,
41+
extended_super_lets: FxHashMap<hir::ItemLocalId, ExtendedTemporaryScope>,
42+
}
43+
44+
#[derive(Copy, Clone)]
45+
struct ExtendedTemporaryScope {
46+
/// The scope of extended temporaries.
47+
scope: Option<Scope>,
48+
/// Whether this lifetime originated from a regular `let` or a `super let` initializer. In the
49+
/// latter case, this scope may shorten after #145838 if applied to temporaries within block
50+
/// tail expressions.
51+
let_kind: LetKind,
52+
/// Whether this scope will shorten after #145838. If this is applied to a temporary value,
53+
/// we'll emit the `macro_extended_temporary_scopes` lint.
54+
compat: ExtendedTemporaryScopeCompatibility,
4255
}
4356

4457
/// Records the lifetime of a local variable as `cx.var_parent`
@@ -467,37 +480,55 @@ fn resolve_local<'tcx>(
467480
// A, but the inner rvalues `a()` and `b()` have an extended lifetime
468481
// due to rule C.
469482

470-
if let_kind == LetKind::Super {
471-
if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) {
472-
// This expression was lifetime-extended by a parent let binding. E.g.
473-
//
474-
// let a = {
475-
// super let b = temp();
476-
// &b
477-
// };
478-
//
479-
// (Which needs to behave exactly as: let a = &temp();)
480-
//
481-
// Processing of `let a` will have already decided to extend the lifetime of this
482-
// `super let` to its own var_scope. We use that scope.
483-
visitor.cx.var_parent = scope;
484-
} else {
485-
// This `super let` is not subject to lifetime extension from a parent let binding. E.g.
486-
//
487-
// identity({ super let x = temp(); &x }).method();
488-
//
489-
// (Which needs to behave exactly as: identity(&temp()).method();)
490-
//
491-
// Iterate up to the enclosing destruction scope to find the same scope that will also
492-
// be used for the result of the block itself.
493-
if let Some(inner_scope) = visitor.cx.var_parent {
494-
(visitor.cx.var_parent, _) = visitor.scope_tree.default_temporary_scope(inner_scope)
483+
let (source_let_kind, compat) = match let_kind {
484+
// Normal `let` initializers are unaffected by #145838.
485+
LetKind::Regular => {
486+
(LetKind::Regular, ExtendedTemporaryScopeCompatibility::FutureCompatible)
487+
}
488+
LetKind::Super => {
489+
if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) {
490+
// This expression was lifetime-extended by a parent let binding. E.g.
491+
//
492+
// let a = {
493+
// super let b = temp();
494+
// &b
495+
// };
496+
//
497+
// (Which needs to behave exactly as: let a = &temp();)
498+
//
499+
// Processing of `let a` will have already decided to extend the lifetime of this
500+
// `super let` to its own var_scope. We use that scope.
501+
visitor.cx.var_parent = scope.scope;
502+
// Inherit compatibility from the original `let` statement. If the original `let`
503+
// was regular, lifetime extension should apply as normal. If the original `let` was
504+
// `super`, blocks within the initializer will be affected by #145838.
505+
(scope.let_kind, scope.compat)
506+
} else {
507+
// This `super let` is not subject to lifetime extension from a parent let binding. E.g.
508+
//
509+
// identity({ super let x = temp(); &x }).method();
510+
//
511+
// (Which needs to behave exactly as: identity(&temp()).method();)
512+
//
513+
// Iterate up to the enclosing destruction scope to find the same scope that will also
514+
// be used for the result of the block itself.
515+
if let Some(inner_scope) = visitor.cx.var_parent {
516+
(visitor.cx.var_parent, _) =
517+
visitor.scope_tree.default_temporary_scope(inner_scope)
518+
}
519+
// Blocks within the initializer will be affected by #145838.
520+
(LetKind::Super, ExtendedTemporaryScopeCompatibility::FutureCompatible)
495521
}
496522
}
497-
}
523+
};
498524

499525
if let Some(expr) = init {
500-
record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent);
526+
let scope = ExtendedTemporaryScope {
527+
scope: visitor.cx.var_parent,
528+
let_kind: source_let_kind,
529+
compat,
530+
};
531+
record_rvalue_scope_if_borrow_expr(visitor, expr, scope);
501532

502533
if let Some(pat) = pat {
503534
if is_binding_pat(pat) {
@@ -506,6 +537,7 @@ fn resolve_local<'tcx>(
506537
RvalueCandidate {
507538
target: expr.hir_id.local_id,
508539
lifetime: visitor.cx.var_parent,
540+
compat: ExtendedTemporaryScopeCompatibility::FutureCompatible,
509541
},
510542
);
511543
}
@@ -607,50 +639,106 @@ fn resolve_local<'tcx>(
607639
fn record_rvalue_scope_if_borrow_expr<'tcx>(
608640
visitor: &mut ScopeResolutionVisitor<'tcx>,
609641
expr: &hir::Expr<'_>,
610-
blk_id: Option<Scope>,
642+
scope: ExtendedTemporaryScope,
611643
) {
612644
match expr.kind {
613645
hir::ExprKind::AddrOf(_, _, subexpr) => {
614-
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
646+
record_rvalue_scope_if_borrow_expr(visitor, subexpr, scope);
615647
visitor.scope_tree.record_rvalue_candidate(
616648
subexpr.hir_id,
617-
RvalueCandidate { target: subexpr.hir_id.local_id, lifetime: blk_id },
649+
RvalueCandidate {
650+
target: subexpr.hir_id.local_id,
651+
lifetime: scope.scope,
652+
compat: scope.compat,
653+
},
618654
);
619655
}
620656
hir::ExprKind::Struct(_, fields, _) => {
621657
for field in fields {
622-
record_rvalue_scope_if_borrow_expr(visitor, field.expr, blk_id);
658+
record_rvalue_scope_if_borrow_expr(visitor, field.expr, scope);
623659
}
624660
}
625661
hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => {
626662
for subexpr in subexprs {
627-
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
663+
record_rvalue_scope_if_borrow_expr(visitor, subexpr, scope);
628664
}
629665
}
630666
hir::ExprKind::Cast(subexpr, _) => {
631-
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id)
667+
record_rvalue_scope_if_borrow_expr(visitor, subexpr, scope)
632668
}
633669
hir::ExprKind::Block(block, _) => {
634670
if let Some(subexpr) = block.expr {
635-
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
671+
let tail_expr_scope =
672+
if scope.let_kind == LetKind::Super && block.span.at_least_rust_2024() {
673+
// The tail expression will no longer be extending after #145838.
674+
// Since tail expressions are temporary scopes in Rust 2024, lint on
675+
// temporaries that acquire this (longer) lifetime.
676+
ExtendedTemporaryScope {
677+
compat: ExtendedTemporaryScopeCompatibility::FutureIncompatible {
678+
shortens_to: Scope {
679+
local_id: subexpr.hir_id.local_id,
680+
data: ScopeData::Node,
681+
},
682+
},
683+
..scope
684+
}
685+
} else {
686+
// This is extended by a regular `let`, so it won't be changed.
687+
scope
688+
};
689+
record_rvalue_scope_if_borrow_expr(visitor, subexpr, tail_expr_scope);
636690
}
637691
for stmt in block.stmts {
638692
if let hir::StmtKind::Let(local) = stmt.kind
639693
&& let Some(_) = local.super_
640694
{
641-
visitor.extended_super_lets.insert(local.pat.hir_id.local_id, blk_id);
695+
visitor.extended_super_lets.insert(local.pat.hir_id.local_id, scope);
642696
}
643697
}
644698
}
645699
hir::ExprKind::If(_, then_block, else_block) => {
646-
record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id);
700+
let then_scope = if scope.let_kind == LetKind::Super {
701+
// The then and else blocks will no longer be extending after #145838.
702+
// Since `if` blocks are temporary scopes in all editions, lint on temporaries
703+
// that acquire this (longer) lifetime.
704+
ExtendedTemporaryScope {
705+
compat: ExtendedTemporaryScopeCompatibility::FutureIncompatible {
706+
shortens_to: Scope {
707+
local_id: then_block.hir_id.local_id,
708+
data: ScopeData::Node,
709+
},
710+
},
711+
..scope
712+
}
713+
} else {
714+
// This is extended by a regular `let`, so it won't be changed.
715+
scope
716+
};
717+
record_rvalue_scope_if_borrow_expr(visitor, then_block, then_scope);
647718
if let Some(else_block) = else_block {
648-
record_rvalue_scope_if_borrow_expr(visitor, else_block, blk_id);
719+
let else_scope = if scope.let_kind == LetKind::Super {
720+
// The then and else blocks will no longer be extending after #145838.
721+
// Since `if` blocks are temporary scopes in all editions, lint on temporaries
722+
// that acquire this (longer) lifetime.
723+
ExtendedTemporaryScope {
724+
compat: ExtendedTemporaryScopeCompatibility::FutureIncompatible {
725+
shortens_to: Scope {
726+
local_id: else_block.hir_id.local_id,
727+
data: ScopeData::Node,
728+
},
729+
},
730+
..scope
731+
}
732+
} else {
733+
// This is extended by a regular `let`, so it won't be changed.
734+
scope
735+
};
736+
record_rvalue_scope_if_borrow_expr(visitor, else_block, else_scope);
649737
}
650738
}
651739
hir::ExprKind::Match(_, arms, _) => {
652740
for arm in arms {
653-
record_rvalue_scope_if_borrow_expr(visitor, arm.body, blk_id);
741+
record_rvalue_scope_if_borrow_expr(visitor, arm.body, scope);
654742
}
655743
}
656744
hir::ExprKind::Call(func, args) => {
@@ -663,7 +751,7 @@ fn resolve_local<'tcx>(
663751
&& let Res::SelfCtor(_) | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) = path.res
664752
{
665753
for arg in args {
666-
record_rvalue_scope_if_borrow_expr(visitor, arg, blk_id);
754+
record_rvalue_scope_if_borrow_expr(visitor, arg, scope);
667755
}
668756
}
669757
}

compiler/rustc_hir_typeck/src/rvalue_scopes.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use hir::Node;
22
use hir::def_id::DefId;
33
use rustc_hir as hir;
44
use rustc_middle::bug;
5-
use rustc_middle::middle::region::{RvalueCandidate, Scope, ScopeTree};
5+
use rustc_middle::middle::region::{
6+
ExtendedTemporaryScopeCompatibility, RvalueCandidate, Scope, ScopeTree,
7+
};
68
use rustc_middle::ty::RvalueScopes;
79
use tracing::debug;
810

@@ -29,6 +31,7 @@ fn record_rvalue_scope_rec(
2931
rvalue_scopes: &mut RvalueScopes,
3032
mut expr: &hir::Expr<'_>,
3133
lifetime: Option<Scope>,
34+
compat: ExtendedTemporaryScopeCompatibility,
3235
) {
3336
loop {
3437
// Note: give all the expressions matching `ET` with the
@@ -37,7 +40,7 @@ fn record_rvalue_scope_rec(
3740
// into a temporary, we request the temporary scope of the
3841
// outer expression.
3942

40-
rvalue_scopes.record_rvalue_scope(expr.hir_id.local_id, lifetime);
43+
rvalue_scopes.record_rvalue_scope(expr.hir_id.local_id, lifetime, compat);
4144

4245
match expr.kind {
4346
hir::ExprKind::AddrOf(_, _, subexpr)
@@ -58,7 +61,7 @@ fn record_rvalue_scope(
5861
candidate: &RvalueCandidate,
5962
) {
6063
debug!("resolve_rvalue_scope(expr={expr:?}, candidate={candidate:?})");
61-
record_rvalue_scope_rec(rvalue_scopes, expr, candidate.lifetime)
64+
record_rvalue_scope_rec(rvalue_scopes, expr, candidate.lifetime, candidate.compat)
6265
// FIXME(@dingxiangfei2009): handle the candidates in the function call arguments
6366
}
6467

0 commit comments

Comments
 (0)