rescope temp lifetime in let-chain into IfElse

apply rules by span edition
This commit is contained in:
Ding Xiang Fei 2023-01-24 16:06:35 +08:00
parent 33855f80d4
commit f93df1f7dc
No known key found for this signature in database
GPG Key ID: 3CD748647EEF6359
30 changed files with 1102 additions and 35 deletions

View File

@ -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 {
@ -2862,7 +2875,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,

View File

@ -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,
@ -99,7 +99,12 @@ 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",
@ -107,9 +112,26 @@ impl<'tcx> BorrowExplanation<'tcx> {
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(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}"),
@ -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(expr, &pat, init, conseq, alt, err);
}
}
}
@ -390,6 +428,38 @@ impl<'tcx> BorrowExplanation<'tcx> {
}
}
fn suggest_rewrite_if_let<'tcx>(
expr: &hir::Expr<'tcx>,
pat: &str,
init: &hir::Expr<'tcx>,
conseq: &hir::Expr<'tcx>,
alt: Option<&hir::Expr<'tcx>>,
err: &mut Diag<'_>,
) {
err.span_note(
conseq.span.shrink_to_hi(),
"lifetime for temporaries generated in `if let`s have been shorted in Edition 2024",
);
if expr.span.can_be_used_for_suggestions() && conseq.span.can_be_used_for_suggestions() {
let mut sugg = vec![
(expr.span.shrink_to_lo().between(init.span), "match ".into()),
(conseq.span.shrink_to_lo(), format!(" {{ {pat} => ")),
];
let expr_end = expr.span.shrink_to_hi();
if let Some(alt) = alt {
sugg.push((conseq.span.between(alt.span), format!(" _ => ")));
sugg.push((expr_end, "}".into()));
} else {
sugg.push((expr_end, " _ => {} }".into()));
}
err.multipart_suggestion(
"consider rewriting the `if` into `match` which preserves the extended lifetime",
sugg,
Applicability::MachineApplicable,
);
}
}
impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
fn free_region_constraint_info(
&self,
@ -465,14 +535,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,
)
}
}

View File

@ -497,6 +497,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.

View File

@ -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);
@ -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);

View File

@ -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 = rewrite this `if let` into a `match` with a single arm to 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 ->

View File

