Skip to content

Commit

Permalink
Auto merge of rust-lang#107251 - dingxiangfei2009:let-chain-rescope, …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
bors committed Sep 13, 2024
2 parents d3a8524 + b4b2b35 commit a5efa01
Show file tree
Hide file tree
Showing 29 changed files with 1,393 additions and 35 deletions.
29 changes: 21 additions & 8 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1999,19 +1999,32 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
) {
let used_in_call = matches!(
explanation,
BorrowExplanation::UsedLater(LaterUseKind::Call | LaterUseKind::Other, _call_span, _)
BorrowExplanation::UsedLater(
_,
LaterUseKind::Call | LaterUseKind::Other,
_call_span,
_
)
);
if !used_in_call {
debug!("not later used in call");
return;
}
if matches!(
self.body.local_decls[issued_borrow.borrowed_place.local].local_info(),
LocalInfo::IfThenRescopeTemp { .. }
) {
// A better suggestion will be issued by the `if_let_rescope` lint
return;
}

let use_span =
if let BorrowExplanation::UsedLater(LaterUseKind::Other, use_span, _) = explanation {
Some(use_span)
} else {
None
};
let use_span = if let BorrowExplanation::UsedLater(_, LaterUseKind::Other, use_span, _) =
explanation
{
Some(use_span)
} else {
None
};

let outer_call_loc =
if let TwoPhaseActivation::ActivatedAt(loc) = issued_borrow.activation_location {
Expand Down Expand Up @@ -2859,7 +2872,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
// and `move` will not help here.
(
Some(name),
BorrowExplanation::UsedLater(LaterUseKind::ClosureCapture, var_or_use_span, _),
BorrowExplanation::UsedLater(_, LaterUseKind::ClosureCapture, var_or_use_span, _),
) if borrow_spans.for_coroutine() || borrow_spans.for_closure() => self
.report_escaping_closure_capture(
borrow_spans,
Expand Down
110 changes: 101 additions & 9 deletions compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::{MirBorrowckCtxt, WriteKind};

#[derive(Debug)]
pub(crate) enum BorrowExplanation<'tcx> {
UsedLater(LaterUseKind, Span, Option<Span>),
UsedLater(Local, LaterUseKind, Span, Option<Span>),
UsedLaterInLoop(LaterUseKind, Span, Option<Span>),
UsedLaterWhenDropped {
drop_loc: Location,
Expand Down Expand Up @@ -99,17 +99,39 @@ impl<'tcx> BorrowExplanation<'tcx> {
}
}
match *self {
BorrowExplanation::UsedLater(later_use_kind, var_or_use_span, path_span) => {
BorrowExplanation::UsedLater(
dropped_local,
later_use_kind,
var_or_use_span,
path_span,
) => {
let message = match later_use_kind {
LaterUseKind::TraitCapture => "captured here by trait object",
LaterUseKind::ClosureCapture => "captured here by closure",
LaterUseKind::Call => "used by call",
LaterUseKind::FakeLetRead => "stored here",
LaterUseKind::Other => "used here",
};
// We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) {
if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) {
let local_decl = &body.local_decls[dropped_local];

if let &LocalInfo::IfThenRescopeTemp { if_then } = local_decl.local_info()
&& let Some((_, hir::Node::Expr(expr))) = tcx.hir().parent_iter(if_then).next()
&& let hir::ExprKind::If(cond, conseq, alt) = expr.kind
&& let hir::ExprKind::Let(&hir::LetExpr {
span: _,
pat,
init,
// FIXME(#101728): enable rewrite when type ascription is stabilized again
ty: None,
recovered: _,
}) = cond.kind
&& pat.span.can_be_used_for_suggestions()
&& let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
{
suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
} else if path_span.map_or(true, |path_span| path_span == var_or_use_span) {
// We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
if borrow_span.map_or(true, |sp| !sp.overlaps(var_or_use_span)) {
err.span_label(
var_or_use_span,
format!("{borrow_desc}borrow later {message}"),
Expand Down Expand Up @@ -255,6 +277,22 @@ impl<'tcx> BorrowExplanation<'tcx> {
Applicability::MaybeIncorrect,
);
};
} else if let &LocalInfo::IfThenRescopeTemp { if_then } =
local_decl.local_info()
&& let hir::Node::Expr(expr) = tcx.hir_node(if_then)
&& let hir::ExprKind::If(cond, conseq, alt) = expr.kind
&& let hir::ExprKind::Let(&hir::LetExpr {
span: _,
pat,
init,
// FIXME(#101728): enable rewrite when type ascription is stabilized again
ty: None,
recovered: _,
}) = cond.kind
&& pat.span.can_be_used_for_suggestions()
&& let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
{
suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
}
}
}
Expand Down Expand Up @@ -390,6 +428,53 @@ impl<'tcx> BorrowExplanation<'tcx> {
}
}

fn suggest_rewrite_if_let(
tcx: TyCtxt<'_>,
expr: &hir::Expr<'_>,
pat: &str,
init: &hir::Expr<'_>,
conseq: &hir::Expr<'_>,
alt: Option<&hir::Expr<'_>>,
err: &mut Diag<'_>,
) {
let source_map = tcx.sess.source_map();
err.span_note(
source_map.end_point(conseq.span),
"lifetimes for temporaries generated in `if let`s have been shortened in Edition 2024 so that they are dropped here instead",
);
if expr.span.can_be_used_for_suggestions() && conseq.span.can_be_used_for_suggestions() {
let needs_block = if let Some(hir::Node::Expr(expr)) =
alt.and_then(|alt| tcx.hir().parent_iter(alt.hir_id).next()).map(|(_, node)| node)
{
matches!(expr.kind, hir::ExprKind::If(..))
} else {
false
};
let mut sugg = vec![
(
expr.span.shrink_to_lo().between(init.span),
if needs_block { "{ match ".into() } else { "match ".into() },
),
(conseq.span.shrink_to_lo(), format!(" {{ {pat} => ")),
];
let expr_end = expr.span.shrink_to_hi();
let mut expr_end_code;
if let Some(alt) = alt {
sugg.push((conseq.span.between(alt.span), " _ => ".into()));
expr_end_code = "}".to_string();
} else {
expr_end_code = " _ => {} }".into();
}
expr_end_code.push('}');
sugg.push((expr_end, expr_end_code));
err.multipart_suggestion(
"consider rewriting the `if` into `match` which preserves the extended lifetime",
sugg,
Applicability::MaybeIncorrect,
);
}
}

impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
fn free_region_constraint_info(
&self,
Expand Down Expand Up @@ -465,14 +550,21 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
.or_else(|| self.borrow_spans(span, location));

if use_in_later_iteration_of_loop {
let later_use = self.later_use_kind(borrow, spans, use_location);
BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1, later_use.2)
let (later_use_kind, var_or_use_span, path_span) =
self.later_use_kind(borrow, spans, use_location);
BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span)
} else {
// Check if the location represents a `FakeRead`, and adapt the error
// message to the `FakeReadCause` it is from: in particular,
// the ones inserted in optimized `let var = <expr>` patterns.
let later_use = self.later_use_kind(borrow, spans, location);
BorrowExplanation::UsedLater(later_use.0, later_use.1, later_use.2)
let (later_use_kind, var_or_use_span, path_span) =
self.later_use_kind(borrow, spans, location);
BorrowExplanation::UsedLater(
borrow.borrowed_place.local,
later_use_kind,
var_or_use_span,
path_span,
)
}
}

Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ declare_features! (
(unstable, half_open_range_patterns_in_slices, "1.66.0", Some(67264)),
/// Allows `if let` guard in match arms.
(unstable, if_let_guard, "1.47.0", Some(51114)),
/// Rescoping temporaries in `if let` to align with Rust 2024.
(unstable, if_let_rescope, "CURRENT_RUSTC_VERSION", Some(124085)),
/// Allows `impl Trait` to be used inside associated types (RFC 2515).
(unstable, impl_trait_in_assoc_type, "1.70.0", Some(63063)),
/// Allows `impl Trait` as output type in `Fn` traits in return position of functions.
Expand Down
14 changes: 12 additions & 2 deletions compiler/rustc_hir_analysis/src/check/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,12 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h

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

hir::ExprKind::If(cond, then, None) => {
let expr_cx = visitor.cx;
visitor.enter_scope(Scope { id: then.hir_id.local_id, data: ScopeData::IfThen });
let data = if expr.span.at_least_rust_2024() && visitor.tcx.features().if_let_rescope {
ScopeData::IfThenRescope
} else {
ScopeData::IfThen
};
visitor.enter_scope(Scope { id: then.hir_id.local_id, data });
visitor.cx.var_parent = visitor.cx.parent;
visitor.visit_expr(cond);
visitor.visit_expr(then);
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,11 @@ lint_identifier_uncommon_codepoints = identifier contains {$codepoints_len ->
*[other] {" "}{$identifier_type}
} Unicode general security profile
lint_if_let_rescope = `if let` assigns a shorter lifetime since Edition 2024
.label = this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
.help = the value is now dropped here in Edition 2024
.suggestion = a `match` with a single arm can preserve the drop order up to Edition 2021
lint_ignored_unless_crate_specified = {$level}({$name}) is ignored unless specified at crate level
lint_ill_formed_attribute_input = {$num_suggestions ->
Expand Down
Loading

0 comments on commit a5efa01

Please sign in to comment.