mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-23 16:47:59 +01:00
Auto merge of #131326 - dingxiangfei2009:issue-130836-attempt-2, r=nikomatsakis
Reduce false positives of tail-expr-drop-order from consumed values (attempt #2) r? `@nikomatsakis` Tracked by #123739. Related to #129864 but not replacing, yet. Related to #130836. This is an implementation of the approach suggested in the [Zulip stream](https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/temporary.20drop.20order.20changes). A new MIR statement `BackwardsIncompatibleDrop` is added to the MIR syntax. The lint now works by inspecting possibly live move paths before at the `BackwardsIncompatibleDrop` location and the actual drop under the current edition, which should be one before Edition 2024 in practice.
This commit is contained in:
commit
3fee0f12e4
@ -641,6 +641,7 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> {
|
||||
| mir::StatementKind::Coverage(..)
|
||||
| mir::StatementKind::Intrinsic(..)
|
||||
| mir::StatementKind::ConstEvalCounter
|
||||
| mir::StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| mir::StatementKind::Nop => {}
|
||||
}
|
||||
}
|
||||
|
@ -652,6 +652,8 @@ impl<'a, 'tcx> ResultsVisitor<'a, 'tcx, Borrowck<'a, 'tcx>> for MirBorrowckCtxt<
|
||||
| StatementKind::Coverage(..)
|
||||
// These do not actually affect borrowck
|
||||
| StatementKind::ConstEvalCounter
|
||||
// This do not affect borrowck
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::StorageLive(..) => {}
|
||||
StatementKind::StorageDead(local) => {
|
||||
self.access_place(
|
||||
|
@ -88,6 +88,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'a, 'tcx> {
|
||||
| StatementKind::Nop
|
||||
| StatementKind::Retag { .. }
|
||||
| StatementKind::Deinit(..)
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::SetDiscriminant { .. } => {
|
||||
bug!("Statement not allowed in this MIR phase")
|
||||
}
|
||||
|
@ -1252,6 +1252,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::PlaceMention(..)
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => {}
|
||||
StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
|
||||
bug!("Statement not allowed in this MIR phase")
|
||||
|
@ -924,6 +924,7 @@ fn codegen_stmt<'tcx>(
|
||||
| StatementKind::FakeRead(..)
|
||||
| StatementKind::Retag { .. }
|
||||
| StatementKind::PlaceMention(..)
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::AscribeUserType(..) => {}
|
||||
|
||||
StatementKind::Coverage { .. } => unreachable!(),
|
||||
|
@ -583,6 +583,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
|
||||
| StatementKind::PlaceMention(..)
|
||||
| StatementKind::Coverage(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => {}
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
| mir::StatementKind::AscribeUserType(..)
|
||||
| mir::StatementKind::ConstEvalCounter
|
||||
| mir::StatementKind::PlaceMention(..)
|
||||
| mir::StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| mir::StatementKind::Nop => {}
|
||||
}
|
||||
}
|
||||
|
@ -609,6 +609,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::Intrinsic(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => {}
|
||||
}
|
||||
}
|
||||
|
@ -143,6 +143,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||
// Defined to do nothing. These are added by optimization passes, to avoid changing the
|
||||
// size of MIR constantly.
|
||||
Nop => {}
|
||||
|
||||
// Only used for temporary lifetime lints
|
||||
BackwardIncompatibleDropHint { .. } => {}
|
||||
}
|
||||
|
||||
interp_ok(())
|
||||
|
@ -17,6 +17,7 @@ use rustc_index::Idx;
|
||||
use rustc_middle::bug;
|
||||
use rustc_middle::middle::region::*;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::lint;
|
||||
use rustc_span::source_map;
|
||||
use tracing::debug;
|
||||
|
||||
@ -167,8 +168,23 @@ fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx h
|
||||
}
|
||||
}
|
||||
if let Some(tail_expr) = blk.expr {
|
||||
if blk.span.edition().at_least_rust_2024() {
|
||||
visitor.terminating_scopes.insert(tail_expr.hir_id.local_id);
|
||||
let local_id = tail_expr.hir_id.local_id;
|
||||
let edition = blk.span.edition();
|
||||
if edition.at_least_rust_2024() {
|
||||
visitor.terminating_scopes.insert(local_id);
|
||||
} else if !visitor
|
||||
.tcx
|
||||
.lints_that_dont_need_to_run(())
|
||||
.contains(&lint::LintId::of(lint::builtin::TAIL_EXPR_DROP_ORDER))
|
||||
{
|
||||
// If this temporary scope will be changing once the codebase adopts Rust 2024,
|
||||
// and we are linting about possible semantic changes that would result,
|
||||
// then record this node-id in the field `backwards_incompatible_scope`
|
||||
// for future reference.
|
||||
visitor
|
||||
.scope_tree
|
||||
.backwards_incompatible_scope
|
||||
.insert(local_id, Scope { id: local_id, data: ScopeData::Node });
|
||||
}
|
||||
visitor.visit_expr(tail_expr);
|
||||
}
|
||||
|
@ -460,6 +460,10 @@ impl<T: Idx> ChunkedBitSet<T> {
|
||||
self.chunks.iter().map(|chunk| chunk.count()).sum()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.chunks.iter().all(|chunk| matches!(chunk, Chunk::Zeros(..) | Chunk::Ones(0)))
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` contains `elem`.
|
||||
#[inline]
|
||||
pub fn contains(&self, elem: T) -> bool {
|
||||
@ -668,12 +672,140 @@ impl<T: Idx> BitRelations<ChunkedBitSet<T>> for ChunkedBitSet<T> {
|
||||
changed
|
||||
}
|
||||
|
||||
fn subtract(&mut self, _other: &ChunkedBitSet<T>) -> bool {
|
||||
unimplemented!("implement if/when necessary");
|
||||
fn subtract(&mut self, other: &ChunkedBitSet<T>) -> bool {
|
||||
assert_eq!(self.domain_size, other.domain_size);
|
||||
debug_assert_eq!(self.chunks.len(), other.chunks.len());
|
||||
|
||||
let mut changed = false;
|
||||
for (mut self_chunk, other_chunk) in self.chunks.iter_mut().zip(other.chunks.iter()) {
|
||||
match (&mut self_chunk, &other_chunk) {
|
||||
(Zeros(..), _) | (_, Zeros(..)) => {}
|
||||
(
|
||||
Ones(self_chunk_domain_size) | Mixed(self_chunk_domain_size, _, _),
|
||||
Ones(other_chunk_domain_size),
|
||||
) => {
|
||||
debug_assert_eq!(self_chunk_domain_size, other_chunk_domain_size);
|
||||
changed = true;
|
||||
*self_chunk = Zeros(*self_chunk_domain_size);
|
||||
}
|
||||
(
|
||||
Ones(self_chunk_domain_size),
|
||||
Mixed(other_chunk_domain_size, other_chunk_count, other_chunk_words),
|
||||
) => {
|
||||
debug_assert_eq!(self_chunk_domain_size, other_chunk_domain_size);
|
||||
changed = true;
|
||||
let num_words = num_words(*self_chunk_domain_size as usize);
|
||||
debug_assert!(num_words > 0 && num_words <= CHUNK_WORDS);
|
||||
let mut tail_mask =
|
||||
1 << (*other_chunk_domain_size - ((num_words - 1) * WORD_BITS) as u16) - 1;
|
||||
let mut self_chunk_words = **other_chunk_words;
|
||||
for word in self_chunk_words[0..num_words].iter_mut().rev() {
|
||||
*word = !*word & tail_mask;
|
||||
tail_mask = u64::MAX;
|
||||
}
|
||||
let self_chunk_count = *self_chunk_domain_size - *other_chunk_count;
|
||||
debug_assert_eq!(
|
||||
self_chunk_count,
|
||||
self_chunk_words[0..num_words]
|
||||
.iter()
|
||||
.map(|w| w.count_ones() as ChunkSize)
|
||||
.sum()
|
||||
);
|
||||
*self_chunk =
|
||||
Mixed(*self_chunk_domain_size, self_chunk_count, Rc::new(self_chunk_words));
|
||||
}
|
||||
(
|
||||
Mixed(
|
||||
self_chunk_domain_size,
|
||||
ref mut self_chunk_count,
|
||||
ref mut self_chunk_words,
|
||||
),
|
||||
Mixed(_other_chunk_domain_size, _other_chunk_count, other_chunk_words),
|
||||
) => {
|
||||
// See [`<Self as BitRelations<ChunkedBitSet<T>>>::union`] for the explanation
|
||||
let op = |a: u64, b: u64| a & !b;
|
||||
let num_words = num_words(*self_chunk_domain_size as usize);
|
||||
if bitwise_changes(
|
||||
&self_chunk_words[0..num_words],
|
||||
&other_chunk_words[0..num_words],
|
||||
op,
|
||||
) {
|
||||
let self_chunk_words = Rc::make_mut(self_chunk_words);
|
||||
let has_changed = bitwise(
|
||||
&mut self_chunk_words[0..num_words],
|
||||
&other_chunk_words[0..num_words],
|
||||
op,
|
||||
);
|
||||
debug_assert!(has_changed);
|
||||
*self_chunk_count = self_chunk_words[0..num_words]
|
||||
.iter()
|
||||
.map(|w| w.count_ones() as ChunkSize)
|
||||
.sum();
|
||||
if *self_chunk_count == 0 {
|
||||
*self_chunk = Zeros(*self_chunk_domain_size);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
fn intersect(&mut self, _other: &ChunkedBitSet<T>) -> bool {
|
||||
unimplemented!("implement if/when necessary");
|
||||
fn intersect(&mut self, other: &ChunkedBitSet<T>) -> bool {
|
||||
assert_eq!(self.domain_size, other.domain_size);
|
||||
debug_assert_eq!(self.chunks.len(), other.chunks.len());
|
||||
|
||||
let mut changed = false;
|
||||
for (mut self_chunk, other_chunk) in self.chunks.iter_mut().zip(other.chunks.iter()) {
|
||||
match (&mut self_chunk, &other_chunk) {
|
||||
(Zeros(..), _) | (_, Ones(..)) => {}
|
||||
(
|
||||
Ones(self_chunk_domain_size),
|
||||
Zeros(other_chunk_domain_size) | Mixed(other_chunk_domain_size, ..),
|
||||
)
|
||||
| (Mixed(self_chunk_domain_size, ..), Zeros(other_chunk_domain_size)) => {
|
||||
debug_assert_eq!(self_chunk_domain_size, other_chunk_domain_size);
|
||||
changed = true;
|
||||
*self_chunk = other_chunk.clone();
|
||||
}
|
||||
(
|
||||
Mixed(
|
||||
self_chunk_domain_size,
|
||||
ref mut self_chunk_count,
|
||||
ref mut self_chunk_words,
|
||||
),
|
||||
Mixed(_other_chunk_domain_size, _other_chunk_count, other_chunk_words),
|
||||
) => {
|
||||
// See [`<Self as BitRelations<ChunkedBitSet<T>>>::union`] for the explanation
|
||||
let op = |a, b| a & b;
|
||||
let num_words = num_words(*self_chunk_domain_size as usize);
|
||||
if bitwise_changes(
|
||||
&self_chunk_words[0..num_words],
|
||||
&other_chunk_words[0..num_words],
|
||||
op,
|
||||
) {
|
||||
let self_chunk_words = Rc::make_mut(self_chunk_words);
|
||||
let has_changed = bitwise(
|
||||
&mut self_chunk_words[0..num_words],
|
||||
&other_chunk_words[0..num_words],
|
||||
op,
|
||||
);
|
||||
debug_assert!(has_changed);
|
||||
*self_chunk_count = self_chunk_words[0..num_words]
|
||||
.iter()
|
||||
.map(|w| w.count_ones() as ChunkSize)
|
||||
.sum();
|
||||
if *self_chunk_count == 0 {
|
||||
*self_chunk = Zeros(*self_chunk_domain_size);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -772,9 +772,6 @@ lint_suspicious_double_ref_clone =
|
||||
lint_suspicious_double_ref_deref =
|
||||
using `.deref()` on a double reference, which returns `{$ty}` instead of dereferencing the inner type
|
||||
|
||||
lint_tail_expr_drop_order = these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
|
||||
.label = these values have significant drop implementation and will observe changes in drop order under Edition 2024
|
||||
|
||||
lint_trailing_semi_macro = trailing semicolon in macro used in expression position
|
||||
.note1 = macro invocations at the end of a block are treated as expressions
|
||||
.note2 = to ignore the value produced by the macro, add a semicolon after the invocation of `{$name}`
|
||||
|
@ -75,7 +75,6 @@ mod redundant_semicolon;
|
||||
mod reference_casting;
|
||||
mod shadowed_into_iter;
|
||||
mod static_mut_refs;
|
||||
mod tail_expr_drop_order;
|
||||
mod traits;
|
||||
mod types;
|
||||
mod unit_bindings;
|
||||
@ -116,7 +115,6 @@ use rustc_middle::ty::TyCtxt;
|
||||
use shadowed_into_iter::ShadowedIntoIter;
|
||||
pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER};
|
||||
use static_mut_refs::*;
|
||||
use tail_expr_drop_order::TailExprDropOrder;
|
||||
use traits::*;
|
||||
use types::*;
|
||||
use unit_bindings::*;
|
||||
@ -240,7 +238,6 @@ late_lint_methods!(
|
||||
AsyncFnInTrait: AsyncFnInTrait,
|
||||
NonLocalDefinitions: NonLocalDefinitions::default(),
|
||||
ImplTraitOvercaptures: ImplTraitOvercaptures,
|
||||
TailExprDropOrder: TailExprDropOrder,
|
||||
IfLetRescope: IfLetRescope::default(),
|
||||
StaticMutRefs: StaticMutRefs,
|
||||
UnqualifiedLocalImports: UnqualifiedLocalImports,
|
||||
|
@ -1,308 +0,0 @@
|
||||
use std::mem::swap;
|
||||
|
||||
use rustc_ast::UnOp;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{self as hir, Block, Expr, ExprKind, LetStmt, Pat, PatKind, QPath, StmtKind};
|
||||
use rustc_macros::LintDiagnostic;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::lint::FutureIncompatibilityReason;
|
||||
use rustc_session::{declare_lint, declare_lint_pass};
|
||||
use rustc_span::Span;
|
||||
use rustc_span::edition::Edition;
|
||||
|
||||
use crate::{LateContext, LateLintPass};
|
||||
|
||||
declare_lint! {
|
||||
/// The `tail_expr_drop_order` lint looks for those values generated at the tail expression location,
|
||||
/// that runs a custom `Drop` destructor.
|
||||
/// Some of them may be dropped earlier in Edition 2024 that they used to in Edition 2021 and prior.
|
||||
/// This lint detects those cases and provides you information on those values and their custom destructor implementations.
|
||||
/// Your discretion on this information is required.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,edition2021
|
||||
/// #![warn(tail_expr_drop_order)]
|
||||
/// struct Droppy(i32);
|
||||
/// impl Droppy {
|
||||
/// fn get(&self) -> i32 {
|
||||
/// self.0
|
||||
/// }
|
||||
/// }
|
||||
/// impl Drop for Droppy {
|
||||
/// fn drop(&mut self) {
|
||||
/// // This is a custom destructor and it induces side-effects that is observable
|
||||
/// // especially when the drop order at a tail expression changes.
|
||||
/// println!("loud drop {}", self.0);
|
||||
/// }
|
||||
/// }
|
||||
/// fn edition_2021() -> i32 {
|
||||
/// let another_droppy = Droppy(0);
|
||||
/// Droppy(1).get()
|
||||
/// }
|
||||
/// fn main() {
|
||||
/// edition_2021();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// {{produces}}
|
||||
///
|
||||
/// ### Explanation
|
||||
///
|
||||
/// In tail expression of blocks or function bodies,
|
||||
/// values of type with significant `Drop` implementation has an ill-specified drop order
|
||||
/// before Edition 2024 so that they are dropped only after dropping local variables.
|
||||
/// Edition 2024 introduces a new rule with drop orders for them,
|
||||
/// so that they are dropped first before dropping local variables.
|
||||
///
|
||||
/// A significant `Drop::drop` 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.
|
||||
///
|
||||
/// This lint only points out the issue with `Droppy`, which will be dropped before `another_droppy`
|
||||
/// does in Edition 2024.
|
||||
/// No fix will be proposed by this lint.
|
||||
/// However, the most probable fix is to hoist `Droppy` into its own local variable binding.
|
||||
/// ```rust
|
||||
/// struct Droppy(i32);
|
||||
/// impl Droppy {
|
||||
/// fn get(&self) -> i32 {
|
||||
/// self.0
|
||||
/// }
|
||||
/// }
|
||||
/// fn edition_2024() -> i32 {
|
||||
/// let value = Droppy(0);
|
||||
/// let another_droppy = Droppy(1);
|
||||
/// value.get()
|
||||
/// }
|
||||
/// ```
|
||||
pub TAIL_EXPR_DROP_ORDER,
|
||||
Allow,
|
||||
"Detect and warn on significant change in drop order in tail expression location",
|
||||
@future_incompatible = FutureIncompatibleInfo {
|
||||
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
|
||||
reference: "issue #123739 <https://github.com/rust-lang/rust/issues/123739>",
|
||||
};
|
||||
}
|
||||
|
||||
declare_lint_pass!(TailExprDropOrder => [TAIL_EXPR_DROP_ORDER]);
|
||||
|
||||
impl TailExprDropOrder {
|
||||
fn check_fn_or_closure<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
fn_kind: hir::intravisit::FnKind<'tcx>,
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
def_id: rustc_span::def_id::LocalDefId,
|
||||
) {
|
||||
let mut locals = vec![];
|
||||
if matches!(fn_kind, hir::intravisit::FnKind::Closure) {
|
||||
for &capture in cx.tcx.closure_captures(def_id) {
|
||||
if matches!(capture.info.capture_kind, ty::UpvarCapture::ByValue)
|
||||
&& capture.place.ty().has_significant_drop(cx.tcx, cx.typing_env())
|
||||
{
|
||||
locals.push(capture.var_ident.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
for param in body.params {
|
||||
if cx
|
||||
.typeck_results()
|
||||
.node_type(param.hir_id)
|
||||
.has_significant_drop(cx.tcx, cx.typing_env())
|
||||
{
|
||||
locals.push(param.span);
|
||||
}
|
||||
}
|
||||
if let hir::ExprKind::Block(block, _) = body.value.kind {
|
||||
LintVisitor { cx, locals }.check_block_inner(block);
|
||||
} else {
|
||||
LintTailExpr { cx, locals: &locals, is_root_tail_expr: true }.visit_expr(body.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for TailExprDropOrder {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
fn_kind: hir::intravisit::FnKind<'tcx>,
|
||||
_: &'tcx hir::FnDecl<'tcx>,
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
_: Span,
|
||||
def_id: rustc_span::def_id::LocalDefId,
|
||||
) {
|
||||
if !body.value.span.edition().at_least_rust_2024() {
|
||||
Self::check_fn_or_closure(cx, fn_kind, body, def_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LintVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
// We only record locals that have significant drops
|
||||
locals: Vec<Span>,
|
||||
}
|
||||
|
||||
struct LocalCollector<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
locals: &'a mut Vec<Span>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for LocalCollector<'a, 'tcx> {
|
||||
type Result = ();
|
||||
fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
|
||||
if let PatKind::Binding(_binding_mode, id, ident, pat) = pat.kind {
|
||||
let ty = self.cx.typeck_results().node_type(id);
|
||||
if ty.has_significant_drop(self.cx.tcx, self.cx.typing_env()) {
|
||||
self.locals.push(ident.span);
|
||||
}
|
||||
if let Some(pat) = pat {
|
||||
self.visit_pat(pat);
|
||||
}
|
||||
} else {
|
||||
intravisit::walk_pat(self, pat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for LintVisitor<'a, 'tcx> {
|
||||
fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
|
||||
let mut locals = <_>::default();
|
||||
swap(&mut locals, &mut self.locals);
|
||||
self.check_block_inner(block);
|
||||
swap(&mut locals, &mut self.locals);
|
||||
}
|
||||
fn visit_local(&mut self, local: &'tcx LetStmt<'tcx>) {
|
||||
LocalCollector { cx: self.cx, locals: &mut self.locals }.visit_local(local);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> LintVisitor<'a, 'tcx> {
|
||||
fn check_block_inner(&mut self, block: &Block<'tcx>) {
|
||||
if block.span.at_least_rust_2024() {
|
||||
// We only lint up to Edition 2021
|
||||
return;
|
||||
}
|
||||
let Some(tail_expr) = block.expr else { return };
|
||||
for stmt in block.stmts {
|
||||
match stmt.kind {
|
||||
StmtKind::Let(let_stmt) => self.visit_local(let_stmt),
|
||||
StmtKind::Item(_) => {}
|
||||
StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e),
|
||||
}
|
||||
}
|
||||
if self.locals.is_empty() {
|
||||
return;
|
||||
}
|
||||
LintTailExpr { cx: self.cx, locals: &self.locals, is_root_tail_expr: true }
|
||||
.visit_expr(tail_expr);
|
||||
}
|
||||
}
|
||||
|
||||
struct LintTailExpr<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
is_root_tail_expr: bool,
|
||||
locals: &'a [Span],
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> LintTailExpr<'a, 'tcx> {
|
||||
fn expr_eventually_point_into_local(mut expr: &Expr<'tcx>) -> bool {
|
||||
loop {
|
||||
match expr.kind {
|
||||
ExprKind::Index(access, _, _) | ExprKind::Field(access, _) => expr = access,
|
||||
ExprKind::AddrOf(_, _, referee) | ExprKind::Unary(UnOp::Deref, referee) => {
|
||||
expr = referee
|
||||
}
|
||||
ExprKind::Path(_)
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind
|
||||
&& let [local, ..] = path.segments
|
||||
&& let Res::Local(_) = local.res =>
|
||||
{
|
||||
return true;
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_generates_nonlocal_droppy_value(&self, expr: &Expr<'tcx>) -> bool {
|
||||
if Self::expr_eventually_point_into_local(expr) {
|
||||
return false;
|
||||
}
|
||||
self.cx
|
||||
.typeck_results()
|
||||
.expr_ty(expr)
|
||||
.has_significant_drop(self.cx.tcx, self.cx.typing_env())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for LintTailExpr<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
|
||||
if self.is_root_tail_expr {
|
||||
self.is_root_tail_expr = false;
|
||||
} else if self.expr_generates_nonlocal_droppy_value(expr) {
|
||||
self.cx.tcx.emit_node_span_lint(
|
||||
TAIL_EXPR_DROP_ORDER,
|
||||
expr.hir_id,
|
||||
expr.span,
|
||||
TailExprDropOrderLint { spans: self.locals.to_vec() },
|
||||
);
|
||||
return;
|
||||
}
|
||||
match expr.kind {
|
||||
ExprKind::Match(scrutinee, _, _) => self.visit_expr(scrutinee),
|
||||
|
||||
ExprKind::ConstBlock(_)
|
||||
| ExprKind::Array(_)
|
||||
| ExprKind::Break(_, _)
|
||||
| ExprKind::Continue(_)
|
||||
| ExprKind::Ret(_)
|
||||
| ExprKind::Become(_)
|
||||
| ExprKind::Yield(_, _)
|
||||
| ExprKind::InlineAsm(_)
|
||||
| ExprKind::If(_, _, _)
|
||||
| ExprKind::Loop(_, _, _, _)
|
||||
| ExprKind::Closure(_)
|
||||
| ExprKind::DropTemps(_)
|
||||
| ExprKind::OffsetOf(_, _)
|
||||
| ExprKind::Assign(_, _, _)
|
||||
| ExprKind::AssignOp(_, _, _)
|
||||
| ExprKind::Lit(_)
|
||||
| ExprKind::Err(_) => {}
|
||||
|
||||
ExprKind::MethodCall(_, _, _, _)
|
||||
| ExprKind::Call(_, _)
|
||||
| ExprKind::Type(_, _)
|
||||
| ExprKind::Tup(_)
|
||||
| ExprKind::Binary(_, _, _)
|
||||
| ExprKind::Unary(_, _)
|
||||
| ExprKind::Path(_)
|
||||
| ExprKind::Let(_)
|
||||
| ExprKind::Cast(_, _)
|
||||
| ExprKind::Field(_, _)
|
||||
| ExprKind::Index(_, _, _)
|
||||
| ExprKind::AddrOf(_, _, _)
|
||||
| ExprKind::Struct(_, _, _)
|
||||
| ExprKind::Repeat(_, _) => intravisit::walk_expr(self, expr),
|
||||
|
||||
ExprKind::Block(_, _) => {
|
||||
// We do not lint further because the drop order stays the same inside the block
|
||||
}
|
||||
}
|
||||
}
|
||||
fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
|
||||
LintVisitor { cx: self.cx, locals: <_>::default() }.check_block_inner(block);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(lint_tail_expr_drop_order)]
|
||||
struct TailExprDropOrderLint {
|
||||
#[label]
|
||||
pub spans: Vec<Span>,
|
||||
}
|
@ -101,6 +101,7 @@ declare_lint_pass! {
|
||||
SINGLE_USE_LIFETIMES,
|
||||
SOFT_UNSTABLE,
|
||||
STABLE_FEATURES,
|
||||
TAIL_EXPR_DROP_ORDER,
|
||||
TEST_UNSTABLE_LINT,
|
||||
TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
|
||||
TRIVIAL_CASTS,
|
||||
@ -4994,6 +4995,83 @@ declare_lint! {
|
||||
"detects pointer to integer transmutes in const functions and associated constants",
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// The `tail_expr_drop_order` lint looks for those values generated at the tail expression location,
|
||||
/// that runs a custom `Drop` destructor.
|
||||
/// Some of them may be dropped earlier in Edition 2024 that they used to in Edition 2021 and prior.
|
||||
/// This lint detects those cases and provides you information on those values and their custom destructor implementations.
|
||||
/// Your discretion on this information is required.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,edition2021
|
||||
/// #![warn(tail_expr_drop_order)]
|
||||
/// struct Droppy(i32);
|
||||
/// impl Droppy {
|
||||
/// fn get(&self) -> i32 {
|
||||
/// self.0
|
||||
/// }
|
||||
/// }
|
||||
/// impl Drop for Droppy {
|
||||
/// fn drop(&mut self) {
|
||||
/// // This is a custom destructor and it induces side-effects that is observable
|
||||
/// // especially when the drop order at a tail expression changes.
|
||||
/// println!("loud drop {}", self.0);
|
||||
/// }
|
||||
/// }
|
||||
/// fn edition_2021() -> i32 {
|
||||
/// let another_droppy = Droppy(0);
|
||||
/// Droppy(1).get()
|
||||
/// }
|
||||
/// fn main() {
|
||||
/// edition_2021();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// {{produces}}
|
||||
///
|
||||
/// ### Explanation
|
||||
///
|
||||
/// In tail expression of blocks or function bodies,
|
||||
/// values of type with significant `Drop` implementation has an ill-specified drop order
|
||||
/// before Edition 2024 so that they are dropped only after dropping local variables.
|
||||
/// Edition 2024 introduces a new rule with drop orders for them,
|
||||
/// so that they are dropped first before dropping local variables.
|
||||
///
|
||||
/// A significant `Drop::drop` 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.
|
||||
///
|
||||
/// This lint only points out the issue with `Droppy`, which will be dropped before `another_droppy`
|
||||
/// does in Edition 2024.
|
||||
/// No fix will be proposed by this lint.
|
||||
/// However, the most probable fix is to hoist `Droppy` into its own local variable binding.
|
||||
/// ```rust
|
||||
/// struct Droppy(i32);
|
||||
/// impl Droppy {
|
||||
/// fn get(&self) -> i32 {
|
||||
/// self.0
|
||||
/// }
|
||||
/// }
|
||||
/// fn edition_2024() -> i32 {
|
||||
/// let value = Droppy(0);
|
||||
/// let another_droppy = Droppy(1);
|
||||
/// value.get()
|
||||
/// }
|
||||
/// ```
|
||||
pub TAIL_EXPR_DROP_ORDER,
|
||||
Allow,
|
||||
"Detect and warn on significant change in drop order in tail expression location",
|
||||
@future_incompatible = FutureIncompatibleInfo {
|
||||
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
|
||||
reference: "issue #123739 <https://github.com/rust-lang/rust/issues/123739>",
|
||||
};
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// The `rust_2024_guarded_string_incompatible_syntax` lint detects `#` tokens
|
||||
/// that will be parsed as part of a guarded string literal in Rust 2024.
|
||||
|
@ -236,6 +236,11 @@ pub struct ScopeTree {
|
||||
/// during type check based on a traversal of the AST.
|
||||
pub rvalue_candidates: HirIdMap<RvalueCandidateType>,
|
||||
|
||||
/// Backwards incompatible scoping that will be introduced in future editions.
|
||||
/// This information is used later for linting to identify locals and
|
||||
/// temporary values that will receive backwards-incompatible drop orders.
|
||||
pub backwards_incompatible_scope: UnordMap<hir::ItemLocalId, Scope>,
|
||||
|
||||
/// If there are any `yield` nested within a scope, this map
|
||||
/// stores the `Span` of the last one and its index in the
|
||||
/// postorder of the Visitor traversal on the HIR.
|
||||
|
@ -834,6 +834,11 @@ impl Debug for Statement<'_> {
|
||||
Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"),
|
||||
ConstEvalCounter => write!(fmt, "ConstEvalCounter"),
|
||||
Nop => write!(fmt, "nop"),
|
||||
BackwardIncompatibleDropHint { ref place, reason: _ } => {
|
||||
// For now, we don't record the reason because there is only one use case,
|
||||
// which is to report breaking change in drop order by Edition 2024
|
||||
write!(fmt, "backward incompatible drop({place:?})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -432,6 +432,18 @@ pub enum StatementKind<'tcx> {
|
||||
|
||||
/// No-op. Useful for deleting instructions without affecting statement indices.
|
||||
Nop,
|
||||
|
||||
/// Marker statement indicating where `place` would be dropped.
|
||||
/// This is semantically equivalent to `Nop`, so codegen and MIRI should interpret this
|
||||
/// statement as such.
|
||||
/// The only use case of this statement is for linting in MIR to detect temporary lifetime
|
||||
/// changes.
|
||||
BackwardIncompatibleDropHint {
|
||||
/// Place to drop
|
||||
place: Box<Place<'tcx>>,
|
||||
/// Reason for backward incompatibility
|
||||
reason: BackwardIncompatibleDropReason,
|
||||
},
|
||||
}
|
||||
|
||||
impl StatementKind<'_> {
|
||||
@ -452,6 +464,7 @@ impl StatementKind<'_> {
|
||||
StatementKind::Intrinsic(..) => "Intrinsic",
|
||||
StatementKind::ConstEvalCounter => "ConstEvalCounter",
|
||||
StatementKind::Nop => "Nop",
|
||||
StatementKind::BackwardIncompatibleDropHint { .. } => "BackwardIncompatibleDropHint",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -897,6 +910,21 @@ pub enum TerminatorKind<'tcx> {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
TyEncodable,
|
||||
TyDecodable,
|
||||
Hash,
|
||||
HashStable,
|
||||
PartialEq,
|
||||
TypeFoldable,
|
||||
TypeVisitable
|
||||
)]
|
||||
pub enum BackwardIncompatibleDropReason {
|
||||
Edition2024,
|
||||
}
|
||||
|
||||
impl TerminatorKind<'_> {
|
||||
/// Returns a simple string representation of a `TerminatorKind` variant, independent of any
|
||||
/// values it might hold (e.g. `TerminatorKind::Call` always returns `"Call"`).
|
||||
|
@ -452,6 +452,7 @@ macro_rules! make_mir_visitor {
|
||||
}
|
||||
StatementKind::ConstEvalCounter => {}
|
||||
StatementKind::Nop => {}
|
||||
StatementKind::BackwardIncompatibleDropHint { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -360,6 +360,14 @@ impl<'tcx> Key for (ty::ParamEnv<'tcx>, ty::TraitRef<'tcx>) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Key for ty::ParamEnvAnd<'tcx, Ty<'tcx>> {
|
||||
type Cache<V> = DefaultCache<Self, V>;
|
||||
|
||||
fn default_span(&self, _tcx: TyCtxt<'_>) -> Span {
|
||||
DUMMY_SP
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Key for ty::TraitRef<'tcx> {
|
||||
type Cache<V> = DefaultCache<Self, V>;
|
||||
|
||||
|
@ -1448,6 +1448,28 @@ rustc_queries! {
|
||||
cache_on_disk_if { false }
|
||||
}
|
||||
|
||||
/// Returns a list of types which (a) have a potentially significant destructor
|
||||
/// and (b) may be dropped as a result of dropping a value of some type `ty`
|
||||
/// (in the given environment).
|
||||
///
|
||||
/// The idea of "significant" drop is somewhat informal and is used only for
|
||||
/// diagnostics and edition migrations. The idea is that a significant drop may have
|
||||
/// some visible side-effect on execution; freeing memory is NOT considered a side-effect.
|
||||
/// The rules are as follows:
|
||||
/// * Type with no explicit drop impl do not have significant drop.
|
||||
/// * Types with a drop impl are assumed to have significant drop unless they have a `#[rustc_insignificant_dtor]` annotation.
|
||||
///
|
||||
/// Note that insignificant drop is a "shallow" property. A type like `Vec<LockGuard>` does not
|
||||
/// have significant drop but the type `LockGuard` does, and so if `ty = Vec<LockGuard>`
|
||||
/// then the return value would be `&[LockGuard]`.
|
||||
/// *IMPORTANT*: *DO NOT* run this query before promoted MIR body is constructed,
|
||||
/// because this query partially depends on that query.
|
||||
/// Otherwise, there is a risk of query cycles.
|
||||
query list_significant_drop_tys(ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> &'tcx ty::List<Ty<'tcx>> {
|
||||
desc { |tcx| "computing when `{}` has a significant destructor", ty.value }
|
||||
cache_on_disk_if { false }
|
||||
}
|
||||
|
||||
/// Computes the layout of a type. Note that this implicitly
|
||||
/// executes in "reveal all" mode, and will normalize the input type.
|
||||
query layout_of(
|
||||
|
@ -247,13 +247,24 @@ pub struct Expr<'tcx> {
|
||||
pub ty: Ty<'tcx>,
|
||||
|
||||
/// The lifetime of this expression if it should be spilled into a
|
||||
/// temporary; should be `None` only if in a constant context
|
||||
pub temp_lifetime: Option<region::Scope>,
|
||||
/// temporary
|
||||
pub temp_lifetime: TempLifetime,
|
||||
|
||||
/// span of the expression in the source
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// Temporary lifetime information for THIR expressions
|
||||
#[derive(Clone, Copy, Debug, HashStable)]
|
||||
pub struct TempLifetime {
|
||||
/// Lifetime for temporaries as expected.
|
||||
/// This should be `None` in a constant context.
|
||||
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>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, HashStable)]
|
||||
pub enum ExprKind<'tcx> {
|
||||
/// `Scope`s are used to explicitly mark destruction scopes,
|
||||
@ -1087,7 +1098,7 @@ mod size_asserts {
|
||||
use super::*;
|
||||
// tidy-alphabetical-start
|
||||
static_assert_size!(Block, 48);
|
||||
static_assert_size!(Expr<'_>, 64);
|
||||
static_assert_size!(Expr<'_>, 72);
|
||||
static_assert_size!(ExprKind<'_>, 40);
|
||||
static_assert_size!(Pat<'_>, 64);
|
||||
static_assert_size!(PatKind<'_>, 48);
|
||||
|
@ -18,15 +18,17 @@ impl RvalueScopes {
|
||||
}
|
||||
|
||||
/// Returns the scope when the temp created by `expr_id` will be cleaned up.
|
||||
/// It also emits a lint on potential backwards incompatible change to the temporary scope
|
||||
/// which is *for now* always shortening.
|
||||
pub fn temporary_scope(
|
||||
&self,
|
||||
region_scope_tree: &ScopeTree,
|
||||
expr_id: hir::ItemLocalId,
|
||||
) -> Option<Scope> {
|
||||
) -> (Option<Scope>, Option<Scope>) {
|
||||
// Check for a designated rvalue scope.
|
||||
if let Some(&s) = self.map.get(&expr_id) {
|
||||
debug!("temporary_scope({expr_id:?}) = {s:?} [custom]");
|
||||
return s;
|
||||
return (s, None);
|
||||
}
|
||||
|
||||
// Otherwise, locate the innermost terminating scope
|
||||
@ -34,27 +36,40 @@ impl RvalueScopes {
|
||||
// have an enclosing scope, hence no scope will be
|
||||
// returned.
|
||||
let mut id = Scope { id: expr_id, data: ScopeData::Node };
|
||||
let mut backwards_incompatible = None;
|
||||
|
||||
while let Some(&(p, _)) = region_scope_tree.parent_map.get(&id) {
|
||||
match p.data {
|
||||
ScopeData::Destruction => {
|
||||
debug!("temporary_scope({expr_id:?}) = {id:?} [enclosing]");
|
||||
return Some(id);
|
||||
return (Some(id), backwards_incompatible);
|
||||
}
|
||||
ScopeData::IfThenRescope => {
|
||||
debug!("temporary_scope({expr_id:?}) = {p:?} [enclosing]");
|
||||
return Some(p);
|
||||
return (Some(p), backwards_incompatible);
|
||||
}
|
||||
ScopeData::Node
|
||||
| ScopeData::CallSite
|
||||
| ScopeData::Arguments
|
||||
| ScopeData::IfThen
|
||||
| ScopeData::Remainder(_) => id = p,
|
||||
| ScopeData::Remainder(_) => {
|
||||
// If we haven't already passed through a backwards-incompatible node,
|
||||
// then check if we are passing through one now and record it if so.
|
||||
// This is for now only working for cases where a temporary lifetime is
|
||||
// *shortened*.
|
||||
if backwards_incompatible.is_none() {
|
||||
backwards_incompatible = region_scope_tree
|
||||
.backwards_incompatible_scope
|
||||
.get(&p.item_local_id())
|
||||
.copied();
|
||||
}
|
||||
id = p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("temporary_scope({expr_id:?}) = None");
|
||||
None
|
||||
(None, backwards_incompatible)
|
||||
}
|
||||
|
||||
/// Make an association between a sub-expression and an extended lifetime
|
||||
|
@ -1,6 +1,5 @@
|
||||
//! See docs in build/expr/mod.rs
|
||||
|
||||
use rustc_middle::middle::region;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::thir::*;
|
||||
use tracing::{debug, instrument};
|
||||
@ -9,6 +8,12 @@ use crate::build::expr::category::Category;
|
||||
use crate::build::{BlockAnd, BlockAndExtension, Builder, NeedsTemporary};
|
||||
|
||||
impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
/// Construct a temporary lifetime restricted to just the local scope
|
||||
pub(crate) fn local_temp_lifetime(&self) -> TempLifetime {
|
||||
let local_scope = self.local_scope();
|
||||
TempLifetime { temp_lifetime: Some(local_scope), backwards_incompatible: None }
|
||||
}
|
||||
|
||||
/// Returns an operand suitable for use until the end of the current
|
||||
/// scope expression.
|
||||
///
|
||||
@ -21,8 +26,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
block: BasicBlock,
|
||||
expr_id: ExprId,
|
||||
) -> BlockAnd<Operand<'tcx>> {
|
||||
let local_scope = self.local_scope();
|
||||
self.as_operand(block, Some(local_scope), expr_id, LocalInfo::Boring, NeedsTemporary::Maybe)
|
||||
self.as_operand(
|
||||
block,
|
||||
self.local_temp_lifetime(),
|
||||
expr_id,
|
||||
LocalInfo::Boring,
|
||||
NeedsTemporary::Maybe,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an operand suitable for use until the end of the current scope expression and
|
||||
@ -80,8 +90,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
block: BasicBlock,
|
||||
expr: ExprId,
|
||||
) -> BlockAnd<Operand<'tcx>> {
|
||||
let local_scope = self.local_scope();
|
||||
self.as_call_operand(block, Some(local_scope), expr)
|
||||
self.as_call_operand(block, self.local_temp_lifetime(), expr)
|
||||
}
|
||||
|
||||
/// Compile `expr` into a value that can be used as an operand.
|
||||
@ -102,7 +111,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
pub(crate) fn as_operand(
|
||||
&mut self,
|
||||
mut block: BasicBlock,
|
||||
scope: Option<region::Scope>,
|
||||
scope: TempLifetime,
|
||||
expr_id: ExprId,
|
||||
local_info: LocalInfo<'tcx>,
|
||||
needs_temporary: NeedsTemporary,
|
||||
@ -146,7 +155,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
pub(crate) fn as_call_operand(
|
||||
&mut self,
|
||||
mut block: BasicBlock,
|
||||
scope: Option<region::Scope>,
|
||||
scope: TempLifetime,
|
||||
expr_id: ExprId,
|
||||
) -> BlockAnd<Operand<'tcx>> {
|
||||
let this = self;
|
||||
|
@ -6,7 +6,6 @@ use std::iter;
|
||||
use rustc_abi::{FIRST_VARIANT, FieldIdx, VariantIdx};
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_middle::hir::place::{Projection as HirProjection, ProjectionKind as HirProjectionKind};
|
||||
use rustc_middle::middle::region;
|
||||
use rustc_middle::mir::AssertKind::BoundsCheck;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::thir::*;
|
||||
@ -598,7 +597,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
index: ExprId,
|
||||
mutability: Mutability,
|
||||
fake_borrow_temps: Option<&mut Vec<Local>>,
|
||||
temp_lifetime: Option<region::Scope>,
|
||||
temp_lifetime: TempLifetime,
|
||||
expr_span: Span,
|
||||
source_info: SourceInfo,
|
||||
) -> BlockAnd<PlaceBuilder<'tcx>> {
|
||||
|
@ -33,14 +33,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
expr_id: ExprId,
|
||||
) -> BlockAnd<Rvalue<'tcx>> {
|
||||
let local_scope = self.local_scope();
|
||||
self.as_rvalue(block, Some(local_scope), expr_id)
|
||||
self.as_rvalue(
|
||||
block,
|
||||
TempLifetime { temp_lifetime: Some(local_scope), backwards_incompatible: None },
|
||||
expr_id,
|
||||
)
|
||||
}
|
||||
|
||||
/// Compile `expr`, yielding an rvalue.
|
||||
pub(crate) fn as_rvalue(
|
||||
&mut self,
|
||||
mut block: BasicBlock,
|
||||
scope: Option<region::Scope>,
|
||||
scope: TempLifetime,
|
||||
expr_id: ExprId,
|
||||
) -> BlockAnd<Rvalue<'tcx>> {
|
||||
let this = self;
|
||||
@ -171,7 +175,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
source_info,
|
||||
kind: StatementKind::StorageLive(result),
|
||||
});
|
||||
if let Some(scope) = scope {
|
||||
if let Some(scope) = scope.temp_lifetime {
|
||||
// schedule a shallow free of that memory, lest we unwind:
|
||||
this.schedule_drop_storage_and_value(expr_span, scope, result);
|
||||
}
|
||||
@ -445,7 +449,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
block = this.limit_capture_mutability(
|
||||
upvar_expr.span,
|
||||
upvar_expr.ty,
|
||||
scope,
|
||||
scope.temp_lifetime,
|
||||
block,
|
||||
arg,
|
||||
)
|
||||
@ -705,7 +709,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
&mut self,
|
||||
mut block: BasicBlock,
|
||||
value: ExprId,
|
||||
scope: Option<region::Scope>,
|
||||
scope: TempLifetime,
|
||||
outer_source_info: SourceInfo,
|
||||
) -> BlockAnd<Rvalue<'tcx>> {
|
||||
let this = self;
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
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::*;
|
||||
@ -17,7 +16,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
pub(crate) fn as_temp(
|
||||
&mut self,
|
||||
block: BasicBlock,
|
||||
temp_lifetime: Option<region::Scope>,
|
||||
temp_lifetime: TempLifetime,
|
||||
expr_id: ExprId,
|
||||
mutability: Mutability,
|
||||
) -> BlockAnd<Local> {
|
||||
@ -31,7 +30,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
fn as_temp_inner(
|
||||
&mut self,
|
||||
mut block: BasicBlock,
|
||||
temp_lifetime: Option<region::Scope>,
|
||||
temp_lifetime: TempLifetime,
|
||||
expr_id: ExprId,
|
||||
mutability: Mutability,
|
||||
) -> BlockAnd<Local> {
|
||||
@ -47,8 +46,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
}
|
||||
|
||||
let expr_ty = expr.ty;
|
||||
let deduplicate_temps =
|
||||
this.fixed_temps_scope.is_some() && this.fixed_temps_scope == temp_lifetime;
|
||||
let deduplicate_temps = this.fixed_temps_scope.is_some()
|
||||
&& this.fixed_temps_scope == temp_lifetime.temp_lifetime;
|
||||
let temp = if deduplicate_temps && let Some(temp_index) = this.fixed_temps.get(&expr_id) {
|
||||
*temp_index
|
||||
} else {
|
||||
@ -76,7 +75,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
LocalInfo::BlockTailTemp(tail_info)
|
||||
}
|
||||
|
||||
_ if let Some(Scope { data: ScopeData::IfThenRescope, id }) = temp_lifetime => {
|
||||
_ if let Some(Scope { data: ScopeData::IfThenRescope, id }) =
|
||||
temp_lifetime.temp_lifetime =>
|
||||
{
|
||||
LocalInfo::IfThenRescopeTemp {
|
||||
if_then: HirId { owner: this.hir_id.owner, local_id: id },
|
||||
}
|
||||
@ -117,7 +118,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
// Anything with a shorter lifetime (e.g the `&foo()` in
|
||||
// `bar(&foo())` or anything within a block will keep the
|
||||
// regular drops just like runtime code.
|
||||
if let Some(temp_lifetime) = temp_lifetime {
|
||||
if let Some(temp_lifetime) = temp_lifetime.temp_lifetime {
|
||||
this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Storage);
|
||||
}
|
||||
}
|
||||
@ -125,10 +126,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
|
||||
block = this.expr_into_dest(temp_place, block, expr_id).into_block();
|
||||
|
||||
if let Some(temp_lifetime) = temp_lifetime {
|
||||
if let Some(temp_lifetime) = temp_lifetime.temp_lifetime {
|
||||
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);
|
||||
}
|
||||
|
||||
block.and(temp)
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
// (#66975) Source could be a const of type `!`, so has to
|
||||
// exist in the generated MIR.
|
||||
unpack!(
|
||||
block = this.as_temp(block, Some(this.local_scope()), source, Mutability::Mut)
|
||||
block =
|
||||
this.as_temp(block, this.local_temp_lifetime(), source, Mutability::Mut)
|
||||
);
|
||||
|
||||
// This is an optimization. If the expression was a call then we already have an
|
||||
@ -321,7 +322,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
let is_union = adt_def.is_union();
|
||||
let active_field_index = is_union.then(|| fields[0].name);
|
||||
|
||||
let scope = this.local_scope();
|
||||
let scope = this.local_temp_lifetime();
|
||||
|
||||
// first process the set of fields that were provided
|
||||
// (evaluating them in order given by user)
|
||||
@ -333,7 +334,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
unpack!(
|
||||
block = this.as_operand(
|
||||
block,
|
||||
Some(scope),
|
||||
scope,
|
||||
f.expr,
|
||||
LocalInfo::AggregateTemp,
|
||||
NeedsTemporary::Maybe,
|
||||
@ -548,15 +549,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
}
|
||||
|
||||
ExprKind::Yield { value } => {
|
||||
let scope = this.local_scope();
|
||||
let scope = this.local_temp_lifetime();
|
||||
let value = unpack!(
|
||||
block = this.as_operand(
|
||||
block,
|
||||
Some(scope),
|
||||
value,
|
||||
LocalInfo::Boring,
|
||||
NeedsTemporary::No
|
||||
)
|
||||
block =
|
||||
this.as_operand(block, scope, value, LocalInfo::Boring, NeedsTemporary::No)
|
||||
);
|
||||
let resume = this.cfg.start_new_block();
|
||||
this.cfg.terminate(block, source_info, TerminatorKind::Yield {
|
||||
|
@ -172,8 +172,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
None
|
||||
};
|
||||
|
||||
let temp =
|
||||
unpack!(block = this.as_temp(block, statement_scope, expr_id, Mutability::Not));
|
||||
let temp = unpack!(
|
||||
block = this.as_temp(
|
||||
block,
|
||||
TempLifetime {
|
||||
temp_lifetime: statement_scope,
|
||||
backwards_incompatible: None
|
||||
},
|
||||
expr_id,
|
||||
Mutability::Not
|
||||
)
|
||||
);
|
||||
|
||||
if let Some(span) = adjusted_span {
|
||||
this.local_decls[temp].source_info.span = span;
|
||||
|
@ -202,8 +202,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
// Increment the decision depth, in case we encounter boolean expressions
|
||||
// further down.
|
||||
this.mcdc_increment_depth_if_enabled();
|
||||
let place =
|
||||
unpack!(block = this.as_temp(block, Some(temp_scope), expr_id, mutability));
|
||||
let place = unpack!(
|
||||
block = this.as_temp(
|
||||
block,
|
||||
TempLifetime {
|
||||
temp_lifetime: Some(temp_scope),
|
||||
backwards_incompatible: None
|
||||
},
|
||||
expr_id,
|
||||
mutability
|
||||
)
|
||||
);
|
||||
this.mcdc_decrement_depth_if_enabled();
|
||||
|
||||
let operand = Operand::Move(Place::from(place));
|
||||
|
@ -89,7 +89,7 @@ use rustc_index::{IndexSlice, IndexVec};
|
||||
use rustc_middle::middle::region;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::thir::{ExprId, LintLevel};
|
||||
use rustc_middle::{bug, span_bug};
|
||||
use rustc_middle::{bug, span_bug, ty};
|
||||
use rustc_session::lint::Level;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::{DUMMY_SP, Span};
|
||||
@ -151,6 +151,9 @@ struct DropData {
|
||||
|
||||
/// Whether this is a value Drop or a StorageDead.
|
||||
kind: DropKind,
|
||||
|
||||
/// Whether this is a backwards-incompatible drop lint
|
||||
backwards_incompatible_lint: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@ -274,8 +277,12 @@ impl DropTree {
|
||||
// represents the block in the tree that should be jumped to once all
|
||||
// of the required drops have been performed.
|
||||
let fake_source_info = SourceInfo::outermost(DUMMY_SP);
|
||||
let fake_data =
|
||||
DropData { source_info: fake_source_info, local: Local::MAX, kind: DropKind::Storage };
|
||||
let fake_data = DropData {
|
||||
source_info: fake_source_info,
|
||||
local: Local::MAX,
|
||||
kind: DropKind::Storage,
|
||||
backwards_incompatible_lint: false,
|
||||
};
|
||||
let drops = IndexVec::from_raw(vec![DropNode { data: fake_data, next: DropIdx::MAX }]);
|
||||
Self { drops, entry_points: Vec::new(), existing_drops_map: FxHashMap::default() }
|
||||
}
|
||||
@ -763,7 +770,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
let local =
|
||||
place.as_local().unwrap_or_else(|| bug!("projection in tail call args"));
|
||||
|
||||
Some(DropData { source_info, local, kind: DropKind::Value })
|
||||
Some(DropData {
|
||||
source_info,
|
||||
local,
|
||||
kind: DropKind::Value,
|
||||
backwards_incompatible_lint: false,
|
||||
})
|
||||
}
|
||||
Operand::Constant(_) => None,
|
||||
})
|
||||
@ -1019,9 +1031,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
if local.index() <= self.arg_count {
|
||||
span_bug!(
|
||||
span,
|
||||
"`schedule_drop` called with local {:?} and arg_count {}",
|
||||
"`schedule_drop` called with body argument {:?} \
|
||||
but its storage does not require a drop",
|
||||
local,
|
||||
self.arg_count,
|
||||
)
|
||||
}
|
||||
false
|
||||
@ -1089,6 +1101,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
source_info: SourceInfo { span: scope_end, scope: scope.source_scope },
|
||||
local,
|
||||
kind: drop_kind,
|
||||
backwards_incompatible_lint: false,
|
||||
});
|
||||
|
||||
return;
|
||||
@ -1098,6 +1111,45 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
span_bug!(span, "region scope {:?} not in scope to drop {:?}", region_scope, local);
|
||||
}
|
||||
|
||||
/// Schedule emission of a backwards incompatible drop lint hint.
|
||||
/// Applicable only to temporary values for now.
|
||||
pub(crate) fn schedule_backwards_incompatible_drop(
|
||||
&mut self,
|
||||
span: Span,
|
||||
region_scope: region::Scope,
|
||||
local: Local,
|
||||
) {
|
||||
if !self.local_decls[local].ty.has_significant_drop(self.tcx, ty::TypingEnv {
|
||||
typing_mode: ty::TypingMode::non_body_analysis(),
|
||||
param_env: self.param_env,
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
for scope in self.scopes.scopes.iter_mut().rev() {
|
||||
// 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);
|
||||
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::Value,
|
||||
backwards_incompatible_lint: true,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
span_bug!(
|
||||
span,
|
||||
"region scope {:?} not in scope to drop {:?} for linting",
|
||||
region_scope,
|
||||
local
|
||||
);
|
||||
}
|
||||
|
||||
/// Indicates that the "local operand" stored in `local` is
|
||||
/// *moved* at some point during execution (see `local_scope` for
|
||||
/// more information about what a "local operand" is -- in short,
|
||||
@ -1378,8 +1430,16 @@ fn build_scope_drops<'tcx>(
|
||||
continue;
|
||||
}
|
||||
|
||||
if drop_data.backwards_incompatible_lint {
|
||||
cfg.push(block, Statement {
|
||||
source_info,
|
||||
kind: StatementKind::BackwardIncompatibleDropHint {
|
||||
place: Box::new(local.into()),
|
||||
reason: BackwardIncompatibleDropReason::Edition2024,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
unwind_drops.add_entry_point(block, unwind_to);
|
||||
|
||||
let next = cfg.start_new_block();
|
||||
cfg.terminate(block, source_info, TerminatorKind::Drop {
|
||||
place: local.into(),
|
||||
@ -1389,6 +1449,7 @@ fn build_scope_drops<'tcx>(
|
||||
});
|
||||
block = next;
|
||||
}
|
||||
}
|
||||
DropKind::Storage => {
|
||||
if storage_dead_on_unwind {
|
||||
debug_assert_eq!(unwind_drops.drops[unwind_to].data.local, drop_data.local);
|
||||
|
@ -23,7 +23,6 @@ use tracing::{debug, info, instrument, trace};
|
||||
|
||||
use crate::errors;
|
||||
use crate::thir::cx::Cx;
|
||||
use crate::thir::cx::region::Scope;
|
||||
use crate::thir::util::UserAnnotatedTyHelpers;
|
||||
|
||||
impl<'tcx> Cx<'tcx> {
|
||||
@ -240,7 +239,7 @@ impl<'tcx> Cx<'tcx> {
|
||||
fn mirror_expr_cast(
|
||||
&mut self,
|
||||
source: &'tcx hir::Expr<'tcx>,
|
||||
temp_lifetime: Option<Scope>,
|
||||
temp_lifetime: TempLifetime,
|
||||
span: Span,
|
||||
) -> ExprKind<'tcx> {
|
||||
let tcx = self.tcx;
|
||||
@ -325,7 +324,7 @@ impl<'tcx> Cx<'tcx> {
|
||||
fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> {
|
||||
let tcx = self.tcx;
|
||||
let expr_ty = self.typeck_results().expr_ty(expr);
|
||||
let temp_lifetime =
|
||||
let (temp_lifetime, backwards_incompatible) =
|
||||
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
|
||||
|
||||
let kind = match expr.kind {
|
||||
@ -361,7 +360,7 @@ impl<'tcx> Cx<'tcx> {
|
||||
let arg_tys = args.iter().map(|e| self.typeck_results().expr_ty_adjusted(e));
|
||||
let tupled_args = Expr {
|
||||
ty: Ty::new_tup_from_iter(tcx, arg_tys),
|
||||
temp_lifetime,
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
span: expr.span,
|
||||
kind: ExprKind::Tuple { fields: self.mirror_exprs(args) },
|
||||
};
|
||||
@ -391,7 +390,10 @@ impl<'tcx> Cx<'tcx> {
|
||||
&& let [value] = args
|
||||
{
|
||||
return Expr {
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime,
|
||||
backwards_incompatible,
|
||||
},
|
||||
ty: expr_ty,
|
||||
span: expr.span,
|
||||
kind: ExprKind::Box { value: self.mirror_expr(value) },
|
||||
@ -811,13 +813,13 @@ impl<'tcx> Cx<'tcx> {
|
||||
},
|
||||
hir::ExprKind::Loop(body, ..) => {
|
||||
let block_ty = self.typeck_results().node_type(body.hir_id);
|
||||
let temp_lifetime = self
|
||||
let (temp_lifetime, backwards_incompatible) = self
|
||||
.rvalue_scopes
|
||||
.temporary_scope(self.region_scope_tree, body.hir_id.local_id);
|
||||
let block = self.mirror_block(body);
|
||||
let body = self.thir.exprs.push(Expr {
|
||||
ty: block_ty,
|
||||
temp_lifetime,
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
span: self.thir[block].span,
|
||||
kind: ExprKind::Block { block },
|
||||
});
|
||||
@ -838,13 +840,17 @@ impl<'tcx> Cx<'tcx> {
|
||||
expr, cast_ty.hir_id, user_ty,
|
||||
);
|
||||
|
||||
let cast = self.mirror_expr_cast(source, temp_lifetime, expr.span);
|
||||
let cast = self.mirror_expr_cast(
|
||||
source,
|
||||
TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
expr.span,
|
||||
);
|
||||
|
||||
if let Some(user_ty) = user_ty {
|
||||
// NOTE: Creating a new Expr and wrapping a Cast inside of it may be
|
||||
// inefficient, revisit this when performance becomes an issue.
|
||||
let cast_expr = self.thir.exprs.push(Expr {
|
||||
temp_lifetime,
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
ty: expr_ty,
|
||||
span: expr.span,
|
||||
kind: cast,
|
||||
@ -887,7 +893,12 @@ impl<'tcx> Cx<'tcx> {
|
||||
hir::ExprKind::Err(_) => unreachable!("cannot lower a `hir::ExprKind::Err` to THIR"),
|
||||
};
|
||||
|
||||
Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind }
|
||||
Expr {
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
ty: expr_ty,
|
||||
span: expr.span,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
fn user_args_applied_to_res(
|
||||
@ -931,7 +942,7 @@ impl<'tcx> Cx<'tcx> {
|
||||
span: Span,
|
||||
overloaded_callee: Option<Ty<'tcx>>,
|
||||
) -> Expr<'tcx> {
|
||||
let temp_lifetime =
|
||||
let (temp_lifetime, backwards_incompatible) =
|
||||
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
|
||||
let (ty, user_ty) = match overloaded_callee {
|
||||
Some(fn_def) => (fn_def, None),
|
||||
@ -952,7 +963,12 @@ impl<'tcx> Cx<'tcx> {
|
||||
)
|
||||
}
|
||||
};
|
||||
Expr { temp_lifetime, ty, span, kind: ExprKind::ZstLiteral { user_ty } }
|
||||
Expr {
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
ty,
|
||||
span,
|
||||
kind: ExprKind::ZstLiteral { user_ty },
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId {
|
||||
@ -1025,7 +1041,7 @@ impl<'tcx> Cx<'tcx> {
|
||||
Res::Def(DefKind::Static { .. }, id) => {
|
||||
// this is &raw for extern static or static mut, and & for other statics
|
||||
let ty = self.tcx.static_ptr_ty(id, self.typing_env());
|
||||
let temp_lifetime = self
|
||||
let (temp_lifetime, backwards_incompatible) = self
|
||||
.rvalue_scopes
|
||||
.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
|
||||
let kind = if self.tcx.is_thread_local_static(id) {
|
||||
@ -1035,7 +1051,12 @@ impl<'tcx> Cx<'tcx> {
|
||||
ExprKind::StaticRef { alloc_id, ty, def_id: id }
|
||||
};
|
||||
ExprKind::Deref {
|
||||
arg: self.thir.exprs.push(Expr { ty, temp_lifetime, span: expr.span, kind }),
|
||||
arg: self.thir.exprs.push(Expr {
|
||||
ty,
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
span: expr.span,
|
||||
kind,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1106,13 +1127,13 @@ impl<'tcx> Cx<'tcx> {
|
||||
|
||||
// construct the complete expression `foo()` for the overloaded call,
|
||||
// which will yield the &T type
|
||||
let temp_lifetime =
|
||||
let (temp_lifetime, backwards_incompatible) =
|
||||
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
|
||||
let fun = self.method_callee(expr, span, overloaded_callee);
|
||||
let fun = self.thir.exprs.push(fun);
|
||||
let fun_ty = self.thir[fun].ty;
|
||||
let ref_expr = self.thir.exprs.push(Expr {
|
||||
temp_lifetime,
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
ty: ref_ty,
|
||||
span,
|
||||
kind: ExprKind::Call { ty: fun_ty, fun, args, from_hir_call: false, fn_span: span },
|
||||
@ -1127,7 +1148,7 @@ impl<'tcx> Cx<'tcx> {
|
||||
closure_expr: &'tcx hir::Expr<'tcx>,
|
||||
place: HirPlace<'tcx>,
|
||||
) -> Expr<'tcx> {
|
||||
let temp_lifetime = self
|
||||
let (temp_lifetime, backwards_incompatible) = self
|
||||
.rvalue_scopes
|
||||
.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);
|
||||
let var_ty = place.base_ty;
|
||||
@ -1143,7 +1164,7 @@ impl<'tcx> Cx<'tcx> {
|
||||
};
|
||||
|
||||
let mut captured_place_expr = Expr {
|
||||
temp_lifetime,
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
ty: var_ty,
|
||||
span: closure_expr.span,
|
||||
kind: self.convert_var(var_hir_id),
|
||||
@ -1168,8 +1189,12 @@ impl<'tcx> Cx<'tcx> {
|
||||
}
|
||||
};
|
||||
|
||||
captured_place_expr =
|
||||
Expr { temp_lifetime, ty: proj.ty, span: closure_expr.span, kind };
|
||||
captured_place_expr = Expr {
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
ty: proj.ty,
|
||||
span: closure_expr.span,
|
||||
kind,
|
||||
};
|
||||
}
|
||||
|
||||
captured_place_expr
|
||||
@ -1184,7 +1209,7 @@ impl<'tcx> Cx<'tcx> {
|
||||
let upvar_capture = captured_place.info.capture_kind;
|
||||
let captured_place_expr =
|
||||
self.convert_captured_hir_place(closure_expr, captured_place.place.clone());
|
||||
let temp_lifetime = self
|
||||
let (temp_lifetime, backwards_incompatible) = self
|
||||
.rvalue_scopes
|
||||
.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);
|
||||
|
||||
@ -1201,7 +1226,7 @@ impl<'tcx> Cx<'tcx> {
|
||||
}
|
||||
};
|
||||
Expr {
|
||||
temp_lifetime,
|
||||
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
|
||||
ty: upvar_ty,
|
||||
span: closure_expr.span,
|
||||
kind: ExprKind::Borrow {
|
||||
|
@ -253,6 +253,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::Intrinsic(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => None,
|
||||
};
|
||||
if let Some(destination) = destination {
|
||||
|
@ -157,6 +157,7 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
|
||||
| StatementKind::Nop
|
||||
| StatementKind::Retag(..)
|
||||
| StatementKind::Intrinsic(..)
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::StorageLive(..) => {}
|
||||
}
|
||||
}
|
||||
|
@ -385,6 +385,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::Intrinsic(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => {}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,31 @@ mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspen
|
||||
.help = consider using a block (`{"{ ... }"}`) to shrink the value's scope, ending before the suspend point
|
||||
mir_transform_operation_will_panic = this operation will panic at runtime
|
||||
|
||||
mir_transform_tail_expr_drop_order = relative drop order changing in Rust 2024
|
||||
.temporaries = in Rust 2024, this temporary value will be dropped first
|
||||
.observers = in Rust 2024, this local variable or temporary value will be dropped second
|
||||
.note_dtors =
|
||||
dropping the temporary value runs this custom `Drop` impl, which we could not prove to be side-effect free
|
||||
.note_observer_dtors =
|
||||
dropping the local runs this custom `Drop` impl, which we could not prove to be side-effect free
|
||||
.drop_location =
|
||||
now the temporary value is dropped here, before the local variables in the block or statement
|
||||
.note_epilogue = most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
|
||||
.label_local_epilogue = {$is_dropped_first_edition_2024 ->
|
||||
[true] up until Edition 2021 `{$name}` is dropped last but will be dropped earlier in Edition 2024
|
||||
*[false] `{$name}` will be dropped later as of Edition 2024
|
||||
}
|
||||
|
||||
mir_transform_tail_expr_dtor = {$dtor_kind ->
|
||||
[dyn] `{$name}` may invoke a custom destructor because it contains a trait object
|
||||
*[concrete] `{$name}` invokes this custom destructor
|
||||
}
|
||||
|
||||
mir_transform_tail_expr_local = {$is_generated_name ->
|
||||
[true] this value will be stored in a temporary; let us call it `{$name}`
|
||||
*[false] `{$name}` calls a custom destructor
|
||||
}
|
||||
|
||||
mir_transform_unaligned_packed_ref = reference to packed field is unaligned
|
||||
.note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses
|
||||
.note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
|
||||
|
@ -1772,6 +1772,7 @@ impl<'tcx> Visitor<'tcx> for EnsureCoroutineFieldAssignmentsNeverAlias<'_> {
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::Intrinsic(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => {}
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +97,7 @@ fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span> {
|
||||
StatementKind::StorageLive(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => None,
|
||||
|
||||
// FIXME(#78546): MIR InstrumentCoverage - Can the source_info.span for `FakeRead`
|
||||
|
@ -186,7 +186,8 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
|
||||
| StatementKind::FakeRead(..)
|
||||
| StatementKind::PlaceMention(..)
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::AscribeUserType(..) => (),
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::AscribeUserType(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,8 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
| StatementKind::Intrinsic(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::PlaceMention(_)
|
||||
| StatementKind::Nop => (),
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => {}
|
||||
|
||||
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {
|
||||
bug!("{:?} not found in this MIR phase!", statement.kind)
|
||||
|
@ -581,7 +581,7 @@ impl WriteInfo {
|
||||
| Rvalue::RawPtr(_, _)
|
||||
| Rvalue::Len(_)
|
||||
| Rvalue::Discriminant(_)
|
||||
| Rvalue::CopyForDeref(_) => (),
|
||||
| Rvalue::CopyForDeref(_) => {}
|
||||
}
|
||||
}
|
||||
// Retags are technically also reads, but reporting them as a write suffices
|
||||
@ -596,7 +596,8 @@ impl WriteInfo {
|
||||
| StatementKind::Coverage(_)
|
||||
| StatementKind::StorageLive(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::PlaceMention(_) => (),
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::PlaceMention(_) => {}
|
||||
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {
|
||||
bug!("{:?} not found in this MIR phase", statement)
|
||||
}
|
||||
|
@ -352,6 +352,7 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
||||
| StatementKind::FakeRead(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::PlaceMention(..)
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => None,
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ mod deduce_param_attrs;
|
||||
mod errors;
|
||||
mod ffi_unwind_calls;
|
||||
mod lint;
|
||||
mod lint_tail_expr_drop_order;
|
||||
mod shim;
|
||||
mod ssa;
|
||||
|
||||
@ -490,6 +491,7 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> &
|
||||
}
|
||||
|
||||
let (body, _) = tcx.mir_promoted(def);
|
||||
lint_tail_expr_drop_order::run_lint(tcx, def, &body.borrow());
|
||||
let mut body = body.steal();
|
||||
|
||||
if let Some(error_reported) = tainted_by_errors {
|
||||
|
701
compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs
Normal file
701
compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs
Normal file
@ -0,0 +1,701 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map;
|
||||
use std::rc::Rc;
|
||||
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_data_structures::unord::{UnordMap, UnordSet};
|
||||
use rustc_errors::Subdiagnostic;
|
||||
use rustc_hir::CRATE_HIR_ID;
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_index::bit_set::ChunkedBitSet;
|
||||
use rustc_index::{IndexSlice, IndexVec};
|
||||
use rustc_macros::{LintDiagnostic, Subdiagnostic};
|
||||
use rustc_middle::bug;
|
||||
use rustc_middle::mir::{
|
||||
self, BasicBlock, Body, ClearCrossCrate, Local, Location, Place, StatementKind, TerminatorKind,
|
||||
dump_mir,
|
||||
};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
|
||||
use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
|
||||
use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor};
|
||||
use rustc_session::lint::builtin::TAIL_EXPR_DROP_ORDER;
|
||||
use rustc_session::lint::{self};
|
||||
use rustc_span::{DUMMY_SP, Span, Symbol};
|
||||
use rustc_type_ir::data_structures::IndexMap;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
fn place_has_common_prefix<'tcx>(left: &Place<'tcx>, right: &Place<'tcx>) -> bool {
|
||||
left.local == right.local
|
||||
&& left.projection.iter().zip(right.projection).all(|(left, right)| left == right)
|
||||
}
|
||||
|
||||
/// Cache entry of `drop` at a `BasicBlock`
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum MovePathIndexAtBlock {
|
||||
/// We know nothing yet
|
||||
Unknown,
|
||||
/// We know that the `drop` here has no effect
|
||||
None,
|
||||
/// We know that the `drop` here will invoke a destructor
|
||||
Some(MovePathIndex),
|
||||
}
|
||||
|
||||
struct DropsReachable<'a, 'mir, 'tcx> {
|
||||
body: &'a Body<'tcx>,
|
||||
place: &'a Place<'tcx>,
|
||||
drop_span: &'a mut Option<Span>,
|
||||
move_data: &'a MoveData<'tcx>,
|
||||
maybe_init: &'a mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
|
||||
block_drop_value_info: &'a mut IndexSlice<BasicBlock, MovePathIndexAtBlock>,
|
||||
collected_drops: &'a mut ChunkedBitSet<MovePathIndex>,
|
||||
visited: FxHashMap<BasicBlock, Rc<RefCell<ChunkedBitSet<MovePathIndex>>>>,
|
||||
}
|
||||
|
||||
impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
|
||||
fn visit(&mut self, block: BasicBlock) {
|
||||
let move_set_size = self.move_data.move_paths.len();
|
||||
let make_new_path_set = || Rc::new(RefCell::new(ChunkedBitSet::new_empty(move_set_size)));
|
||||
|
||||
let data = &self.body.basic_blocks[block];
|
||||
let Some(terminator) = &data.terminator else { return };
|
||||
// Given that we observe these dropped locals here at `block` so far,
|
||||
// we will try to update the successor blocks.
|
||||
// An occupied entry at `block` in `self.visited` signals that we have visited `block` before.
|
||||
let dropped_local_here =
|
||||
Rc::clone(self.visited.entry(block).or_insert_with(make_new_path_set));
|
||||
// We could have invoked reverse lookup for a `MovePathIndex` every time, but unfortunately it is expensive.
|
||||
// Let's cache them in `self.block_drop_value_info`.
|
||||
match self.block_drop_value_info[block] {
|
||||
MovePathIndexAtBlock::Some(dropped) => {
|
||||
dropped_local_here.borrow_mut().insert(dropped);
|
||||
}
|
||||
MovePathIndexAtBlock::Unknown => {
|
||||
if let TerminatorKind::Drop { place, .. } = &terminator.kind
|
||||
&& let LookupResult::Exact(idx) | LookupResult::Parent(Some(idx)) =
|
||||
self.move_data.rev_lookup.find(place.as_ref())
|
||||
{
|
||||
// Since we are working with MIRs at a very early stage,
|
||||
// observing a `drop` terminator is not indicative enough that
|
||||
// the drop will definitely happen.
|
||||
// That is decided in the drop elaboration pass instead.
|
||||
// Therefore, we need to consult with the maybe-initialization information.
|
||||
self.maybe_init.seek_before_primary_effect(Location {
|
||||
block,
|
||||
statement_index: data.statements.len(),
|
||||
});
|
||||
|
||||
// Check if the drop of `place` under inspection is really in effect.
|
||||
// This is true only when `place` may have been initialized along a control flow path from a BID to the drop program point today.
|
||||
// In other words, this is where the drop of `place` will happen in the future instead.
|
||||
if let MaybeReachable::Reachable(maybe_init) = self.maybe_init.get()
|
||||
&& maybe_init.contains(idx)
|
||||
{
|
||||
// We also cache the drop information, so that we do not need to check on data-flow cursor again
|
||||
self.block_drop_value_info[block] = MovePathIndexAtBlock::Some(idx);
|
||||
dropped_local_here.borrow_mut().insert(idx);
|
||||
} else {
|
||||
self.block_drop_value_info[block] = MovePathIndexAtBlock::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
MovePathIndexAtBlock::None => {}
|
||||
}
|
||||
|
||||
for succ in terminator.successors() {
|
||||
let target = &self.body.basic_blocks[succ];
|
||||
if target.is_cleanup {
|
||||
continue;
|
||||
}
|
||||
|
||||
// As long as we are passing through a new block, or new dropped places to propagate,
|
||||
// we will proceed with `succ`
|
||||
let dropped_local_there = match self.visited.entry(succ) {
|
||||
hash_map::Entry::Occupied(occupied_entry) => {
|
||||
if succ == block
|
||||
|| !occupied_entry.get().borrow_mut().union(&*dropped_local_here.borrow())
|
||||
{
|
||||
// `succ` has been visited but no new drops observed so far,
|
||||
// so we can bail on `succ` until new drop information arrives
|
||||
continue;
|
||||
}
|
||||
Rc::clone(occupied_entry.get())
|
||||
}
|
||||
hash_map::Entry::Vacant(vacant_entry) => Rc::clone(
|
||||
vacant_entry.insert(Rc::new(RefCell::new(dropped_local_here.borrow().clone()))),
|
||||
),
|
||||
};
|
||||
if let Some(terminator) = &target.terminator
|
||||
&& let TerminatorKind::Drop {
|
||||
place: dropped_place,
|
||||
target: _,
|
||||
unwind: _,
|
||||
replace: _,
|
||||
} = &terminator.kind
|
||||
&& place_has_common_prefix(dropped_place, self.place)
|
||||
{
|
||||
// We have now reached the current drop of the `place`.
|
||||
// Let's check the observed dropped places in.
|
||||
self.collected_drops.union(&*dropped_local_there.borrow());
|
||||
if self.drop_span.is_none() {
|
||||
// FIXME(@dingxiangfei2009): it turns out that `self.body.source_scopes` are still a bit wonky.
|
||||
// There is a high chance that this span still points to a block rather than a statement semicolon.
|
||||
*self.drop_span = Some(terminator.source_info.span);
|
||||
}
|
||||
// Now we have discovered a simple control flow path from a future drop point
|
||||
// to the current drop point.
|
||||
// We will not continue from there.
|
||||
} else {
|
||||
self.visit(succ)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An additional filter to exclude well-known types from the ecosystem
|
||||
/// because their drops are trivial.
|
||||
/// This returns additional types to check if the drops are delegated to those.
|
||||
/// A typical example is `hashbrown::HashMap<K, V>`, whose drop is delegated to `K` and `V`.
|
||||
fn true_significant_drop_ty<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
) -> Option<SmallVec<[Ty<'tcx>; 2]>> {
|
||||
if let ty::Adt(def, args) = ty.kind() {
|
||||
let mut did = def.did();
|
||||
let mut name_rev = vec![];
|
||||
loop {
|
||||
let key = tcx.def_key(did);
|
||||
|
||||
match key.disambiguated_data.data {
|
||||
rustc_hir::definitions::DefPathData::CrateRoot => {
|
||||
name_rev.push(tcx.crate_name(did.krate))
|
||||
}
|
||||
rustc_hir::definitions::DefPathData::TypeNs(symbol) => name_rev.push(symbol),
|
||||
_ => return None,
|
||||
}
|
||||
if let Some(parent) = key.parent {
|
||||
did = DefId { krate: did.krate, index: parent };
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let name_str: Vec<_> = name_rev.iter().rev().map(|x| x.as_str()).collect();
|
||||
debug!(?name_str);
|
||||
match name_str[..] {
|
||||
// These are the types from Rust core ecosystem
|
||||
["sym" | "proc_macro2", ..]
|
||||
| ["core" | "std", "task", "LocalWaker" | "Waker"]
|
||||
| ["core" | "std", "task", "wake", "LocalWaker" | "Waker"] => Some(smallvec![]),
|
||||
// These are important types from Rust ecosystem
|
||||
["tracing", "instrument", "Instrumented"] | ["bytes", "Bytes"] => Some(smallvec![]),
|
||||
["hashbrown", "raw", "RawTable" | "RawIntoIter"] => {
|
||||
if let [ty, ..] = &***args
|
||||
&& let Some(ty) = ty.as_type()
|
||||
{
|
||||
Some(smallvec![ty])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
["hashbrown", "raw", "RawDrain"] => {
|
||||
if let [_, ty, ..] = &***args
|
||||
&& let Some(ty) = ty.as_type()
|
||||
{
|
||||
Some(smallvec![ty])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of types with a "potentially sigificant" that may be dropped
|
||||
/// by dropping a value of type `ty`.
|
||||
#[instrument(level = "debug", skip(tcx, param_env))]
|
||||
fn extract_component_raw<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
ty_seen: &mut UnordSet<Ty<'tcx>>,
|
||||
) -> SmallVec<[Ty<'tcx>; 4]> {
|
||||
// Droppiness does not depend on regions, so let us erase them.
|
||||
let ty = tcx
|
||||
.try_normalize_erasing_regions(
|
||||
ty::TypingEnv { param_env, typing_mode: ty::TypingMode::PostAnalysis },
|
||||
ty,
|
||||
)
|
||||
.unwrap_or(ty);
|
||||
|
||||
let tys = tcx.list_significant_drop_tys(param_env.and(ty));
|
||||
debug!(?ty, "components");
|
||||
let mut out_tys = smallvec![];
|
||||
for ty in tys {
|
||||
if let Some(tys) = true_significant_drop_ty(tcx, ty) {
|
||||
// Some types can be further opened up because the drop is simply delegated
|
||||
for ty in tys {
|
||||
if ty_seen.insert(ty) {
|
||||
out_tys.extend(extract_component_raw(tcx, param_env, ty, ty_seen));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ty_seen.insert(ty) {
|
||||
out_tys.push(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
out_tys
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(tcx, param_env))]
|
||||
fn extract_component_with_significant_dtor<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
) -> SmallVec<[Ty<'tcx>; 4]> {
|
||||
let mut tys = extract_component_raw(tcx, param_env, ty, &mut Default::default());
|
||||
let mut deduplicate = FxHashSet::default();
|
||||
tys.retain(|oty| deduplicate.insert(*oty));
|
||||
tys.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Extract the span of the custom destructor of a type
|
||||
/// especially the span of the `impl Drop` header or its entire block
|
||||
/// when we are working with current local crate.
|
||||
#[instrument(level = "debug", skip(tcx))]
|
||||
fn ty_dtor_span<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Span> {
|
||||
match ty.kind() {
|
||||
ty::Bool
|
||||
| ty::Char
|
||||
| ty::Int(_)
|
||||
| ty::Uint(_)
|
||||
| ty::Float(_)
|
||||
| ty::Error(_)
|
||||
| ty::Str
|
||||
| ty::Never
|
||||
| ty::RawPtr(_, _)
|
||||
| ty::Ref(_, _, _)
|
||||
| ty::FnPtr(_, _)
|
||||
| ty::Tuple(_)
|
||||
| ty::Dynamic(_, _, _)
|
||||
| ty::Alias(_, _)
|
||||
| ty::Bound(_, _)
|
||||
| ty::Pat(_, _)
|
||||
| ty::Placeholder(_)
|
||||
| ty::Infer(_)
|
||||
| ty::Slice(_)
|
||||
| ty::Array(_, _) => None,
|
||||
ty::Adt(adt_def, _) => {
|
||||
let did = adt_def.did();
|
||||
let try_local_did_span = |did: DefId| {
|
||||
if let Some(local) = did.as_local() {
|
||||
tcx.source_span(local)
|
||||
} else {
|
||||
tcx.def_span(did)
|
||||
}
|
||||
};
|
||||
let dtor = if let Some(dtor) = tcx.adt_destructor(did) {
|
||||
dtor.did
|
||||
} else if let Some(dtor) = tcx.adt_async_destructor(did) {
|
||||
dtor.future
|
||||
} else {
|
||||
return Some(try_local_did_span(did));
|
||||
};
|
||||
let def_key = tcx.def_key(dtor);
|
||||
let Some(parent_index) = def_key.parent else { return Some(try_local_did_span(dtor)) };
|
||||
let parent_did = DefId { index: parent_index, krate: dtor.krate };
|
||||
Some(try_local_did_span(parent_did))
|
||||
}
|
||||
ty::Coroutine(did, _)
|
||||
| ty::CoroutineWitness(did, _)
|
||||
| ty::CoroutineClosure(did, _)
|
||||
| ty::Closure(did, _)
|
||||
| ty::FnDef(did, _)
|
||||
| ty::Foreign(did) => Some(tcx.def_span(did)),
|
||||
ty::Param(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a moved place at `idx` is a part of a BID.
|
||||
/// The use of this check is that we will consider drops on these
|
||||
/// as a drop of the overall BID and, thus, we can exclude it from the diagnosis.
|
||||
fn place_descendent_of_bids<'tcx>(
|
||||
mut idx: MovePathIndex,
|
||||
move_data: &MoveData<'tcx>,
|
||||
bids: &UnordSet<&Place<'tcx>>,
|
||||
) -> bool {
|
||||
loop {
|
||||
let path = &move_data.move_paths[idx];
|
||||
if bids.contains(&path.place) {
|
||||
return true;
|
||||
}
|
||||
if let Some(parent) = path.parent {
|
||||
idx = parent;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The core of the lint `tail-expr-drop-order`
|
||||
pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<'tcx>) {
|
||||
if matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::SyntheticCoroutineBody) {
|
||||
// A synthetic coroutine has no HIR body and it is enough to just analyse the original body
|
||||
return;
|
||||
}
|
||||
if body.span.edition().at_least_rust_2024()
|
||||
|| tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// ## About BIDs in blocks ##
|
||||
// Track the set of blocks that contain a backwards-incompatible drop (BID)
|
||||
// and, for each block, the vector of locations.
|
||||
//
|
||||
// We group them per-block because they tend to scheduled in the same drop ladder block.
|
||||
let mut bid_per_block = IndexMap::default();
|
||||
let mut bid_places = UnordSet::new();
|
||||
let param_env = tcx.param_env(def_id).with_reveal_all_normalized(tcx);
|
||||
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 {
|
||||
let ty = place.ty(body, tcx).ty;
|
||||
if ty_dropped_components
|
||||
.entry(ty)
|
||||
.or_insert_with(|| extract_component_with_significant_dtor(tcx, param_env, ty))
|
||||
.is_empty()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bid_per_block
|
||||
.entry(block)
|
||||
.or_insert(vec![])
|
||||
.push((Location { block, statement_index }, &**place));
|
||||
bid_places.insert(&**place);
|
||||
}
|
||||
}
|
||||
}
|
||||
if bid_per_block.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
dump_mir(tcx, false, "lint_tail_expr_drop_order", &0 as _, body, |_, _| Ok(()));
|
||||
let locals_with_user_names = collect_user_names(body);
|
||||
let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
|
||||
|
||||
// Compute the "maybe initialized" information for this body.
|
||||
// When we encounter a DROP of some place P we only care
|
||||
// about the drop if `P` may be initialized.
|
||||
let move_data = MoveData::gather_moves(body, tcx, |_| true);
|
||||
let maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data);
|
||||
let mut maybe_init = maybe_init.iterate_to_fixpoint(tcx, body, None).into_results_cursor(body);
|
||||
let mut block_drop_value_info =
|
||||
IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
|
||||
for (&block, candidates) in &bid_per_block {
|
||||
// We will collect drops on locals on paths between BID points to their actual drop locations
|
||||
// into `all_locals_dropped`.
|
||||
let mut all_locals_dropped = ChunkedBitSet::new_empty(move_data.move_paths.len());
|
||||
let mut drop_span = None;
|
||||
for &(_, place) in candidates.iter() {
|
||||
let mut collected_drops = ChunkedBitSet::new_empty(move_data.move_paths.len());
|
||||
// ## On detecting change in relative drop order ##
|
||||
// Iterate through each BID-containing block `block`.
|
||||
// If the place `P` targeted by the BID is "maybe initialized",
|
||||
// then search forward to find the actual `DROP(P)` point.
|
||||
// Everything dropped between the BID and the actual drop point
|
||||
// is something whose relative drop order will change.
|
||||
DropsReachable {
|
||||
body,
|
||||
place,
|
||||
drop_span: &mut drop_span,
|
||||
move_data: &move_data,
|
||||
maybe_init: &mut maybe_init,
|
||||
block_drop_value_info: &mut block_drop_value_info,
|
||||
collected_drops: &mut collected_drops,
|
||||
visited: Default::default(),
|
||||
}
|
||||
.visit(block);
|
||||
// Compute the set `all_locals_dropped` of local variables that are dropped
|
||||
// after the BID point but before the current drop point.
|
||||
//
|
||||
// These are the variables whose drop impls will be reordered with respect
|
||||
// to `place`.
|
||||
all_locals_dropped.union(&collected_drops);
|
||||
}
|
||||
|
||||
// We shall now exclude some local bindings for the following cases.
|
||||
{
|
||||
let mut to_exclude = ChunkedBitSet::new_empty(all_locals_dropped.domain_size());
|
||||
// We will now do subtraction from the candidate dropped locals, because of the following reasons.
|
||||
for path_idx in all_locals_dropped.iter() {
|
||||
let move_path = &move_data.move_paths[path_idx];
|
||||
let dropped_local = move_path.place.local;
|
||||
// a) A return value _0 will eventually be used
|
||||
// Example:
|
||||
// fn f() -> Droppy {
|
||||
// let _x = Droppy;
|
||||
// Droppy
|
||||
// }
|
||||
// _0 holds the literal `Droppy` and rightfully `_x` has to be dropped first
|
||||
if dropped_local == Local::ZERO {
|
||||
debug!(?dropped_local, "skip return value");
|
||||
to_exclude.insert(path_idx);
|
||||
continue;
|
||||
}
|
||||
// b) If we are analysing a closure, the captures are still dropped last.
|
||||
// This is part of the closure capture lifetime contract.
|
||||
// They are similar to the return value _0 with respect to lifetime rules.
|
||||
if is_closure_like && matches!(dropped_local, ty::CAPTURE_STRUCT_LOCAL) {
|
||||
debug!(?dropped_local, "skip closure captures");
|
||||
to_exclude.insert(path_idx);
|
||||
continue;
|
||||
}
|
||||
// c) Sometimes we collect places that are projections into the BID locals,
|
||||
// so they are considered dropped now.
|
||||
// Example:
|
||||
// struct NotVeryDroppy(Droppy);
|
||||
// impl Drop for Droppy {..}
|
||||
// fn f() -> NotVeryDroppy {
|
||||
// let x = NotVeryDroppy(droppy());
|
||||
// {
|
||||
// let y: Droppy = x.0;
|
||||
// NotVeryDroppy(y)
|
||||
// }
|
||||
// }
|
||||
// `y` takes `x.0`, which invalidates `x` as a complete `NotVeryDroppy`
|
||||
// so there is no point in linting against `x` any more.
|
||||
if place_descendent_of_bids(path_idx, &move_data, &bid_places) {
|
||||
debug!(?dropped_local, "skip descendent of bids");
|
||||
to_exclude.insert(path_idx);
|
||||
continue;
|
||||
}
|
||||
let observer_ty = move_path.place.ty(body, tcx).ty;
|
||||
// d) The collected local has no custom destructor that passes our ecosystem filter.
|
||||
if ty_dropped_components
|
||||
.entry(observer_ty)
|
||||
.or_insert_with(|| {
|
||||
extract_component_with_significant_dtor(tcx, param_env, observer_ty)
|
||||
})
|
||||
.is_empty()
|
||||
{
|
||||
debug!(?dropped_local, "skip non-droppy types");
|
||||
to_exclude.insert(path_idx);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Suppose that all BIDs point into the same local,
|
||||
// we can remove the this local from the observed drops,
|
||||
// so that we can focus our diagnosis more on the others.
|
||||
if candidates.iter().all(|&(_, place)| candidates[0].1.local == place.local) {
|
||||
for path_idx in all_locals_dropped.iter() {
|
||||
if move_data.move_paths[path_idx].place.local == candidates[0].1.local {
|
||||
to_exclude.insert(path_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
all_locals_dropped.subtract(&to_exclude);
|
||||
}
|
||||
if all_locals_dropped.is_empty() {
|
||||
// No drop effect is observable, so let us move on.
|
||||
continue;
|
||||
}
|
||||
|
||||
// ## The final work to assemble the diagnosis ##
|
||||
// First collect or generate fresh names for local variable bindings and temporary values.
|
||||
let local_names = assign_observables_names(
|
||||
all_locals_dropped
|
||||
.iter()
|
||||
.map(|path_idx| move_data.move_paths[path_idx].place.local)
|
||||
.chain(candidates.iter().map(|(_, place)| place.local)),
|
||||
&locals_with_user_names,
|
||||
);
|
||||
|
||||
let mut lint_root = None;
|
||||
let mut local_labels = vec![];
|
||||
// We now collect the types with custom destructors.
|
||||
for &(_, place) in candidates {
|
||||
let linted_local_decl = &body.local_decls[place.local];
|
||||
let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
|
||||
bug!("a name should have been assigned")
|
||||
};
|
||||
let name = name.as_str();
|
||||
|
||||
if lint_root.is_none()
|
||||
&& let ClearCrossCrate::Set(data) =
|
||||
&body.source_scopes[linted_local_decl.source_info.scope].local_data
|
||||
{
|
||||
lint_root = Some(data.lint_root);
|
||||
}
|
||||
|
||||
// Collect spans of the custom destructors.
|
||||
let mut seen_dyn = false;
|
||||
let destructors = ty_dropped_components
|
||||
.get(&linted_local_decl.ty)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|&ty| {
|
||||
if let Some(span) = ty_dtor_span(tcx, ty) {
|
||||
Some(DestructorLabel { span, name, dtor_kind: "concrete" })
|
||||
} else if matches!(ty.kind(), ty::Dynamic(..)) {
|
||||
if seen_dyn {
|
||||
None
|
||||
} else {
|
||||
seen_dyn = true;
|
||||
Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
local_labels.push(LocalLabel {
|
||||
span: linted_local_decl.source_info.span,
|
||||
destructors,
|
||||
name,
|
||||
is_generated_name,
|
||||
is_dropped_first_edition_2024: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Similarly, custom destructors of the observed drops.
|
||||
for path_idx in all_locals_dropped.iter() {
|
||||
let place = &move_data.move_paths[path_idx].place;
|
||||
// We are not using the type of the local because the drop may be partial.
|
||||
let observer_ty = place.ty(body, tcx).ty;
|
||||
|
||||
let observer_local_decl = &body.local_decls[place.local];
|
||||
let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
|
||||
bug!("a name should have been assigned")
|
||||
};
|
||||
let name = name.as_str();
|
||||
|
||||
let mut seen_dyn = false;
|
||||
let destructors = extract_component_with_significant_dtor(tcx, param_env, observer_ty)
|
||||
.into_iter()
|
||||
.filter_map(|ty| {
|
||||
if let Some(span) = ty_dtor_span(tcx, ty) {
|
||||
Some(DestructorLabel { span, name, dtor_kind: "concrete" })
|
||||
} else if matches!(ty.kind(), ty::Dynamic(..)) {
|
||||
if seen_dyn {
|
||||
None
|
||||
} else {
|
||||
seen_dyn = true;
|
||||
Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
local_labels.push(LocalLabel {
|
||||
span: observer_local_decl.source_info.span,
|
||||
destructors,
|
||||
name,
|
||||
is_generated_name,
|
||||
is_dropped_first_edition_2024: false,
|
||||
});
|
||||
}
|
||||
|
||||
let span = local_labels[0].span;
|
||||
tcx.emit_node_span_lint(
|
||||
lint::builtin::TAIL_EXPR_DROP_ORDER,
|
||||
lint_root.unwrap_or(CRATE_HIR_ID),
|
||||
span,
|
||||
TailExprDropOrderLint { local_labels, drop_span, _epilogue: () },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract binding names if available for diagnosis
|
||||
fn collect_user_names(body: &Body<'_>) -> IndexMap<Local, Symbol> {
|
||||
let mut names = IndexMap::default();
|
||||
for var_debug_info in &body.var_debug_info {
|
||||
if let mir::VarDebugInfoContents::Place(place) = &var_debug_info.value
|
||||
&& let Some(local) = place.local_or_deref_local()
|
||||
{
|
||||
names.entry(local).or_insert(var_debug_info.name);
|
||||
}
|
||||
}
|
||||
names
|
||||
}
|
||||
|
||||
/// Assign names for anonymous or temporary values for diagnosis
|
||||
fn assign_observables_names(
|
||||
locals: impl IntoIterator<Item = Local>,
|
||||
user_names: &IndexMap<Local, Symbol>,
|
||||
) -> IndexMap<Local, (String, bool)> {
|
||||
let mut names = IndexMap::default();
|
||||
let mut assigned_names = FxHashSet::default();
|
||||
let mut idx = 0u64;
|
||||
let mut fresh_name = || {
|
||||
idx += 1;
|
||||
(format!("#{idx}"), true)
|
||||
};
|
||||
for local in locals {
|
||||
let name = if let Some(name) = user_names.get(&local) {
|
||||
let name = name.as_str();
|
||||
if assigned_names.contains(name) { fresh_name() } else { (name.to_owned(), false) }
|
||||
} else {
|
||||
fresh_name()
|
||||
};
|
||||
assigned_names.insert(name.0.clone());
|
||||
names.insert(local, name);
|
||||
}
|
||||
names
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(mir_transform_tail_expr_drop_order)]
|
||||
struct TailExprDropOrderLint<'a> {
|
||||
#[subdiagnostic]
|
||||
local_labels: Vec<LocalLabel<'a>>,
|
||||
#[label(mir_transform_drop_location)]
|
||||
drop_span: Option<Span>,
|
||||
#[note(mir_transform_note_epilogue)]
|
||||
_epilogue: (),
|
||||
}
|
||||
|
||||
struct LocalLabel<'a> {
|
||||
span: Span,
|
||||
name: &'a str,
|
||||
is_generated_name: bool,
|
||||
is_dropped_first_edition_2024: bool,
|
||||
destructors: Vec<DestructorLabel<'a>>,
|
||||
}
|
||||
|
||||
/// A custom `Subdiagnostic` implementation so that the notes are delivered in a specific order
|
||||
impl Subdiagnostic for LocalLabel<'_> {
|
||||
fn add_to_diag_with<
|
||||
G: rustc_errors::EmissionGuarantee,
|
||||
F: rustc_errors::SubdiagMessageOp<G>,
|
||||
>(
|
||||
self,
|
||||
diag: &mut rustc_errors::Diag<'_, G>,
|
||||
f: &F,
|
||||
) {
|
||||
diag.arg("name", self.name);
|
||||
diag.arg("is_generated_name", self.is_generated_name);
|
||||
diag.arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024);
|
||||
let msg = f(diag, crate::fluent_generated::mir_transform_tail_expr_local.into());
|
||||
diag.span_label(self.span, msg);
|
||||
for dtor in self.destructors {
|
||||
dtor.add_to_diag_with(diag, f);
|
||||
}
|
||||
let msg = f(diag, crate::fluent_generated::mir_transform_label_local_epilogue.into());
|
||||
diag.span_label(self.span, msg);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
#[note(mir_transform_tail_expr_dtor)]
|
||||
struct DestructorLabel<'a> {
|
||||
#[primary_span]
|
||||
span: Span,
|
||||
dtor_kind: &'static str,
|
||||
name: &'a str,
|
||||
}
|
@ -92,6 +92,7 @@ impl RemoveNoopLandingPads {
|
||||
| StatementKind::AscribeUserType(..)
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => {
|
||||
// These are all noops in a landing pad
|
||||
}
|
||||
|
@ -125,6 +125,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
|
||||
StatementKind::Coverage(_)
|
||||
| StatementKind::Intrinsic(_)
|
||||
| StatementKind::Nop
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::ConstEvalCounter => None,
|
||||
};
|
||||
if let Some(place_for_ty) = place_for_ty
|
||||
|
@ -523,7 +523,8 @@ impl<'tcx> Visitor<'tcx> for UsedLocals {
|
||||
}
|
||||
|
||||
StatementKind::SetDiscriminant { ref place, variant_index: _ }
|
||||
| StatementKind::Deinit(ref place) => {
|
||||
| StatementKind::Deinit(ref place)
|
||||
| StatementKind::BackwardIncompatibleDropHint { ref place, reason: _ } => {
|
||||
self.visit_lhs(place, location);
|
||||
}
|
||||
}
|
||||
@ -560,6 +561,7 @@ fn remove_unused_definitions_helper(used_locals: &mut UsedLocals, body: &mut Bod
|
||||
StatementKind::Assign(box (place, _)) => used_locals.is_used(place.local),
|
||||
|
||||
StatementKind::SetDiscriminant { ref place, .. }
|
||||
| StatementKind::BackwardIncompatibleDropHint { ref place, reason: _ }
|
||||
| StatementKind::Deinit(ref place) => used_locals.is_used(place.local),
|
||||
StatementKind::Nop => false,
|
||||
_ => true,
|
||||
@ -587,6 +589,20 @@ impl<'tcx> MutVisitor<'tcx> for LocalUpdater<'tcx> {
|
||||
self.tcx
|
||||
}
|
||||
|
||||
fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
|
||||
if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } =
|
||||
&mut statement.kind
|
||||
{
|
||||
self.visit_local(
|
||||
&mut place.local,
|
||||
PlaceContext::MutatingUse(MutatingUseContext::Store),
|
||||
location,
|
||||
);
|
||||
} else {
|
||||
self.super_statement(statement, location);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &mut Local, _: PlaceContext, _: Location) {
|
||||
*l = self.map[*l].unwrap();
|
||||
}
|
||||
|
@ -343,6 +343,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
|
||||
| StatementKind::Intrinsic(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::PlaceMention(..)
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => {}
|
||||
}
|
||||
|
||||
@ -1493,6 +1494,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
||||
| StatementKind::Coverage(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::PlaceMention(..)
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => {}
|
||||
}
|
||||
|
||||
|
@ -151,6 +151,10 @@ impl<'tcx> Stable<'tcx> for mir::StatementKind<'tcx> {
|
||||
mir::StatementKind::ConstEvalCounter => {
|
||||
stable_mir::mir::StatementKind::ConstEvalCounter
|
||||
}
|
||||
// BackwardIncompatibleDropHint has no semantics, so it is translated to Nop.
|
||||
mir::StatementKind::BackwardIncompatibleDropHint { .. } => {
|
||||
stable_mir::mir::StatementKind::Nop
|
||||
}
|
||||
mir::StatementKind::Nop => stable_mir::mir::StatementKind::Nop,
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use rustc_middle::ty::util::{AlwaysRequiresDrop, needs_drop_components};
|
||||
use rustc_middle::ty::{self, EarlyBinder, GenericArgsRef, Ty, TyCtxt};
|
||||
use rustc_session::Limit;
|
||||
use rustc_span::sym;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use crate::errors::NeedsDropOverflow;
|
||||
|
||||
@ -23,7 +23,7 @@ fn needs_drop_raw<'tcx>(
|
||||
// needs drop.
|
||||
let adt_has_dtor =
|
||||
|adt_def: ty::AdtDef<'tcx>| adt_def.destructor(tcx).map(|_| DtorType::Significant);
|
||||
let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_dtor, false)
|
||||
let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_dtor, false, false)
|
||||
.filter(filter_array_elements(tcx, query.typing_env))
|
||||
.next()
|
||||
.is_some();
|
||||
@ -41,7 +41,7 @@ fn needs_async_drop_raw<'tcx>(
|
||||
// it needs async drop.
|
||||
let adt_has_async_dtor =
|
||||
|adt_def: ty::AdtDef<'tcx>| adt_def.async_destructor(tcx).map(|_| DtorType::Significant);
|
||||
let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_async_dtor, false)
|
||||
let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_async_dtor, false, false)
|
||||
.filter(filter_array_elements(tcx, query.typing_env))
|
||||
.next()
|
||||
.is_some();
|
||||
@ -77,6 +77,7 @@ fn has_significant_drop_raw<'tcx>(
|
||||
query.typing_env,
|
||||
adt_consider_insignificant_dtor(tcx),
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.filter(filter_array_elements(tcx, query.typing_env))
|
||||
.next()
|
||||
@ -88,8 +89,8 @@ fn has_significant_drop_raw<'tcx>(
|
||||
struct NeedsDropTypes<'tcx, F> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
typing_env: ty::TypingEnv<'tcx>,
|
||||
// Whether to reveal coroutine witnesses, this is set
|
||||
// to `false` unless we compute `needs_drop` for a coroutine witness.
|
||||
/// Whether to reveal coroutine witnesses, this is set
|
||||
/// to `false` unless we compute `needs_drop` for a coroutine witness.
|
||||
reveal_coroutine_witnesses: bool,
|
||||
query_ty: Ty<'tcx>,
|
||||
seen_tys: FxHashSet<Ty<'tcx>>,
|
||||
@ -100,6 +101,9 @@ struct NeedsDropTypes<'tcx, F> {
|
||||
unchecked_tys: Vec<(Ty<'tcx>, usize)>,
|
||||
recursion_limit: Limit,
|
||||
adt_components: F,
|
||||
/// Set this to true if an exhaustive list of types involved in
|
||||
/// drop obligation is requested.
|
||||
exhaustive: bool,
|
||||
}
|
||||
|
||||
impl<'tcx, F> NeedsDropTypes<'tcx, F> {
|
||||
@ -107,6 +111,7 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
typing_env: ty::TypingEnv<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
exhaustive: bool,
|
||||
adt_components: F,
|
||||
) -> Self {
|
||||
let mut seen_tys = FxHashSet::default();
|
||||
@ -114,14 +119,22 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
|
||||
Self {
|
||||
tcx,
|
||||
typing_env,
|
||||
reveal_coroutine_witnesses: false,
|
||||
reveal_coroutine_witnesses: exhaustive,
|
||||
seen_tys,
|
||||
query_ty: ty,
|
||||
unchecked_tys: vec![(ty, 0)],
|
||||
recursion_limit: tcx.recursion_limit(),
|
||||
adt_components,
|
||||
exhaustive,
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when `ty` is found to always require drop.
|
||||
/// If the exhaustive flag is true, then `Ok(ty)` is returned like any other type.
|
||||
/// Otherwise, `Err(AlwaysRequireDrop)` is returned, which will cause iteration to abort.
|
||||
fn always_drop_component(&self, ty: Ty<'tcx>) -> NeedsDropResult<Ty<'tcx>> {
|
||||
if self.exhaustive { Ok(ty) } else { Err(AlwaysRequiresDrop) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, F, I> Iterator for NeedsDropTypes<'tcx, F>
|
||||
@ -131,19 +144,22 @@ where
|
||||
{
|
||||
type Item = NeedsDropResult<Ty<'tcx>>;
|
||||
|
||||
#[instrument(level = "debug", skip(self), ret)]
|
||||
fn next(&mut self) -> Option<NeedsDropResult<Ty<'tcx>>> {
|
||||
let tcx = self.tcx;
|
||||
|
||||
while let Some((ty, level)) = self.unchecked_tys.pop() {
|
||||
debug!(?ty, "needs_drop_components: inspect");
|
||||
if !self.recursion_limit.value_within_limit(level) {
|
||||
// Not having a `Span` isn't great. But there's hopefully some other
|
||||
// recursion limit error as well.
|
||||
debug!("needs_drop_components: recursion limit exceeded");
|
||||
tcx.dcx().emit_err(NeedsDropOverflow { query_ty: self.query_ty });
|
||||
return Some(Err(AlwaysRequiresDrop));
|
||||
return Some(self.always_drop_component(ty));
|
||||
}
|
||||
|
||||
let components = match needs_drop_components(tcx, ty) {
|
||||
Err(e) => return Some(Err(e)),
|
||||
Err(AlwaysRequiresDrop) => return Some(self.always_drop_component(ty)),
|
||||
Ok(components) => components,
|
||||
};
|
||||
debug!("needs_drop_components({:?}) = {:?}", ty, components);
|
||||
@ -171,7 +187,7 @@ where
|
||||
if self.reveal_coroutine_witnesses {
|
||||
queue_type(self, args.as_coroutine().witness());
|
||||
} else {
|
||||
return Some(Err(AlwaysRequiresDrop));
|
||||
return Some(self.always_drop_component(ty));
|
||||
}
|
||||
}
|
||||
ty::CoroutineWitness(def_id, args) => {
|
||||
@ -186,7 +202,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
_ if component.is_copy_modulo_regions(tcx, self.typing_env) => (),
|
||||
_ if component.is_copy_modulo_regions(tcx, self.typing_env) => {}
|
||||
|
||||
ty::Closure(_, args) => {
|
||||
for upvar in args.as_closure().upvar_tys() {
|
||||
@ -205,7 +221,9 @@ where
|
||||
// impl then check whether the field types need `Drop`.
|
||||
ty::Adt(adt_def, args) => {
|
||||
let tys = match (self.adt_components)(adt_def, args) {
|
||||
Err(e) => return Some(Err(e)),
|
||||
Err(AlwaysRequiresDrop) => {
|
||||
return Some(self.always_drop_component(ty));
|
||||
}
|
||||
Ok(tys) => tys,
|
||||
};
|
||||
for required_ty in tys {
|
||||
@ -230,7 +248,8 @@ where
|
||||
}
|
||||
|
||||
ty::Foreign(_) | ty::Dynamic(..) => {
|
||||
return Some(Err(AlwaysRequiresDrop));
|
||||
debug!("needs_drop_components: foreign or dynamic");
|
||||
return Some(self.always_drop_component(ty));
|
||||
}
|
||||
|
||||
ty::Bool
|
||||
@ -280,6 +299,7 @@ fn drop_tys_helper<'tcx>(
|
||||
typing_env: ty::TypingEnv<'tcx>,
|
||||
adt_has_dtor: impl Fn(ty::AdtDef<'tcx>) -> Option<DtorType>,
|
||||
only_significant: bool,
|
||||
exhaustive: bool,
|
||||
) -> impl Iterator<Item = NeedsDropResult<Ty<'tcx>>> {
|
||||
fn with_query_cache<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
@ -343,7 +363,7 @@ fn drop_tys_helper<'tcx>(
|
||||
.map(|v| v.into_iter())
|
||||
};
|
||||
|
||||
NeedsDropTypes::new(tcx, typing_env, ty, adt_components)
|
||||
NeedsDropTypes::new(tcx, typing_env, ty, exhaustive, adt_components)
|
||||
}
|
||||
|
||||
fn adt_consider_insignificant_dtor<'tcx>(
|
||||
@ -384,6 +404,7 @@ fn adt_drop_tys<'tcx>(
|
||||
ty::TypingEnv::non_body_analysis(tcx, def_id),
|
||||
adt_has_dtor,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map(|components| tcx.mk_type_list(&components))
|
||||
@ -401,11 +422,31 @@ fn adt_significant_drop_tys(
|
||||
ty::TypingEnv::non_body_analysis(tcx, def_id),
|
||||
adt_consider_insignificant_dtor(tcx),
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map(|components| tcx.mk_type_list(&components))
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(tcx), ret)]
|
||||
fn list_significant_drop_tys<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
|
||||
) -> &'tcx ty::List<Ty<'tcx>> {
|
||||
tcx.mk_type_list(
|
||||
&drop_tys_helper(
|
||||
tcx,
|
||||
ty.value,
|
||||
ty::TypingEnv { typing_mode: ty::TypingMode::PostAnalysis, param_env: ty.param_env },
|
||||
adt_consider_insignificant_dtor(tcx),
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.filter_map(|res| res.ok())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn provide(providers: &mut Providers) {
|
||||
*providers = Providers {
|
||||
needs_drop_raw,
|
||||
@ -413,6 +454,7 @@ pub(crate) fn provide(providers: &mut Providers) {
|
||||
has_significant_drop_raw,
|
||||
adt_drop_tys,
|
||||
adt_significant_drop_tys,
|
||||
list_significant_drop_tys,
|
||||
..*providers
|
||||
};
|
||||
}
|
||||
|
@ -233,6 +233,7 @@ fn check_statement<'tcx>(
|
||||
| StatementKind::PlaceMention(..)
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::ConstEvalCounter
|
||||
| StatementKind::BackwardIncompatibleDropHint { .. }
|
||||
| StatementKind::Nop => Ok(()),
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
// This test is to demonstrate that the lint is gated behind Edition and
|
||||
// is triggered only for Edition 2021 and before.
|
||||
// This test ensures that `tail_expr_drop_order` does not activate in case Edition 2024 is used
|
||||
// because this is a migration lint.
|
||||
// Only `cargo fix --edition 2024` shall activate this lint.
|
||||
|
||||
//@ check-pass
|
||||
//@ edition: 2024
|
||||
//@ compile-flags: -Z unstable-options
|
||||
//@ edition: 2024
|
||||
|
||||
#![deny(tail_expr_drop_order)]
|
||||
|
||||
|
@ -1,13 +1,26 @@
|
||||
//@ edition: 2021
|
||||
|
||||
// Edition 2024 lint for change in drop order at tail expression
|
||||
// This lint is to capture potential change in program semantics
|
||||
// due to implementation of RFC 3606 <https://github.com/rust-lang/rfcs/pull/3606>
|
||||
//@ edition: 2021
|
||||
//@ build-fail
|
||||
|
||||
#![deny(tail_expr_drop_order)]
|
||||
#![deny(tail_expr_drop_order)] //~ NOTE: the lint level is defined here
|
||||
#![allow(dropping_copy_types)]
|
||||
|
||||
struct LoudDropper;
|
||||
impl Drop for LoudDropper {
|
||||
//~^ NOTE: `#1` invokes this custom destructor
|
||||
//~| NOTE: `x` invokes this custom destructor
|
||||
//~| NOTE: `#1` invokes this custom destructor
|
||||
//~| NOTE: `x` invokes this custom destructor
|
||||
//~| NOTE: `#1` invokes this custom destructor
|
||||
//~| NOTE: `x` invokes this custom destructor
|
||||
//~| NOTE: `#1` invokes this custom destructor
|
||||
//~| NOTE: `x` invokes this custom destructor
|
||||
//~| NOTE: `#1` invokes this custom destructor
|
||||
//~| NOTE: `future` invokes this custom destructor
|
||||
//~| NOTE: `_x` invokes this custom destructor
|
||||
//~| NOTE: `#1` invokes this custom destructor
|
||||
fn drop(&mut self) {
|
||||
// This destructor should be considered significant because it is a custom destructor
|
||||
// and we will assume that the destructor can generate side effects arbitrarily so that
|
||||
@ -23,30 +36,70 @@ impl LoudDropper {
|
||||
|
||||
fn should_lint() -> i32 {
|
||||
let x = LoudDropper;
|
||||
//~^ NOTE: `x` calls a custom destructor
|
||||
//~| NOTE: `x` will be dropped later as of Edition 2024
|
||||
// Should lint
|
||||
x.get() + LoudDropper.get()
|
||||
//~^ ERROR: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
|
||||
//~^ ERROR: relative drop order changing in Rust 2024
|
||||
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
|
||||
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
//~| WARN: this changes meaning in Rust 2024
|
||||
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
|
||||
//~| NOTE: for more information, see issue #123739
|
||||
}
|
||||
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
||||
fn should_not_lint_closure() -> impl FnOnce() -> i32 {
|
||||
let x = LoudDropper;
|
||||
move || {
|
||||
// Should not lint because ...
|
||||
x.get() + LoudDropper.get()
|
||||
}
|
||||
// ^ closure captures like `x` are always dropped last by contract
|
||||
}
|
||||
|
||||
fn should_lint_closure() -> impl FnOnce() -> i32 {
|
||||
fn should_lint_in_nested_items() {
|
||||
fn should_lint_me() -> i32 {
|
||||
let x = LoudDropper;
|
||||
move || x.get() + LoudDropper.get()
|
||||
//~^ ERROR: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
|
||||
//~^ NOTE: `x` calls a custom destructor
|
||||
//~| NOTE: `x` will be dropped later as of Edition 2024
|
||||
// Should lint
|
||||
x.get() + LoudDropper.get()
|
||||
//~^ ERROR: relative drop order changing in Rust 2024
|
||||
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
|
||||
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
//~| WARN: this changes meaning in Rust 2024
|
||||
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
|
||||
//~| NOTE: for more information, see issue #123739
|
||||
}
|
||||
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
|
||||
}
|
||||
|
||||
fn should_not_lint_params(x: LoudDropper) -> i32 {
|
||||
// Should not lint because ...
|
||||
x.get() + LoudDropper.get()
|
||||
}
|
||||
// ^ function parameters like `x` are always dropped last
|
||||
|
||||
fn should_not_lint() -> i32 {
|
||||
let x = LoudDropper;
|
||||
// Should not lint
|
||||
x.get()
|
||||
}
|
||||
|
||||
fn should_not_lint_in_nested_block() -> i32 {
|
||||
fn should_lint_in_nested_block() -> i32 {
|
||||
let x = LoudDropper;
|
||||
// Should not lint because Edition 2021 drops temporaries in blocks earlier already
|
||||
//~^ NOTE: `x` calls a custom destructor
|
||||
//~| NOTE: `x` will be dropped later as of Edition 2024
|
||||
{ LoudDropper.get() }
|
||||
//~^ ERROR: relative drop order changing in Rust 2024
|
||||
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
|
||||
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
//~| WARN: this changes meaning in Rust 2024
|
||||
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
|
||||
//~| NOTE: for more information, see issue #123739
|
||||
}
|
||||
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
||||
fn should_not_lint_in_match_arm() -> i32 {
|
||||
let x = LoudDropper;
|
||||
@ -56,14 +109,144 @@ fn should_not_lint_in_match_arm() -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
fn should_lint_in_nested_items() {
|
||||
fn should_lint_me() -> i32 {
|
||||
fn should_not_lint_when_consumed() -> (LoudDropper, i32) {
|
||||
let x = LoudDropper;
|
||||
// Should lint
|
||||
x.get() + LoudDropper.get()
|
||||
//~^ ERROR: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
|
||||
//~| WARN: this changes meaning in Rust 2024
|
||||
// Should not lint because `LoudDropper` is consumed by the return value
|
||||
(LoudDropper, x.get())
|
||||
}
|
||||
|
||||
struct MyAdt {
|
||||
a: LoudDropper,
|
||||
b: LoudDropper,
|
||||
}
|
||||
|
||||
fn should_not_lint_when_consumed_in_ctor() -> MyAdt {
|
||||
let a = LoudDropper;
|
||||
// Should not lint
|
||||
MyAdt { a, b: LoudDropper }
|
||||
}
|
||||
|
||||
fn should_not_lint_when_moved() -> i32 {
|
||||
let x = LoudDropper;
|
||||
drop(x);
|
||||
// Should not lint because `x` is not live
|
||||
LoudDropper.get()
|
||||
}
|
||||
|
||||
fn should_lint_into_async_body() -> i32 {
|
||||
async fn f() {
|
||||
async fn f() {}
|
||||
let x = LoudDropper;
|
||||
f().await;
|
||||
drop(x);
|
||||
}
|
||||
|
||||
let future = f();
|
||||
//~^ NOTE: `future` calls a custom destructor
|
||||
//~| NOTE: `future` will be dropped later as of Edition 2024
|
||||
LoudDropper.get()
|
||||
//~^ ERROR: relative drop order changing in Rust 2024
|
||||
//~| WARN: this changes meaning in Rust 2024
|
||||
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
|
||||
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
|
||||
//~| NOTE: for more information, see issue #123739
|
||||
}
|
||||
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
||||
fn should_lint_generics<T: Default>() -> &'static str {
|
||||
fn extract<T>(_: &T) -> &'static str {
|
||||
todo!()
|
||||
}
|
||||
let x = T::default();
|
||||
//~^ NOTE: `x` calls a custom destructor
|
||||
//~| NOTE: `x` will be dropped later as of Edition 2024
|
||||
extract(&T::default())
|
||||
//~^ ERROR: relative drop order changing in Rust 2024
|
||||
//~| WARN: this changes meaning in Rust 2024
|
||||
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
|
||||
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
|
||||
//~| NOTE: for more information, see issue #123739
|
||||
}
|
||||
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
||||
fn should_lint_adt() -> i32 {
|
||||
let x: Result<LoudDropper, ()> = Ok(LoudDropper);
|
||||
//~^ NOTE: `x` calls a custom destructor
|
||||
//~| NOTE: `x` will be dropped later as of Edition 2024
|
||||
LoudDropper.get()
|
||||
//~^ ERROR: relative drop order changing in Rust 2024
|
||||
//~| WARN: this changes meaning in Rust 2024
|
||||
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
|
||||
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
|
||||
//~| NOTE: for more information, see issue #123739
|
||||
}
|
||||
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
||||
fn should_not_lint_insign_dtor() -> i32 {
|
||||
let x = String::new();
|
||||
LoudDropper.get()
|
||||
}
|
||||
|
||||
fn should_lint_with_dtor_span() -> i32 {
|
||||
struct LoudDropper3;
|
||||
impl Drop for LoudDropper3 {
|
||||
//~^ NOTE: `#1` invokes this custom destructor
|
||||
fn drop(&mut self) {
|
||||
println!("loud drop");
|
||||
}
|
||||
}
|
||||
impl LoudDropper3 {
|
||||
fn get(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
struct LoudDropper2;
|
||||
impl Drop for LoudDropper2 {
|
||||
//~^ NOTE: `x` invokes this custom destructor
|
||||
fn drop(&mut self) {
|
||||
println!("loud drop");
|
||||
}
|
||||
}
|
||||
impl LoudDropper2 {
|
||||
fn get(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
let x = LoudDropper2;
|
||||
//~^ NOTE: `x` calls a custom destructor
|
||||
//~| NOTE: `x` will be dropped later as of Edition 2024
|
||||
LoudDropper3.get()
|
||||
//~^ ERROR: relative drop order changing in Rust 2024
|
||||
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
|
||||
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
//~| WARN: this changes meaning in Rust 2024
|
||||
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
|
||||
//~| NOTE: for more information, see issue #123739
|
||||
}
|
||||
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
||||
fn should_lint_with_transient_drops() {
|
||||
drop((
|
||||
{
|
||||
LoudDropper.get()
|
||||
//~^ ERROR: relative drop order changing in Rust 2024
|
||||
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
|
||||
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
//~| WARN: this changes meaning in Rust 2024
|
||||
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
|
||||
//~| NOTE: for more information, see issue #123739
|
||||
},
|
||||
{
|
||||
let _x = LoudDropper;
|
||||
//~^ NOTE: `_x` calls a custom destructor
|
||||
//~| NOTE: `_x` will be dropped later as of Edition 2024
|
||||
},
|
||||
));
|
||||
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
@ -1,42 +1,335 @@
|
||||
error: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:27:15
|
||||
error: relative drop order changing in Rust 2024
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:42:15
|
||||
|
|
||||
LL | let x = LoudDropper;
|
||||
| - these values have significant drop implementation and will observe changes in drop order under Edition 2024
|
||||
LL | // Should lint
|
||||
| -
|
||||
| |
|
||||
| `x` calls a custom destructor
|
||||
| `x` will be dropped later as of Edition 2024
|
||||
...
|
||||
LL | x.get() + LoudDropper.get()
|
||||
| ^^^^^^^^^^^
|
||||
| |
|
||||
| this value will be stored in a temporary; let us call it `#1`
|
||||
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
...
|
||||
LL | }
|
||||
| - now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
|
||||
= warning: this changes meaning in Rust 2024
|
||||
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
|
||||
note: `#1` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
note: `x` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
|
||||
note: the lint level is defined here
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:7:9
|
||||
|
|
||||
LL | #![deny(tail_expr_drop_order)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:34:23
|
||||
error: relative drop order changing in Rust 2024
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:67:19
|
||||
|
|
||||
LL | let x = LoudDropper;
|
||||
| - these values have significant drop implementation and will observe changes in drop order under Edition 2024
|
||||
LL | move || x.get() + LoudDropper.get()
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= warning: this changes meaning in Rust 2024
|
||||
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
|
||||
|
||||
error: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:63:19
|
||||
|
|
||||
LL | let x = LoudDropper;
|
||||
| - these values have significant drop implementation and will observe changes in drop order under Edition 2024
|
||||
LL | // Should lint
|
||||
| -
|
||||
| |
|
||||
| `x` calls a custom destructor
|
||||
| `x` will be dropped later as of Edition 2024
|
||||
...
|
||||
LL | x.get() + LoudDropper.get()
|
||||
| ^^^^^^^^^^^
|
||||
| |
|
||||
| this value will be stored in a temporary; let us call it `#1`
|
||||
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
...
|
||||
LL | }
|
||||
| - now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
|
||||
= warning: this changes meaning in Rust 2024
|
||||
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
|
||||
note: `#1` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
note: `x` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: relative drop order changing in Rust 2024
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:94:7
|
||||
|
|
||||
LL | let x = LoudDropper;
|
||||
| -
|
||||
| |
|
||||
| `x` calls a custom destructor
|
||||
| `x` will be dropped later as of Edition 2024
|
||||
...
|
||||
LL | { LoudDropper.get() }
|
||||
| ^^^^^^^^^^^
|
||||
| |
|
||||
| this value will be stored in a temporary; let us call it `#1`
|
||||
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
...
|
||||
LL | }
|
||||
| - now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
|
||||
= warning: this changes meaning in Rust 2024
|
||||
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
|
||||
note: `#1` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
note: `x` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
|
||||
|
||||
error: relative drop order changing in Rust 2024
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:147:5
|
||||
|
|
||||
LL | let future = f();
|
||||
| ------
|
||||
| |
|
||||
| `future` calls a custom destructor
|
||||
| `future` will be dropped later as of Edition 2024
|
||||
...
|
||||
LL | LoudDropper.get()
|
||||
| ^^^^^^^^^^^
|
||||
| |
|
||||
| this value will be stored in a temporary; let us call it `#1`
|
||||
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
...
|
||||
LL | }
|
||||
| - now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
|
||||
= warning: this changes meaning in Rust 2024
|
||||
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
|
||||
note: `#1` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
note: `future` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
|
||||
|
||||
error: relative drop order changing in Rust 2024
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:164:14
|
||||
|
|
||||
LL | let x = T::default();
|
||||
| -
|
||||
| |
|
||||
| `x` calls a custom destructor
|
||||
| `x` will be dropped later as of Edition 2024
|
||||
...
|
||||
LL | extract(&T::default())
|
||||
| ^^^^^^^^^^^^
|
||||
| |
|
||||
| this value will be stored in a temporary; let us call it `#1`
|
||||
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
...
|
||||
LL | }
|
||||
| - now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
|
||||
= warning: this changes meaning in Rust 2024
|
||||
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
|
||||
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
|
||||
|
||||
error: relative drop order changing in Rust 2024
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:178:5
|
||||
|
|
||||
LL | let x: Result<LoudDropper, ()> = Ok(LoudDropper);
|
||||
| -
|
||||
| |
|
||||
| `x` calls a custom destructor
|
||||
| `x` will be dropped later as of Edition 2024
|
||||
...
|
||||
LL | LoudDropper.get()
|
||||
| ^^^^^^^^^^^
|
||||
| |
|
||||
| this value will be stored in a temporary; let us call it `#1`
|
||||
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
...
|
||||
LL | }
|
||||
| - now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
|
||||
= warning: this changes meaning in Rust 2024
|
||||
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
|
||||
note: `#1` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
note: `x` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
|
||||
|
||||
error: relative drop order changing in Rust 2024
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:222:5
|
||||
|
|
||||
LL | let x = LoudDropper2;
|
||||
| -
|
||||
| |
|
||||
| `x` calls a custom destructor
|
||||
| `x` will be dropped later as of Edition 2024
|
||||
...
|
||||
LL | LoudDropper3.get()
|
||||
| ^^^^^^^^^^^^
|
||||
| |
|
||||
| this value will be stored in a temporary; let us call it `#1`
|
||||
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
...
|
||||
LL | }
|
||||
| - now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
|
||||
= warning: this changes meaning in Rust 2024
|
||||
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
|
||||
note: `#1` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:195:5
|
||||
|
|
||||
LL | / impl Drop for LoudDropper3 {
|
||||
LL | |
|
||||
LL | | fn drop(&mut self) {
|
||||
LL | | println!("loud drop");
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_____^
|
||||
note: `x` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:207:5
|
||||
|
|
||||
LL | / impl Drop for LoudDropper2 {
|
||||
LL | |
|
||||
LL | | fn drop(&mut self) {
|
||||
LL | | println!("loud drop");
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_____^
|
||||
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
|
||||
|
||||
error: relative drop order changing in Rust 2024
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:235:13
|
||||
|
|
||||
LL | LoudDropper.get()
|
||||
| ^^^^^^^^^^^
|
||||
| |
|
||||
| this value will be stored in a temporary; let us call it `#1`
|
||||
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
|
||||
...
|
||||
LL | let _x = LoudDropper;
|
||||
| --
|
||||
| |
|
||||
| `_x` calls a custom destructor
|
||||
| `_x` will be dropped later as of Edition 2024
|
||||
...
|
||||
LL | ));
|
||||
| - now the temporary value is dropped here, before the local variables in the block or statement
|
||||
|
|
||||
= warning: this changes meaning in Rust 2024
|
||||
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
|
||||
note: `#1` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
note: `_x` invokes this custom destructor
|
||||
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
||||
|
|
||||
LL | / impl Drop for LoudDropper {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | |
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^
|
||||
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
|
||||
|
||||
error: aborting due to 8 previous errors
|
||||
|
||||
|
@ -11,9 +11,12 @@ Thir {
|
||||
fields: [],
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:12:32: 12:34 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -25,9 +28,12 @@ Thir {
|
||||
value: e0,
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:12:32: 12:34 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -47,9 +53,12 @@ Thir {
|
||||
},
|
||||
),
|
||||
ty: Foo,
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:12:23: 12:35 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -61,9 +70,12 @@ Thir {
|
||||
value: e2,
|
||||
},
|
||||
ty: Foo,
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:12:23: 12:35 (#0),
|
||||
},
|
||||
],
|
||||
@ -84,9 +96,12 @@ Thir {
|
||||
fields: [],
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:13:33: 13:35 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -98,9 +113,12 @@ Thir {
|
||||
value: e0,
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:13:33: 13:35 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -120,9 +138,12 @@ Thir {
|
||||
},
|
||||
),
|
||||
ty: Foo,
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:13:23: 13:36 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -134,9 +155,12 @@ Thir {
|
||||
value: e2,
|
||||
},
|
||||
ty: Foo,
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:13:23: 13:36 (#0),
|
||||
},
|
||||
],
|
||||
@ -157,9 +181,12 @@ Thir {
|
||||
fields: [],
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:14:33: 14:35 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -171,9 +198,12 @@ Thir {
|
||||
value: e0,
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:14:33: 14:35 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -193,9 +223,12 @@ Thir {
|
||||
},
|
||||
),
|
||||
ty: Foo,
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:14:24: 14:36 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -207,9 +240,12 @@ Thir {
|
||||
value: e2,
|
||||
},
|
||||
ty: Foo,
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:14:24: 14:36 (#0),
|
||||
},
|
||||
],
|
||||
@ -230,9 +266,12 @@ Thir {
|
||||
fields: [],
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:15:34: 15:36 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -244,9 +283,12 @@ Thir {
|
||||
value: e0,
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:15:34: 15:36 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -266,9 +308,12 @@ Thir {
|
||||
},
|
||||
),
|
||||
ty: Foo,
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:15:24: 15:37 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -280,9 +325,12 @@ Thir {
|
||||
value: e2,
|
||||
},
|
||||
ty: Foo,
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(3),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:15:24: 15:37 (#0),
|
||||
},
|
||||
],
|
||||
@ -312,9 +360,12 @@ Thir {
|
||||
block: b0,
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(2),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:18:11: 18:13 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -326,9 +377,12 @@ Thir {
|
||||
value: e0,
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(2),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat-const-variant.rs:18:11: 18:13 (#0),
|
||||
},
|
||||
],
|
||||
|
@ -20,9 +20,12 @@ Thir {
|
||||
block: b0,
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(2),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat.rs:4:15: 4:17 (#0),
|
||||
},
|
||||
Expr {
|
||||
@ -34,9 +37,12 @@ Thir {
|
||||
value: e0,
|
||||
},
|
||||
ty: (),
|
||||
temp_lifetime: TempLifetime {
|
||||
temp_lifetime: Some(
|
||||
Node(2),
|
||||
),
|
||||
backwards_incompatible: None,
|
||||
},
|
||||
span: $DIR/thir-flat.rs:4:15: 4:17 (#0),
|
||||
},
|
||||
],
|
||||
|
@ -26,7 +26,7 @@ params: [
|
||||
body:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(26))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:15:32: 21:2 (#0)
|
||||
kind:
|
||||
Scope {
|
||||
@ -35,7 +35,7 @@ body:
|
||||
value:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(26))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:15:32: 21:2 (#0)
|
||||
kind:
|
||||
Block {
|
||||
@ -47,7 +47,7 @@ body:
|
||||
expr:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(26))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:16:5: 20:6 (#0)
|
||||
kind:
|
||||
Scope {
|
||||
@ -56,14 +56,14 @@ body:
|
||||
value:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(26))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:16:5: 20:6 (#0)
|
||||
kind:
|
||||
Match {
|
||||
scrutinee:
|
||||
Expr {
|
||||
ty: Foo
|
||||
temp_lifetime: Some(Node(26))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:16:11: 16:14 (#0)
|
||||
kind:
|
||||
Scope {
|
||||
@ -72,7 +72,7 @@ body:
|
||||
value:
|
||||
Expr {
|
||||
ty: Foo
|
||||
temp_lifetime: Some(Node(26))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:16:11: 16:14 (#0)
|
||||
kind:
|
||||
VarRef {
|
||||
@ -123,7 +123,7 @@ body:
|
||||
body:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(13))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(13)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:17:36: 17:40 (#0)
|
||||
kind:
|
||||
Scope {
|
||||
@ -132,7 +132,7 @@ body:
|
||||
value:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(13))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(13)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:17:36: 17:40 (#0)
|
||||
kind:
|
||||
Literal( lit: Spanned { node: Bool(true), span: $DIR/thir-tree-match.rs:17:36: 17:40 (#0) }, neg: false)
|
||||
@ -175,7 +175,7 @@ body:
|
||||
body:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(19))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(19)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:18:27: 18:32 (#0)
|
||||
kind:
|
||||
Scope {
|
||||
@ -184,7 +184,7 @@ body:
|
||||
value:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(19))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(19)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:18:27: 18:32 (#0)
|
||||
kind:
|
||||
Literal( lit: Spanned { node: Bool(false), span: $DIR/thir-tree-match.rs:18:27: 18:32 (#0) }, neg: false)
|
||||
@ -219,7 +219,7 @@ body:
|
||||
body:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(24))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:19:24: 19:28 (#0)
|
||||
kind:
|
||||
Scope {
|
||||
@ -228,7 +228,7 @@ body:
|
||||
value:
|
||||
Expr {
|
||||
ty: bool
|
||||
temp_lifetime: Some(Node(24))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:19:24: 19:28 (#0)
|
||||
kind:
|
||||
Literal( lit: Spanned { node: Bool(true), span: $DIR/thir-tree-match.rs:19:24: 19:28 (#0) }, neg: false)
|
||||
@ -257,7 +257,7 @@ params: [
|
||||
body:
|
||||
Expr {
|
||||
ty: ()
|
||||
temp_lifetime: Some(Node(2))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:23:11: 23:13 (#0)
|
||||
kind:
|
||||
Scope {
|
||||
@ -266,7 +266,7 @@ body:
|
||||
value:
|
||||
Expr {
|
||||
ty: ()
|
||||
temp_lifetime: Some(Node(2))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree-match.rs:23:11: 23:13 (#0)
|
||||
kind:
|
||||
Block {
|
||||
|
@ -4,7 +4,7 @@ params: [
|
||||
body:
|
||||
Expr {
|
||||
ty: ()
|
||||
temp_lifetime: Some(Node(2))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree.rs:4:15: 4:17 (#0)
|
||||
kind:
|
||||
Scope {
|
||||
@ -13,7 +13,7 @@ body:
|
||||
value:
|
||||
Expr {
|
||||
ty: ()
|
||||
temp_lifetime: Some(Node(2))
|
||||
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None }
|
||||
span: $DIR/thir-tree.rs:4:15: 4:17 (#0)
|
||||
kind:
|
||||
Block {
|
||||
|
Loading…
Reference in New Issue
Block a user