@ -0,0 +1,312 @@
use std::ops::ControlFlow;
use hir::intravisit::Visitor;
use rustc_ast::Recovered;
use rustc_hir as hir;
use rustc_macros::{LintDiagnostic, Subdiagnostic};
use rustc_session::lint::FutureIncompatibilityReason;
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::edition::Edition;
use rustc_span::Span;
use crate::{LateContext, LateLintPass};
declare_lint! {
/// The `if_let_rescope` lint detects cases where a temporary value with
/// significant drop is generated on the right hand side of `if let`
/// and suggests a rewrite into `match` when possible.
///
/// ### Example
///
/// ```rust,edition2021
/// #![feature(if_let_rescope)]
/// #![warn(if_let_rescope)]
/// #![allow(unused_variables)]
///
/// struct Droppy;
/// impl Drop for Droppy {
/// fn drop(&mut self) {
/// // Custom destructor, including this `drop` implementation, is considered
/// // significant.
/// // Rust does not check whether this destructor emits side-effects that can
/// // lead to observable change in program semantics, when the drop order changes.
/// // Rust biases to be on the safe side, so that you can apply discretion whether
/// // this change indeed breaches any contract or specification that your code needs
/// // to honour.
/// println!("dropped");
/// }
/// }
/// impl Droppy {
/// fn get(&self) -> Option<u8> {
/// None
/// }
/// }
///
/// fn main() {
/// if let Some(value) = Droppy.get() {
/// // do something
/// } else {
/// // do something else
/// }
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// With Edition 2024, temporaries generated while evaluating `if let`s
/// will be dropped before the `else` block.
/// This lint captures a possible change in runtime behaviour due to
/// a change in sequence of calls to significant `Drop::drop` destructors.
///
/// A significant [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html)
/// destructor here refers to an explicit, arbitrary implementation of the `Drop` trait on the type
/// with exceptions including `Vec`, `Box`, `Rc`, `BTreeMap` and `HashMap`
/// that are marked by the compiler otherwise so long that the generic types have
/// no significant destructor recursively.
/// In other words, a type has a significant drop destructor when it has a `Drop` implementation
/// or its destructor invokes a significant destructor on a type.
/// Since we cannot completely reason about the change by just inspecting the existence of
/// a significant destructor, this lint remains only a suggestion and is set to `allow` by default.
///
/// Whenever possible, a rewrite into an equivalent `match` expression that
/// observe the same order of calls to such destructors is proposed by this lint.
/// Authors may take their own discretion whether the rewrite suggestion shall be
/// accepted, or rejected to continue the use of the `if let` expression.
pub IF_LET_RESCOPE,
Allow,
"`if let` assigns a shorter lifetime to temporary values being pattern-matched against in Edition 2024 and \
rewriting in `match` is an option to preserve the semantics up to Edition 2021",
@future_incompatible = FutureIncompatibleInfo {
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
reference: "issue #124085 <https://github.com/rust-lang/rust/issues/124085>",
};
}
declare_lint_pass!(
/// Lint for potential change in program semantics of `if let`s
IfLetRescope => [IF_LET_RESCOPE]
);
impl<'tcx> LateLintPass<'tcx> for IfLetRescope {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
if !expr.span.edition().at_least_rust_2021() || !cx.tcx.features().if_let_rescope {
return;
}
let hir::ExprKind::If(cond, conseq, alt) = expr.kind else { return };
let hir::ExprKind::Let(&hir::LetExpr {
span,
pat,
init,
ty: ty_ascription,
recovered: Recovered::No,
}) = cond.kind
else {
return;
};
let source_map = cx.tcx.sess.source_map();
let expr_end = expr.span.shrink_to_hi();
let if_let_pat = expr.span.shrink_to_lo().between(init.span);
let before_conseq = conseq.span.shrink_to_lo();
let lifetime_end = source_map.end_point(conseq.span);
if let ControlFlow::Break(significant_dropper) =
(FindSignificantDropper { cx }).visit_expr(init)
{
let lint_without_suggestion = || {
cx.tcx.emit_node_span_lint(
IF_LET_RESCOPE,
expr.hir_id,
span,
IfLetRescopeRewrite { significant_dropper, lifetime_end, sugg: None },
)
};
if ty_ascription.is_some()
|| !expr.span.can_be_used_for_suggestions()
|| !pat.span.can_be_used_for_suggestions()
{
// Our `match` rewrites does not support type ascription,
// so we just bail.
// Alternatively when the span comes from proc macro expansion,
// we will also bail.
// FIXME(#101728): change this when type ascription syntax is stabilized again
lint_without_suggestion();
} else {
let Ok(pat) = source_map.span_to_snippet(pat.span) else {
lint_without_suggestion();
return;
};
if let Some(alt) = alt {
let alt_start = conseq.span.between(alt.span);
if !alt_start.can_be_used_for_suggestions() {
lint_without_suggestion();
return;
}
cx.tcx.emit_node_span_lint(
IF_LET_RESCOPE,
expr.hir_id,
span,
IfLetRescopeRewrite {
significant_dropper,
lifetime_end,
sugg: Some(IfLetRescopeRewriteSuggestion::WithElse {
if_let_pat,
before_conseq,
pat,
expr_end,
alt_start,
}),
},
);
} else {
cx.tcx.emit_node_span_lint(
IF_LET_RESCOPE,
expr.hir_id,
span,
IfLetRescopeRewrite {
significant_dropper,
lifetime_end,
sugg: Some(IfLetRescopeRewriteSuggestion::WithoutElse {
if_let_pat,
before_conseq,
pat,
expr_end,
}),
},
);
}
}
}
}
}
#[derive(LintDiagnostic)]
#[diag(lint_if_let_rescope)]
struct IfLetRescopeRewrite {
#[label]
significant_dropper: Span,
#[help]
lifetime_end: Span,
#[subdiagnostic]
sugg: Option<IfLetRescopeRewriteSuggestion>,
}
#[derive(Subdiagnostic)]
enum IfLetRescopeRewriteSuggestion {
#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
WithElse {
#[suggestion_part(code = "match ")]
if_let_pat: Span,
#[suggestion_part(code = " {{ {pat} => ")]
before_conseq: Span,
pat: String,
#[suggestion_part(code = "}}")]
expr_end: Span,
#[suggestion_part(code = " _ => ")]
alt_start: Span,
},
#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
WithoutElse {
#[suggestion_part(code = "match ")]
if_let_pat: Span,
#[suggestion_part(code = " {{ {pat} => ")]
before_conseq: Span,
pat: String,
#[suggestion_part(code = " _ => {{}} }}")]
expr_end: Span,
},
}
struct FindSignificantDropper<'tcx, 'a> {
cx: &'a LateContext<'tcx>,
}
impl<'tcx, 'a> Visitor<'tcx> for FindSignificantDropper<'tcx, 'a> {
type Result = ControlFlow<Span>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Self::Result {
if self
.cx
.typeck_results()
.expr_ty(expr)
.has_significant_drop(self.cx.tcx, self.cx.param_env)
{
return ControlFlow::Break(expr.span);
}
match expr.kind {
hir::ExprKind::ConstBlock(_)
| hir::ExprKind::Lit(_)
| hir::ExprKind::Path(_)
| hir::ExprKind::Assign(_, _, _)
| hir::ExprKind::AssignOp(_, _, _)
| hir::ExprKind::Break(_, _)
| hir::ExprKind::Continue(_)
| hir::ExprKind::Ret(_)
| hir::ExprKind::Become(_)
| hir::ExprKind::InlineAsm(_)
| hir::ExprKind::OffsetOf(_, _)
| hir::ExprKind::Repeat(_, _)
| hir::ExprKind::Err(_)
| hir::ExprKind::Struct(_, _, _)
| hir::ExprKind::Closure(_)
| hir::ExprKind::Block(_, _)
| hir::ExprKind::DropTemps(_)
| hir::ExprKind::Loop(_, _, _, _) => ControlFlow::Continue(()),
hir::ExprKind::Tup(exprs) | hir::ExprKind::Array(exprs) => {
for expr in exprs {
self.visit_expr(expr)?;
}
ControlFlow::Continue(())
}
hir::ExprKind::Call(callee, args) => {
self.visit_expr(callee)?;
for expr in args {
self.visit_expr(expr)?;
}
ControlFlow::Continue(())
}
hir::ExprKind::MethodCall(_, receiver, args, _) => {
self.visit_expr(receiver)?;
for expr in args {
self.visit_expr(expr)?;
}
ControlFlow::Continue(())
}
hir::ExprKind::Index(left, right, _) | hir::ExprKind::Binary(_, left, right) => {
self.visit_expr(left)?;
self.visit_expr(right)
}
hir::ExprKind::Unary(_, expr)
| hir::ExprKind::Cast(expr, _)
| hir::ExprKind::Type(expr, _)
| hir::ExprKind::Yield(expr, _)
| hir::ExprKind::AddrOf(_, _, expr)
| hir::ExprKind::Match(expr, _, _)
| hir::ExprKind::Field(expr, _)
| hir::ExprKind::Let(&hir::LetExpr {
init: expr,
span: _,
pat: _,
ty: _,
recovered: Recovered::No,
}) => self.visit_expr(expr),
hir::ExprKind::Let(_) => ControlFlow::Continue(()),
hir::ExprKind::If(cond, _, _) => {
if let hir::ExprKind::Let(hir::LetExpr {
init,
span: _,
pat: _,
ty: _,
recovered: Recovered::No,
}) = cond.kind
{
self.visit_expr(init)?;
}
ControlFlow::Continue(())
}
}
}
}

