Skip to content

Commit a5efa01

Browse files
committed
Auto merge of rust-lang#107251 - dingxiangfei2009:let-chain-rescope, r=jieyouxu
Rescope temp lifetime in if-let into IfElse with migration lint Tracking issue rust-lang#124085 This PR shortens the temporary lifetime to cover only the pattern matching and consequent branch of a `if let`. At the expression location, means that the lifetime is shortened from previously the deepest enclosing block or statement in Edition 2021. This warrants an Edition change. Coming with the Edition change, this patch also implements an edition lint to warn about the change and a safe rewrite suggestion to preserve the 2021 semantics in most cases. Related to rust-lang#103108. Related crater runs: rust-lang#129466.
2 parents d3a8524 + b4b2b35 commit a5efa01

29 files changed

+1393
-35
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+21-8
Original file line numberDiff line numberDiff line change
@@ -1999,19 +1999,32 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
19991999
) {
20002000
let used_in_call = matches!(
20012001
explanation,
2002-
BorrowExplanation::UsedLater(LaterUseKind::Call | LaterUseKind::Other, _call_span, _)
2002+
BorrowExplanation::UsedLater(
2003+
_,
2004+
LaterUseKind::Call | LaterUseKind::Other,
2005+
_call_span,
2006+
_
2007+
)
20032008
);
20042009
if !used_in_call {
20052010
debug!("not later used in call");
20062011
return;
20072012
}
2013+
if matches!(
2014+
self.body.local_decls[issued_borrow.borrowed_place.local].local_info(),
2015+
LocalInfo::IfThenRescopeTemp { .. }
2016+
) {
2017+
// A better suggestion will be issued by the `if_let_rescope` lint
2018+
return;
2019+
}
20082020

2009-
let use_span =
2010-
if let BorrowExplanation::UsedLater(LaterUseKind::Other, use_span, _) = explanation {
2011-
Some(use_span)
2012-
} else {
2013-
None
2014-
};
2021+
let use_span = if let BorrowExplanation::UsedLater(_, LaterUseKind::Other, use_span, _) =
2022+
explanation
2023+
{
2024+
Some(use_span)
2025+
} else {
2026+
None
2027+
};
20152028

20162029
let outer_call_loc =
20172030
if let TwoPhaseActivation::ActivatedAt(loc) = issued_borrow.activation_location {
@@ -2859,7 +2872,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
28592872
// and `move` will not help here.
28602873
(
28612874
Some(name),
2862-
BorrowExplanation::UsedLater(LaterUseKind::ClosureCapture, var_or_use_span, _),
2875+
BorrowExplanation::UsedLater(_, LaterUseKind::ClosureCapture, var_or_use_span, _),
28632876
) if borrow_spans.for_coroutine() || borrow_spans.for_closure() => self
28642877
.report_escaping_closure_capture(
28652878
borrow_spans,

compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs

+101-9
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use crate::{MirBorrowckCtxt, WriteKind};
3030

3131
#[derive(Debug)]
3232
pub(crate) enum BorrowExplanation<'tcx> {
33-
UsedLater(LaterUseKind, Span, Option<Span>),
33+
UsedLater(Local, LaterUseKind, Span, Option<Span>),
3434
UsedLaterInLoop(LaterUseKind, Span, Option<Span>),
3535
UsedLaterWhenDropped {
3636
drop_loc: Location,
@@ -99,17 +99,39 @@ impl<'tcx> BorrowExplanation<'tcx> {
9999
}
100100
}
101101
match *self {
102-
BorrowExplanation::UsedLater(later_use_kind, var_or_use_span, path_span) => {
102+
BorrowExplanation::UsedLater(
103+
dropped_local,
104+
later_use_kind,
105+
var_or_use_span,
106+
path_span,
107+
) => {
103108
let message = match later_use_kind {
104109
LaterUseKind::TraitCapture => "captured here by trait object",
105110
LaterUseKind::ClosureCapture => "captured here by closure",
106111
LaterUseKind::Call => "used by call",
107112
LaterUseKind::FakeLetRead => "stored here",
108113
LaterUseKind::Other => "used here",
109114
};
110-
// We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
111-
if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) {
112-
if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) {
115+
let local_decl = &body.local_decls[dropped_local];
116+
117+
if let &LocalInfo::IfThenRescopeTemp { if_then } = local_decl.local_info()
118+
&& let Some((_, hir::Node::Expr(expr))) = tcx.hir().parent_iter(if_then).next()
119+
&& let hir::ExprKind::If(cond, conseq, alt) = expr.kind
120+
&& let hir::ExprKind::Let(&hir::LetExpr {
121+
span: _,
122+
pat,
123+
init,
124+
// FIXME(#101728): enable rewrite when type ascription is stabilized again
125+
ty: None,
126+
recovered: _,
127+
}) = cond.kind
128+
&& pat.span.can_be_used_for_suggestions()
129+
&& let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
130+
{
131+
suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
132+
} else if path_span.map_or(true, |path_span| path_span == var_or_use_span) {
133+
// We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
134+
if borrow_span.map_or(true, |sp| !sp.overlaps(var_or_use_span)) {
113135
err.span_label(
114136
var_or_use_span,
115137
format!("{borrow_desc}borrow later {message}"),
@@ -255,6 +277,22 @@ impl<'tcx> BorrowExplanation<'tcx> {
255277
Applicability::MaybeIncorrect,
256278
);
257279
};
280+
} else if let &LocalInfo::IfThenRescopeTemp { if_then } =
281+
local_decl.local_info()
282+
&& let hir::Node::Expr(expr) = tcx.hir_node(if_then)
283+
&& let hir::ExprKind::If(cond, conseq, alt) = expr.kind
284+
&& let hir::ExprKind::Let(&hir::LetExpr {
285+
span: _,
286+
pat,
287+
init,
288+
// FIXME(#101728): enable rewrite when type ascription is stabilized again
289+
ty: None,
290+
recovered: _,
291+
}) = cond.kind
292+
&& pat.span.can_be_used_for_suggestions()
293+
&& let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
294+
{
295+
suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
258296
}
259297
}
260298
}
@@ -390,6 +428,53 @@ impl<'tcx> BorrowExplanation<'tcx> {
390428
}
391429
}
392430

431+
fn suggest_rewrite_if_let(
432+
tcx: TyCtxt<'_>,
433+
expr: &hir::Expr<'_>,
434+
pat: &str,
435+
init: &hir::Expr<'_>,
436+
conseq: &hir::Expr<'_>,
437+
alt: Option<&hir::Expr<'_>>,
438+
err: &mut Diag<'_>,
439+
) {
440+
let source_map = tcx.sess.source_map();
441+
err.span_note(
442+
source_map.end_point(conseq.span),
443+
"lifetimes for temporaries generated in `if let`s have been shortened in Edition 2024 so that they are dropped here instead",
444+
);
445+
if expr.span.can_be_used_for_suggestions() && conseq.span.can_be_used_for_suggestions() {
446+
let needs_block = if let Some(hir::Node::Expr(expr)) =
447+
alt.and_then(|alt| tcx.hir().parent_iter(alt.hir_id).next()).map(|(_, node)| node)
448+
{
449+
matches!(expr.kind, hir::ExprKind::If(..))
450+
} else {
451+
false
452+
};
453+
let mut sugg = vec![
454+
(
455+
expr.span.shrink_to_lo().between(init.span),
456+
if needs_block { "{ match ".into() } else { "match ".into() },
457+
),
458+
(conseq.span.shrink_to_lo(), format!(" {{ {pat} => ")),
459+
];
460+
let expr_end = expr.span.shrink_to_hi();
461+
let mut expr_end_code;
462+
if let Some(alt) = alt {
463+
sugg.push((conseq.span.between(alt.span), " _ => ".into()));
464+
expr_end_code = "}".to_string();
465+
} else {
466+
expr_end_code = " _ => {} }".into();
467+
}
468+
expr_end_code.push('}');
469+
sugg.push((expr_end, expr_end_code));
470+
err.multipart_suggestion(
471+
"consider rewriting the `if` into `match` which preserves the extended lifetime",
472+
sugg,
473+
Applicability::MaybeIncorrect,
474+
);
475+
}
476+
}
477+
393478
impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
394479
fn free_region_constraint_info(
395480
&self,
@@ -465,14 +550,21 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
465550
.or_else(|| self.borrow_spans(span, location));
466551

467552
if use_in_later_iteration_of_loop {
468-
let later_use = self.later_use_kind(borrow, spans, use_location);
469-
BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1, later_use.2)
553+
let (later_use_kind, var_or_use_span, path_span) =
554+
self.later_use_kind(borrow, spans, use_location);
555+
BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span)
470556
} else {
471557
// Check if the location represents a `FakeRead`, and adapt the error
472558
// message to the `FakeReadCause` it is from: in particular,
473559
// the ones inserted in optimized `let var = <expr>` patterns.
474-
let later_use = self.later_use_kind(borrow, spans, location);
475-
BorrowExplanation::UsedLater(later_use.0, later_use.1, later_use.2)
560+
let (later_use_kind, var_or_use_span, path_span) =
561+
self.later_use_kind(borrow, spans, location);
562+
BorrowExplanation::UsedLater(
563+
borrow.borrowed_place.local,
564+
later_use_kind,
565+
var_or_use_span,
566+
path_span,
567+
)
476568
}
477569
}
478570

compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,8 @@ declare_features! (
495495
(unstable, half_open_range_patterns_in_slices, "1.66.0", Some(67264)),
496496
/// Allows `if let` guard in match arms.
497497
(unstable, if_let_guard, "1.47.0", Some(51114)),
498+
/// Rescoping temporaries in `if let` to align with Rust 2024.
499+
(unstable, if_let_rescope, "CURRENT_RUSTC_VERSION", Some(124085)),
498500
/// Allows `impl Trait` to be used inside associated types (RFC 2515).
499501
(unstable, impl_trait_in_assoc_type, "1.70.0", Some(63063)),
500502
/// Allows `impl Trait` as output type in `Fn` traits in return position of functions.

compiler/rustc_hir_analysis/src/check/region.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,12 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
472472

473473
hir::ExprKind::If(cond, then, Some(otherwise)) => {
474474
let expr_cx = visitor.cx;
475-
visitor.enter_scope(Scope { id: then.hir_id.local_id, data: ScopeData::IfThen });
475+
let data = if expr.span.at_least_rust_2024() && visitor.tcx.features().if_let_rescope {
476+
ScopeData::IfThenRescope
477+
} else {
478+
ScopeData::IfThen
479+
};
480+
visitor.enter_scope(Scope { id: then.hir_id.local_id, data });
476481
visitor.cx.var_parent = visitor.cx.parent;
477482
visitor.visit_expr(cond);
478483
visitor.visit_expr(then);
@@ -482,7 +487,12 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
482487

483488
hir::ExprKind::If(cond, then, None) => {
484489
let expr_cx = visitor.cx;
485-
visitor.enter_scope(Scope { id: then.hir_id.local_id, data: ScopeData::IfThen });
490+
let data = if expr.span.at_least_rust_2024() && visitor.tcx.features().if_let_rescope {
491+
ScopeData::IfThenRescope
492+
} else {
493+
ScopeData::IfThen
494+
};
495+
visitor.enter_scope(Scope { id: then.hir_id.local_id, data });
486496
visitor.cx.var_parent = visitor.cx.parent;
487497
visitor.visit_expr(cond);
488498
visitor.visit_expr(then);

compiler/rustc_lint/messages.ftl

+5
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,11 @@ lint_identifier_uncommon_codepoints = identifier contains {$codepoints_len ->
334334
*[other] {" "}{$identifier_type}
335335
} Unicode general security profile
336336
337+
lint_if_let_rescope = `if let` assigns a shorter lifetime since Edition 2024
338+
.label = this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
339+
.help = the value is now dropped here in Edition 2024
340+
.suggestion = a `match` with a single arm can preserve the drop order up to Edition 2021
341+
337342
lint_ignored_unless_crate_specified = {$level}({$name}) is ignored unless specified at crate level
338343
339344
lint_ill_formed_attribute_input = {$num_suggestions ->

0 commit comments

Comments
 (0)