View File

@ -56,6 +56,7 @@ mod expect;
mod for_loops_over_fallibles;
mod foreign_modules;
pub mod hidden_unicode_codepoints;
mod if_let_rescope;
mod impl_trait_overcaptures;
mod internal;
mod invalid_from_utf8;
@ -94,6 +95,7 @@ use drop_forget_useless::*;
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
use for_loops_over_fallibles::*;
use hidden_unicode_codepoints::*;
use if_let_rescope::IfLetRescope;
use impl_trait_overcaptures::ImplTraitOvercaptures;
use internal::*;
use invalid_from_utf8::*;
@ -243,6 +245,7 @@ late_lint_methods!(
NonLocalDefinitions: NonLocalDefinitions::default(),
ImplTraitOvercaptures: ImplTraitOvercaptures,
TailExprDropOrder: TailExprDropOrder,
IfLetRescope: IfLetRescope,
]
]
);

View File

@ -96,6 +96,7 @@ impl fmt::Debug for Scope {
ScopeData::Arguments => write!(fmt, "Arguments({:?})", self.id),
ScopeData::Destruction => write!(fmt, "Destruction({:?})", self.id),
ScopeData::IfThen => write!(fmt, "IfThen({:?})", self.id),
ScopeData::IfThenRescope => write!(fmt, "IfThen[edition2024]({:?})", self.id),
ScopeData::Remainder(fsi) => write!(
fmt,
"Remainder {{ block: {:?}, first_statement_index: {}}}",
@ -126,6 +127,11 @@ pub enum ScopeData {
/// Used for variables introduced in an if-let expression.
IfThen,
/// Scope of the condition and then block of an if expression
/// Used for variables introduced in an if-let expression,
/// whose lifetimes do not cross beyond this scope.
IfThenRescope,
/// Scope following a `let id = expr;` binding in a block.
Remainder(FirstStatementIndex),
}

View File

@ -1084,6 +1084,9 @@ pub enum LocalInfo<'tcx> {
/// (with no intervening statement context).
// FIXME(matthewjasper) Don't store in this in `Body`
BlockTailTemp(BlockTailInfo),
/// A temporary created during evaluating `if` predicate, possibly for pattern matching for `let`s,
/// and subject to Edition 2024 temporary lifetime rules
IfThenRescopeTemp { if_then: HirId },
/// A temporary created during the pass `Derefer` to avoid it's retagging
DerefTemp,
/// A temporary created for borrow checking.

View File

@ -41,7 +41,15 @@ impl RvalueScopes {
debug!("temporary_scope({expr_id:?}) = {id:?} [enclosing]");
return Some(id);
}
_ => id = p,
ScopeData::IfThenRescope => {
debug!("temporary_scope({expr_id:?}) = {p:?} [enclosing]");
return Some(p);
}
ScopeData::Node
| ScopeData::CallSite
| ScopeData::Arguments
| ScopeData::IfThen
| ScopeData::Remainder(_) => id = p,
}
}

View File

@ -1,7 +1,9 @@
//! See docs in build/expr/mod.rs
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir::HirId;
use rustc_middle::middle::region;
use rustc_middle::middle::region::{Scope, ScopeData};
use rustc_middle::mir::*;
use rustc_middle::thir::*;
use tracing::{debug, instrument};
@ -73,11 +75,19 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
_ if let Some(tail_info) = this.block_context.currently_in_block_tail() => {
LocalInfo::BlockTailTemp(tail_info)
}
_ if let Some(Scope { data: ScopeData::IfThenRescope, id }) = temp_lifetime => {
LocalInfo::IfThenRescopeTemp {
if_then: HirId { owner: this.hir_id.owner, local_id: id },
}
}
_ => LocalInfo::Boring,
};
**local_decl.local_info.as_mut().assert_crate_local() = local_info;
this.local_decls.push(local_decl)
};
debug!(?temp);
if deduplicate_temps {
this.fixed_temps.insert(expr_id, temp);
}

View File

@ -706,7 +706,13 @@ impl<'tcx> Cx<'tcx> {
hir::ExprKind::If(cond, then, else_opt) => ExprKind::If {
if_then_scope: region::Scope {
id: then.hir_id.local_id,
data: region::ScopeData::IfThen,
data: {
if expr.span.at_least_rust_2024() && tcx.features().if_let_rescope {
region::ScopeData::IfThenRescope
} else {
region::ScopeData::IfThen
}
},
},
cond: self.mirror_expr(cond),
then: self.mirror_expr(then),

View File

@ -1017,6 +1017,7 @@ symbols! {
ident,
if_let,
if_let_guard,
if_let_rescope,
if_while_or_patterns,
ignore,
impl_header_lifetime_elision,

View File

@ -1,6 +1,11 @@
//@ run-pass
//@ compile-flags: -Z validate-mir
//@ revisions: edition2021 edition2024
//@ [edition2021] edition: 2021
//@ [edition2024] compile-flags: -Z unstable-options
//@ [edition2024] edition: 2024
#![feature(let_chains)]
#![cfg_attr(edition2024, feature(if_let_rescope))]
use std::cell::RefCell;
use std::convert::TryInto;
@ -55,11 +60,18 @@ impl DropOrderCollector {
}
fn if_let(&self) {
#[cfg(edition2021)]
if let None = self.option_loud_drop(2) {
unreachable!();
} else {
self.print(1);
}
#[cfg(edition2024)]
if let None = self.option_loud_drop(1) {
unreachable!();
} else {
self.print(2);
}
if let Some(_) = self.option_loud_drop(4) {
self.print(3);
@ -194,6 +206,7 @@ impl DropOrderCollector {
self.print(3); // 3
}
#[cfg(edition2021)]
// take the "else" branch
if self.option_loud_drop(5).is_some() // 1
&& self.option_loud_drop(6).is_some() // 2
@ -202,6 +215,15 @@ impl DropOrderCollector {
} else {
self.print(7); // 3
}
#[cfg(edition2024)]
// take the "else" branch
if self.option_loud_drop(5).is_some() // 1
&& self.option_loud_drop(6).is_some() // 2
&& let None = self.option_loud_drop(7) { // 4
unreachable!();
} else {
self.print(8); // 3
}
// let exprs interspersed
if self.option_loud_drop(9).is_some() // 1

View File

@ -0,0 +1,122 @@
//@ run-pass
//@ edition:2024
//@ compile-flags: -Z validate-mir -Zunstable-options
#![feature(let_chains)]
#![feature(if_let_rescope)]
use std::cell::RefCell;
use std::convert::TryInto;
#[derive(Default)]
struct DropOrderCollector(RefCell<Vec<u32>>);
struct LoudDrop<'a>(&'a DropOrderCollector, u32);
impl Drop for LoudDrop<'_> {
fn drop(&mut self) {
println!("{}", self.1);
self.0.0.borrow_mut().push(self.1);
}
}
impl DropOrderCollector {
fn option_loud_drop(&self, n: u32) -> Option<LoudDrop> {
Some(LoudDrop(self, n))
}
fn print(&self, n: u32) {
println!("{}", n);
self.0.borrow_mut().push(n)
}
fn assert_sorted(self) {
assert!(
self.0
.into_inner()
.into_iter()
.enumerate()
.all(|(idx, item)| idx + 1 == item.try_into().unwrap())
);
}
fn if_let(&self) {
if let None = self.option_loud_drop(1) {
unreachable!();
} else {
self.print(2);
}
if let Some(_) = self.option_loud_drop(4) {
self.print(3);
}
if let Some(_d) = self.option_loud_drop(6) {
self.print(5);
}
}
fn let_chain(&self) {
// take the "then" branch
if self.option_loud_drop(1).is_some() // 1
&& self.option_loud_drop(2).is_some() // 2
&& let Some(_d) = self.option_loud_drop(4)
// 4
{
self.print(3); // 3
}
// take the "else" branch
if self.option_loud_drop(5).is_some() // 1
&& self.option_loud_drop(6).is_some() // 2
&& let None = self.option_loud_drop(7)
// 3
{
unreachable!();
} else {
self.print(8); // 4
}
// let exprs interspersed
if self.option_loud_drop(9).is_some() // 1
&& let Some(_d) = self.option_loud_drop(13) // 5
&& self.option_loud_drop(10).is_some() // 2
&& let Some(_e) = self.option_loud_drop(12)
// 4
{
self.print(11); // 3
}
// let exprs first
if let Some(_d) = self.option_loud_drop(18) // 5
&& let Some(_e) = self.option_loud_drop(17) // 4
&& self.option_loud_drop(14).is_some() // 1
&& self.option_loud_drop(15).is_some()
// 2
{
self.print(16); // 3
}
// let exprs last
if self.option_loud_drop(19).is_some() // 1
&& self.option_loud_drop(20).is_some() // 2
&& let Some(_d) = self.option_loud_drop(23) // 5
&& let Some(_e) = self.option_loud_drop(22)
// 4
{
self.print(21); // 3
}
}
}
fn main() {
println!("-- if let --");
let collector = DropOrderCollector::default();
collector.if_let();
collector.assert_sorted();
println!("-- let chain --");
let collector = DropOrderCollector::default();
collector.let_chain();
collector.assert_sorted();
}

View File

@ -0,0 +1,26 @@
//@ edition: 2024
//@ compile-flags: -Z validate-mir -Zunstable-options
//@ run-rustfix
#![feature(if_let_rescope)]
#![deny(if_let_rescope)]
struct Droppy;
impl Drop for Droppy {
fn drop(&mut self) {
println!("dropped");
}
}
impl Droppy {
fn get_ref(&self) -> Option<&u8> {
None
}
}
fn do_something<T>(_: &T) {}
fn main() {
let binding = Droppy;
do_something(match binding.get_ref() { Some(value) => { value } _ => { &0 }});
//~^ ERROR: temporary value dropped while borrowed
}

View File

@ -0,0 +1,25 @@
//@ edition: 2024
//@ compile-flags: -Z validate-mir -Zunstable-options
//@ run-rustfix
#![feature(if_let_rescope)]
#![deny(if_let_rescope)]
struct Droppy;
impl Drop for Droppy {
fn drop(&mut self) {
println!("dropped");
}
}
impl Droppy {
fn get_ref(&self) -> Option<&u8> {
None
}
}
fn do_something<T>(_: &T) {}
fn main() {
do_something(if let Some(value) = Droppy.get_ref() { value } else { &0 });
//~^ ERROR: temporary value dropped while borrowed
}

View File

@ -0,0 +1,26 @@
error[E0716]: temporary value dropped while borrowed
--> $DIR/if-let-rescope-borrowck-suggestions.rs:23:39
|
LL | do_something(if let Some(value) = Droppy.get_ref() { value } else { &0 });
| ^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
|
note: lifetime for temporaries generated in `if let`s have been shorted in Edition 2024
--> $DIR/if-let-rescope-borrowck-suggestions.rs:23:65
|
LL | do_something(if let Some(value) = Droppy.get_ref() { value } else { &0 });
| ^
help: consider using a `let` binding to create a longer lived value
|
LL ~ let binding = Droppy;
LL ~ do_something(if let Some(value) = binding.get_ref() { value } else { &0 });
|
help: consider rewriting the `if` into `match` which preserves the extended lifetime
|
LL | do_something(match Droppy.get_ref() { Some(value) => { value } _ => { &0 }});
| ~~~~~ ++++++++++++++++ ~~~~ +
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0716`.

View File

@ -0,0 +1,31 @@
// This test checks that the lint `if_let_rescope` only actions
// when the feature gate is enabled.
// Edition 2021 is used here because the lint should work especially
// when edition migration towards 2024 is run.
//@ revisions: with_feature_gate without_feature_gate
//@ [without_feature_gate] check-pass
//@ edition: 2021
#![cfg_attr(with_feature_gate, feature(if_let_rescope))]
#![deny(if_let_rescope)]
#![allow(irrefutable_let_patterns)]
struct Droppy;
impl Drop for Droppy {
fn drop(&mut self) {
println!("dropped");
}
}
impl Droppy {
fn get(&self) -> Option<u8> {
None
}
}
fn main() {
if let Some(_value) = Droppy.get() {
//[with_feature_gate]~^ ERROR: `if let` assigns a shorter lifetime since Edition 2024
//[with_feature_gate]~| WARN: this changes meaning in Rust 2024
};
}

View File

@ -0,0 +1,30 @@
error: `if let` assigns a shorter lifetime since Edition 2024
--> $DIR/lint-if-let-rescope-gated.rs:27:8
|
LL | if let Some(_value) = Droppy.get() {
| ^^^^^^^^^^^^^^^^^^^------^^^^^^
| |
| this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #124085 <https://github.com/rust-lang/rust/issues/124085>
help: the value is now dropped here in Edition 2024
--> $DIR/lint-if-let-rescope-gated.rs:30:5
|
LL | };
| ^
note: the lint level is defined here
--> $DIR/lint-if-let-rescope-gated.rs:11:9
|
LL | #![deny(if_let_rescope)]
| ^^^^^^^^^^^^^^
help: rewrite this `if let` into a `match` with a single arm to preserve the drop order up to Edition 2021
|
LL ~ match Droppy.get() { Some(_value) => {
LL |
LL |
LL ~ } _ => {} };
|
error: aborting due to 1 previous error

View File

@ -0,0 +1,41 @@
// This test ensures that no suggestion is emitted if the span originates from
// an expansion that is probably not under a user's control.
//@ edition:2021
//@ compile-flags: -Z unstable-options
#![feature(if_let_rescope)]
#![deny(if_let_rescope)]
#![allow(irrefutable_let_patterns)]
macro_rules! edition_2021_if_let {
($p:pat, $e:expr, { $($conseq:tt)* } { $($alt:tt)* }) => {
if let $p = $e { $($conseq)* } else { $($alt)* }
//~^ ERROR `if let` assigns a shorter lifetime since Edition 2024
//~| WARN this changes meaning in Rust 2024
}
}
fn droppy() -> Droppy {
Droppy
}
struct Droppy;
impl Drop for Droppy {
fn drop(&mut self) {
println!("dropped");
}
}
impl Droppy {
fn get(&self) -> Option<u8> {
None
}
}
fn main() {
edition_2021_if_let! {
Some(_value),
droppy().get(),
{}
{}
};
}

View File

@ -0,0 +1,39 @@
error: `if let` assigns a shorter lifetime since Edition 2024
--> $DIR/lint-if-let-rescope-with-macro.rs:13:12
|
LL | if let $p = $e { $($conseq)* } else { $($alt)* }
| ^^^
...
LL | / edition_2021_if_let! {
LL | | Some(_value),
LL | | droppy().get(),
| | -------- this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
LL | | {}
LL | | {}
LL | | };
| |_____- in this macro invocation
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #124085 <https://github.com/rust-lang/rust/issues/124085>
help: the value is now dropped here in Edition 2024
--> $DIR/lint-if-let-rescope-with-macro.rs:13:38
|
LL | if let $p = $e { $($conseq)* } else { $($alt)* }
| ^
...
LL | / edition_2021_if_let! {
LL | | Some(_value),
LL | | droppy().get(),
LL | | {}
LL | | {}
LL | | };
| |_____- in this macro invocation
note: the lint level is defined here
--> $DIR/lint-if-let-rescope-with-macro.rs:8:9
|
LL | #![deny(if_let_rescope)]
| ^^^^^^^^^^^^^^
= note: this error originates in the macro `edition_2021_if_let` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error

View File

@ -0,0 +1,48 @@
//@ edition:2024
//@ compile-flags: -Z validate-mir -Zunstable-options
//@ run-rustfix
#![feature(if_let_rescope)]
#![deny(if_let_rescope)]
#![allow(irrefutable_let_patterns)]
fn droppy() -> Droppy {
Droppy
}
struct Droppy;
impl Drop for Droppy {
fn drop(&mut self) {
println!("dropped");
}
}
impl Droppy {
fn get(&self) -> Option<u8> {
None
}
}
fn main() {
match droppy().get() { Some(_value) => {
//~^ ERROR: `if let` assigns a shorter lifetime since Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| HELP: rewrite this `if let` into a `match`
// do something
} _ => {
//~^ HELP: the value is now dropped here in Edition 2024
// do something else
}}
if let Some(1) = { match Droppy.get() { Some(_value) => { Some(1) } _ => { None }} } {
//~^ ERROR: `if let` assigns a shorter lifetime since Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| HELP: rewrite this `if let` into a `match`
//~| HELP: the value is now dropped here in Edition 2024
}
if let () = { match Droppy.get() { Some(_value) => {} _ => {} } } {
//~^ ERROR: `if let` assigns a shorter lifetime since Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| HELP: rewrite this `if let` into a `match`
//~| HELP: the value is now dropped here in Edition 2024
}
}

View File

@ -0,0 +1,48 @@
//@ edition:2024
//@ compile-flags: -Z validate-mir -Zunstable-options
//@ run-rustfix
#![feature(if_let_rescope)]
#![deny(if_let_rescope)]
#![allow(irrefutable_let_patterns)]
fn droppy() -> Droppy {
Droppy
}
struct Droppy;
impl Drop for Droppy {
fn drop(&mut self) {
println!("dropped");
}
}
impl Droppy {
fn get(&self) -> Option<u8> {
None
}
}
fn main() {
if let Some(_value) = droppy().get() {
//~^ ERROR: `if let` assigns a shorter lifetime since Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| HELP: rewrite this `if let` into a `match`
// do something
} else {
//~^ HELP: the value is now dropped here in Edition 2024
// do something else
}
if let Some(1) = { if let Some(_value) = Droppy.get() { Some(1) } else { None } } {
//~^ ERROR: `if let` assigns a shorter lifetime since Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| HELP: rewrite this `if let` into a `match`
//~| HELP: the value is now dropped here in Edition 2024
}
if let () = { if let Some(_value) = Droppy.get() {} } {
//~^ ERROR: `if let` assigns a shorter lifetime since Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| HELP: rewrite this `if let` into a `match`
//~| HELP: the value is now dropped here in Edition 2024
}
}

View File

@ -0,0 +1,74 @@
error: `if let` assigns a shorter lifetime since Edition 2024
--> $DIR/lint-if-let-rescope.rs:25:8
|
LL | if let Some(_value) = droppy().get() {
| ^^^^^^^^^^^^^^^^^^^--------^^^^^^
| |
| this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #124085 <https://github.com/rust-lang/rust/issues/124085>
help: the value is now dropped here in Edition 2024
--> $DIR/lint-if-let-rescope.rs:30:5
|
LL | } else {
| ^
note: the lint level is defined here
--> $DIR/lint-if-let-rescope.rs:6:9
|
LL | #![deny(if_let_rescope)]
| ^^^^^^^^^^^^^^
help: rewrite this `if let` into a `match` with a single arm to preserve the drop order up to Edition 2021
|
LL ~ match droppy().get() { Some(_value) => {
LL |
...
LL | // do something
LL ~ } _ => {
LL |
LL | // do something else
LL ~ }}
|
error: `if let` assigns a shorter lifetime since Edition 2024
--> $DIR/lint-if-let-rescope.rs:35:27
|
LL | if let Some(1) = { if let Some(_value) = Droppy.get() { Some(1) } else { None } } {
| ^^^^^^^^^^^^^^^^^^^------^^^^^^
| |
| this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #124085 <https://github.com/rust-lang/rust/issues/124085>
help: the value is now dropped here in Edition 2024
--> $DIR/lint-if-let-rescope.rs:35:69
|
LL | if let Some(1) = { if let Some(_value) = Droppy.get() { Some(1) } else { None } } {
| ^
help: rewrite this `if let` into a `match` with a single arm to preserve the drop order up to Edition 2021
|
LL | if let Some(1) = { match Droppy.get() { Some(_value) => { Some(1) } _ => { None }} } {
| ~~~~~ +++++++++++++++++ ~~~~ +
error: `if let` assigns a shorter lifetime since Edition 2024
--> $DIR/lint-if-let-rescope.rs:42:22
|
LL | if let () = { if let Some(_value) = Droppy.get() {} } {
| ^^^^^^^^^^^^^^^^^^^------^^^^^^
| |
| this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #124085 <https://github.com/rust-lang/rust/issues/124085>
help: the value is now dropped here in Edition 2024
--> $DIR/lint-if-let-rescope.rs:42:55
|
LL | if let () = { if let Some(_value) = Droppy.get() {} } {
| ^
help: rewrite this `if let` into a `match` with a single arm to preserve the drop order up to Edition 2021
|
LL | if let () = { match Droppy.get() { Some(_value) => {} _ => {} } } {
| ~~~~~ +++++++++++++++++ +++++++++
error: aborting due to 3 previous errors

View File

@ -0,0 +1,27 @@
// This test shows the code that could have been accepted by enabling #![feature(if_let_rescope)]
struct A;
struct B<'a, T>(&'a mut T);
impl A {
fn f(&mut self) -> Option<B<'_, Self>> {
Some(B(self))
}
}
impl<'a, T> Drop for B<'a, T> {
fn drop(&mut self) {
// this is needed to keep NLL's hands off and to ensure
// the inner mutable borrow stays alive
}
}
fn main() {
let mut a = A;
if let None = a.f().as_ref() {
unreachable!()
} else {
a.f().unwrap();
//~^ ERROR cannot borrow `a` as mutable more than once at a time
};
}

View File

@ -0,0 +1,18 @@
error[E0499]: cannot borrow `a` as mutable more than once at a time
--> $DIR/feature-gate-if-let-rescope.rs:24:9
|
LL | if let None = a.f().as_ref() {
| -----
| |
| first mutable borrow occurs here
| a temporary with access to the first borrow is created here ...
...
LL | a.f().unwrap();
| ^ second mutable borrow occurs here
LL |
LL | };
| - ... and the first borrow might be used here, when that temporary is dropped and runs the destructor for type `Option<B<'_, A>>`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0499`.

View File

@ -1,9 +1,14 @@
//@ run-pass
//@ needs-unwind
//@ revisions: edition2021 edition2024
//@ [edition2021] edition: 2021
//@ [edition2024] compile-flags: -Z unstable-options
//@ [edition2024] edition: 2024
// See `mir_drop_order.rs` for more information
#![feature(let_chains)]
#![cfg_attr(edition2024, feature(if_let_rescope))]
#![allow(irrefutable_let_patterns)]
use std::cell::RefCell;
@ -39,25 +44,32 @@ fn main() {
0,
d(
1,
if let Some(_) = d(2, Some(true)).extra && let DropLogger { .. } = d(3, None) {
if let Some(_) = d(2, Some(true)).extra
&& let DropLogger { .. } = d(3, None)
{
None
} else {
Some(true)
}
).extra
},
)
.extra,
),
d(4, None),
&d(5, None),
d(6, None),
if let DropLogger { .. } = d(7, None) && let DropLogger { .. } = d(8, None) {
if let DropLogger { .. } = d(7, None)
&& let DropLogger { .. } = d(8, None)
{
d(9, None)
}
else {
} else {
// 10 is not constructed
d(10, None)
},
);
#[cfg(edition2021)]
assert_eq!(get(), vec![8, 7, 1, 3, 2]);
#[cfg(edition2024)]
assert_eq!(get(), vec![3, 2, 8, 7, 1]);
}
assert_eq!(get(), vec![0, 4, 6, 9, 5]);
@ -73,21 +85,26 @@ fn main() {
None
} else {
Some(true)
}
).extra
},
)
.extra,
),
d(15, None),
&d(16, None),
d(17, None),
if let DropLogger { .. } = d(18, None) && let DropLogger { .. } = d(19, None) {
if let DropLogger { .. } = d(18, None)
&& let DropLogger { .. } = d(19, None)
{
d(20, None)
}
else {
} else {
// 10 is not constructed
d(21, None)
},
panic::panic_any(InjectedFailure)
panic::panic_any(InjectedFailure),
);
});
#[cfg(edition2021)]
assert_eq!(get(), vec![20, 17, 15, 11, 19, 18, 16, 12, 14, 13]);
#[cfg(edition2024)]
assert_eq!(get(), vec![14, 13, 19, 18, 20, 17, 15, 11, 16, 12]);
}

View File

@ -1,5 +1,5 @@
error[E0597]: `counter` does not live long enough
--> $DIR/issue-54556-niconii.rs:22:20
--> $DIR/issue-54556-niconii.rs:30:20
|
LL | let counter = Mutex;
| ------- binding `counter` declared here

View File

@ -6,6 +6,14 @@
// of temp drop order, and thus why inserting a semi-colon after the
// `if let` expression in `main` works.
//@ revisions: edition2021 edition2024
//@ [edition2021] edition: 2021
//@ [edition2024] edition: 2024
//@ [edition2024] compile-flags: -Z unstable-options
//@ [edition2024] check-pass
#![cfg_attr(edition2024, feature(if_let_rescope))]
struct Mutex;
struct MutexGuard<'a>(&'a Mutex);
@ -19,8 +27,10 @@ impl Mutex {
fn main() {
let counter = Mutex;
if let Ok(_) = counter.lock() { } //~ ERROR does not live long enough
if let Ok(_) = counter.lock() { }
//[edition2021]~^ ERROR: does not live long enough
// Up until Edition 2021:
// With this code as written, the dynamic semantics here implies
// that `Mutex::drop` for `counter` runs *before*
// `MutexGuard::drop`, which would be unsound since `MutexGuard`
@ -28,4 +38,11 @@ fn main() {
//
// The goal of #54556 is to explain that within a compiler
// diagnostic.
// From Edition 2024:
// Now `MutexGuard::drop` runs *before* `Mutex::drop` because
// the lifetime of the `MutexGuard` is shortened to cover only
// from `if let` until the end of the consequent block.
// Therefore, Niconii's issue is properly solved thanks to the new
// temporary lifetime rule for `if let`s.
}