Skip to main content

rustc_mir_transform/
lint_tail_expr_drop_order.rs

1use std::cell::RefCell;
2use std::collections::hash_map;
3use std::rc::Rc;
4
5use itertools::Itertools as _;
6use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
7use rustc_data_structures::unord::{UnordMap, UnordSet};
8use rustc_errors::formatting::DiagMessageAddArg;
9use rustc_errors::{Subdiagnostic, msg};
10use rustc_hir::CRATE_HIR_ID;
11use rustc_hir::def_id::LocalDefId;
12use rustc_index::bit_set::MixedBitSet;
13use rustc_index::{IndexSlice, IndexVec};
14use rustc_macros::{Diagnostic, Subdiagnostic};
15use rustc_middle::bug;
16use rustc_middle::mir::{
17    self, BasicBlock, Body, ClearCrossCrate, Local, Location, MirDumper, Place, StatementKind,
18    TerminatorKind,
19};
20use rustc_middle::ty::significant_drop_order::{
21    extract_component_with_significant_dtor, ty_dtor_span,
22};
23use rustc_middle::ty::{self, TyCtxt};
24use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
25use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
26use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor};
27use rustc_session::lint;
28use rustc_session::lint::builtin::TAIL_EXPR_DROP_ORDER;
29use rustc_span::{DUMMY_SP, Span, Symbol};
30use tracing::debug;
31
32fn place_has_common_prefix<'tcx>(left: &Place<'tcx>, right: &Place<'tcx>) -> bool {
33    left.local == right.local
34        && left.projection.iter().zip(right.projection).all(|(left, right)| left == right)
35}
36
37/// Cache entry of `drop` at a `BasicBlock`
38#[derive(#[automatically_derived]
impl ::core::fmt::Debug for MovePathIndexAtBlock {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            MovePathIndexAtBlock::Unknown =>
                ::core::fmt::Formatter::write_str(f, "Unknown"),
            MovePathIndexAtBlock::None =>
                ::core::fmt::Formatter::write_str(f, "None"),
            MovePathIndexAtBlock::Some(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Some",
                    &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for MovePathIndexAtBlock {
    #[inline]
    fn clone(&self) -> MovePathIndexAtBlock {
        let _: ::core::clone::AssertParamIsClone<MovePathIndex>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for MovePathIndexAtBlock { }Copy)]
39enum MovePathIndexAtBlock {
40    /// We know nothing yet
41    Unknown,
42    /// We know that the `drop` here has no effect
43    None,
44    /// We know that the `drop` here will invoke a destructor
45    Some(MovePathIndex),
46}
47
48struct DropsReachable<'a, 'mir, 'tcx> {
49    body: &'a Body<'tcx>,
50    place: &'a Place<'tcx>,
51    drop_span: &'a mut Option<Span>,
52    move_data: &'a MoveData<'tcx>,
53    maybe_init: &'a mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
54    block_drop_value_info: &'a mut IndexSlice<BasicBlock, MovePathIndexAtBlock>,
55    collected_drops: &'a mut MixedBitSet<MovePathIndex>,
56    visited: FxHashMap<BasicBlock, Rc<RefCell<MixedBitSet<MovePathIndex>>>>,
57}
58
59impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
60    fn visit(&mut self, block: BasicBlock) {
61        let move_set_size = self.move_data.move_paths.len();
62        let make_new_path_set = || Rc::new(RefCell::new(MixedBitSet::new_empty(move_set_size)));
63
64        let data = &self.body.basic_blocks[block];
65        let Some(terminator) = &data.terminator else { return };
66        // Given that we observe these dropped locals here at `block` so far, we will try to update
67        // the successor blocks. An occupied entry at `block` in `self.visited` signals that we
68        // have visited `block` before.
69        let dropped_local_here =
70            Rc::clone(self.visited.entry(block).or_insert_with(make_new_path_set));
71        // We could have invoked reverse lookup for a `MovePathIndex` every time, but unfortunately
72        // it is expensive. Let's cache them in `self.block_drop_value_info`.
73        match self.block_drop_value_info[block] {
74            MovePathIndexAtBlock::Some(dropped) => {
75                dropped_local_here.borrow_mut().insert(dropped);
76            }
77            MovePathIndexAtBlock::Unknown => {
78                if let TerminatorKind::Drop { place, .. } = &terminator.kind
79                    && let LookupResult::Exact(idx) | LookupResult::Parent(Some(idx)) =
80                        self.move_data.rev_lookup.find(place.as_ref())
81                {
82                    // Since we are working with MIRs at a very early stage, observing a `drop`
83                    // terminator is not indicative enough that the drop will definitely happen.
84                    // That is decided in the drop elaboration pass instead. Therefore, we need to
85                    // consult with the maybe-initialization information.
86                    self.maybe_init.seek_before_primary_effect(Location {
87                        block,
88                        statement_index: data.statements.len(),
89                    });
90
91                    // Check if the drop of `place` under inspection is really in effect. This is
92                    // true only when `place` may have been initialized along a control flow path
93                    // from a BID to the drop program point today. In other words, this is where
94                    // the drop of `place` will happen in the future instead.
95                    if let MaybeReachable::Reachable(maybe_init) = self.maybe_init.get()
96                        && maybe_init.contains(idx)
97                    {
98                        // We also cache the drop information, so that we do not need to check on
99                        // data-flow cursor again.
100                        self.block_drop_value_info[block] = MovePathIndexAtBlock::Some(idx);
101                        dropped_local_here.borrow_mut().insert(idx);
102                    } else {
103                        self.block_drop_value_info[block] = MovePathIndexAtBlock::None;
104                    }
105                }
106            }
107            MovePathIndexAtBlock::None => {}
108        }
109
110        for succ in terminator.successors() {
111            let target = &self.body.basic_blocks[succ];
112            if target.is_cleanup {
113                continue;
114            }
115
116            // As long as we are passing through a new block, or new dropped places to propagate,
117            // we will proceed with `succ`
118            let dropped_local_there = match self.visited.entry(succ) {
119                hash_map::Entry::Occupied(occupied_entry) => {
120                    if succ == block
121                        || !occupied_entry.get().borrow_mut().union(&*dropped_local_here.borrow())
122                    {
123                        // `succ` has been visited but no new drops observed so far,
124                        // so we can bail on `succ` until new drop information arrives
125                        continue;
126                    }
127                    Rc::clone(occupied_entry.get())
128                }
129                hash_map::Entry::Vacant(vacant_entry) => Rc::clone(
130                    vacant_entry.insert(Rc::new(RefCell::new(dropped_local_here.borrow().clone()))),
131                ),
132            };
133            if let Some(terminator) = &target.terminator
134                && let TerminatorKind::Drop {
135                    place: dropped_place,
136                    target: _,
137                    unwind: _,
138                    replace: _,
139                    drop: _,
140                    async_fut: _,
141                } = &terminator.kind
142                && place_has_common_prefix(dropped_place, self.place)
143            {
144                // We have now reached the current drop of the `place`.
145                // Let's check the observed dropped places in.
146                self.collected_drops.union(&*dropped_local_there.borrow());
147                if self.drop_span.is_none() {
148                    // FIXME(@dingxiangfei2009): it turns out that `self.body.source_scopes` are
149                    // still a bit wonky. There is a high chance that this span still points to a
150                    // block rather than a statement semicolon.
151                    *self.drop_span = Some(terminator.source_info.span);
152                }
153                // Now we have discovered a simple control flow path from a future drop point
154                // to the current drop point.
155                // We will not continue from there.
156            } else {
157                self.visit(succ)
158            }
159        }
160    }
161}
162
163/// Check if a moved place at `idx` is a part of a BID.
164/// The use of this check is that we will consider drops on these
165/// as a drop of the overall BID and, thus, we can exclude it from the diagnosis.
166fn place_descendent_of_bids<'tcx>(
167    mut idx: MovePathIndex,
168    move_data: &MoveData<'tcx>,
169    bids: &UnordSet<&Place<'tcx>>,
170) -> bool {
171    loop {
172        let path = &move_data.move_paths[idx];
173        if bids.contains(&path.place) {
174            return true;
175        }
176        if let Some(parent) = path.parent {
177            idx = parent;
178        } else {
179            return false;
180        }
181    }
182}
183
184/// The core of the lint `tail-expr-drop-order`
185pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<'tcx>) {
186    if #[allow(non_exhaustive_omitted_patterns)] match tcx.def_kind(def_id) {
    rustc_hir::def::DefKind::SyntheticCoroutineBody => true,
    _ => false,
}matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::SyntheticCoroutineBody) {
187        // A synthetic coroutine has no HIR body and it is enough to just analyse the original body
188        return;
189    }
190    if body.span.edition().at_least_rust_2024()
191        || tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
192    {
193        return;
194    }
195
196    // FIXME(typing_env): This should be able to reveal the opaques local to the
197    // body using the typeck results.
198    let typing_env = ty::TypingEnv::non_body_analysis(tcx, def_id);
199
200    // ## About BIDs in blocks ##
201    // Track the set of blocks that contain a backwards-incompatible drop (BID)
202    // and, for each block, the vector of locations.
203    //
204    // We group them per-block because they tend to scheduled in the same drop ladder block.
205    let mut bid_per_block = FxIndexMap::default();
206    let mut bid_places = UnordSet::new();
207
208    let mut ty_dropped_components = UnordMap::default();
209    for (block, data) in body.basic_blocks.iter_enumerated() {
210        for (statement_index, stmt) in data.statements.iter().enumerate() {
211            if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } = &stmt.kind {
212                let ty = place.ty(body, tcx).ty;
213                if ty_dropped_components
214                    .entry(ty)
215                    .or_insert_with(|| extract_component_with_significant_dtor(tcx, typing_env, ty))
216                    .is_empty()
217                {
218                    continue;
219                }
220                bid_per_block
221                    .entry(block)
222                    .or_insert(::alloc::vec::Vec::new()vec![])
223                    .push((Location { block, statement_index }, &**place));
224                bid_places.insert(&**place);
225            }
226        }
227    }
228    if bid_per_block.is_empty() {
229        return;
230    }
231
232    if let Some(dumper) = MirDumper::new(tcx, "lint_tail_expr_drop_order", body) {
233        dumper.dump_mir(body);
234    }
235
236    let locals_with_user_names = collect_user_names(body);
237    let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
238
239    // Compute the "maybe initialized" information for this body.
240    // When we encounter a DROP of some place P we only care
241    // about the drop if `P` may be initialized.
242    let move_data = MoveData::gather_moves(body, tcx, |_| true);
243    let mut maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data)
244        .iterate_to_fixpoint(tcx, body, None)
245        .into_results_cursor(body);
246    let mut block_drop_value_info =
247        IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
248    for (&block, candidates) in &bid_per_block {
249        // We will collect drops on locals on paths between BID points to their actual drop locations
250        // into `all_locals_dropped`.
251        let mut all_locals_dropped = MixedBitSet::new_empty(move_data.move_paths.len());
252        let mut drop_span = None;
253        for &(_, place) in candidates.iter() {
254            let mut collected_drops = MixedBitSet::new_empty(move_data.move_paths.len());
255            // ## On detecting change in relative drop order ##
256            // Iterate through each BID-containing block `block`.
257            // If the place `P` targeted by the BID is "maybe initialized",
258            // then search forward to find the actual `DROP(P)` point.
259            // Everything dropped between the BID and the actual drop point
260            // is something whose relative drop order will change.
261            DropsReachable {
262                body,
263                place,
264                drop_span: &mut drop_span,
265                move_data: &move_data,
266                maybe_init: &mut maybe_init,
267                block_drop_value_info: &mut block_drop_value_info,
268                collected_drops: &mut collected_drops,
269                visited: Default::default(),
270            }
271            .visit(block);
272            // Compute the set `all_locals_dropped` of local variables that are dropped
273            // after the BID point but before the current drop point.
274            //
275            // These are the variables whose drop impls will be reordered with respect
276            // to `place`.
277            all_locals_dropped.union(&collected_drops);
278        }
279
280        // We shall now exclude some local bindings for the following cases.
281        {
282            let mut to_exclude = MixedBitSet::new_empty(all_locals_dropped.domain_size());
283            // We will now do subtraction from the candidate dropped locals, because of the
284            // following reasons.
285            for path_idx in all_locals_dropped.iter() {
286                let move_path = &move_data.move_paths[path_idx];
287                let dropped_local = move_path.place.local;
288                // a) A return value _0 will eventually be used
289                // Example:
290                // fn f() -> Droppy {
291                //     let _x = Droppy;
292                //     Droppy
293                // }
294                // _0 holds the literal `Droppy` and rightfully `_x` has to be dropped first
295                if dropped_local == Local::ZERO {
296                    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs:296",
                        "rustc_mir_transform::lint_tail_expr_drop_order",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs"),
                        ::tracing_core::__macro_support::Option::Some(296u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_transform::lint_tail_expr_drop_order"),
                        ::tracing_core::field::FieldSet::new(&["message",
                                        "dropped_local"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("skip return value")
                                            as &dyn Value)),
                                (&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&debug(&dropped_local)
                                            as &dyn Value))])
            });
    } else { ; }
};debug!(?dropped_local, "skip return value");
297                    to_exclude.insert(path_idx);
298                    continue;
299                }
300                // b) If we are analysing a closure, the captures are still dropped last.
301                // This is part of the closure capture lifetime contract.
302                // They are similar to the return value _0 with respect to lifetime rules.
303                if is_closure_like && #[allow(non_exhaustive_omitted_patterns)] match dropped_local {
    ty::CAPTURE_STRUCT_LOCAL => true,
    _ => false,
}matches!(dropped_local, ty::CAPTURE_STRUCT_LOCAL) {
304                    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs:304",
                        "rustc_mir_transform::lint_tail_expr_drop_order",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs"),
                        ::tracing_core::__macro_support::Option::Some(304u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_transform::lint_tail_expr_drop_order"),
                        ::tracing_core::field::FieldSet::new(&["message",
                                        "dropped_local"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("skip closure captures")
                                            as &dyn Value)),
                                (&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&debug(&dropped_local)
                                            as &dyn Value))])
            });
    } else { ; }
};debug!(?dropped_local, "skip closure captures");
305                    to_exclude.insert(path_idx);
306                    continue;
307                }
308                // c) Sometimes we collect places that are projections into the BID locals,
309                // so they are considered dropped now.
310                // Example:
311                // struct NotVeryDroppy(Droppy);
312                // impl Drop for Droppy {..}
313                // fn f() -> NotVeryDroppy {
314                //    let x = NotVeryDroppy(droppy());
315                //    {
316                //        let y: Droppy = x.0;
317                //        NotVeryDroppy(y)
318                //    }
319                // }
320                // `y` takes `x.0`, which invalidates `x` as a complete `NotVeryDroppy`
321                // so there is no point in linting against `x` any more.
322                if place_descendent_of_bids(path_idx, &move_data, &bid_places) {
323                    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs:323",
                        "rustc_mir_transform::lint_tail_expr_drop_order",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs"),
                        ::tracing_core::__macro_support::Option::Some(323u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_transform::lint_tail_expr_drop_order"),
                        ::tracing_core::field::FieldSet::new(&["message",
                                        "dropped_local"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("skip descendent of bids")
                                            as &dyn Value)),
                                (&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&debug(&dropped_local)
                                            as &dyn Value))])
            });
    } else { ; }
};debug!(?dropped_local, "skip descendent of bids");
324                    to_exclude.insert(path_idx);
325                    continue;
326                }
327                let observer_ty = move_path.place.ty(body, tcx).ty;
328                // d) The collected local has no custom destructor that passes our ecosystem filter.
329                if ty_dropped_components
330                    .entry(observer_ty)
331                    .or_insert_with(|| {
332                        extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
333                    })
334                    .is_empty()
335                {
336                    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs:336",
                        "rustc_mir_transform::lint_tail_expr_drop_order",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs"),
                        ::tracing_core::__macro_support::Option::Some(336u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_transform::lint_tail_expr_drop_order"),
                        ::tracing_core::field::FieldSet::new(&["message",
                                        "dropped_local"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("skip non-droppy types")
                                            as &dyn Value)),
                                (&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&debug(&dropped_local)
                                            as &dyn Value))])
            });
    } else { ; }
};debug!(?dropped_local, "skip non-droppy types");
337                    to_exclude.insert(path_idx);
338                    continue;
339                }
340            }
341            // Suppose that all BIDs point into the same local,
342            // we can remove the this local from the observed drops,
343            // so that we can focus our diagnosis more on the others.
344            if let Ok(local) = candidates.iter().map(|&(_, place)| place.local).all_equal_value() {
345                for path_idx in all_locals_dropped.iter() {
346                    if move_data.move_paths[path_idx].place.local == local {
347                        to_exclude.insert(path_idx);
348                    }
349                }
350            }
351            all_locals_dropped.subtract(&to_exclude);
352        }
353        if all_locals_dropped.is_empty() {
354            // No drop effect is observable, so let us move on.
355            continue;
356        }
357
358        // ## The final work to assemble the diagnosis ##
359        // First collect or generate fresh names for local variable bindings and temporary values.
360        let local_names = assign_observables_names(
361            all_locals_dropped
362                .iter()
363                .map(|path_idx| move_data.move_paths[path_idx].place.local)
364                .chain(candidates.iter().map(|(_, place)| place.local)),
365            &locals_with_user_names,
366        );
367
368        let mut lint_root = None;
369        let mut local_labels = ::alloc::vec::Vec::new()vec![];
370        // We now collect the types with custom destructors.
371        for &(_, place) in candidates {
372            let linted_local_decl = &body.local_decls[place.local];
373            let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
374                ::rustc_middle::util::bug::bug_fmt(format_args!("a name should have been assigned"))bug!("a name should have been assigned")
375            };
376            let name = name.as_str();
377
378            if lint_root.is_none()
379                && let ClearCrossCrate::Set(data) =
380                    &body.source_scopes[linted_local_decl.source_info.scope].local_data
381            {
382                lint_root = Some(data.lint_root);
383            }
384
385            // Collect spans of the custom destructors.
386            let mut seen_dyn = false;
387            let destructors = ty_dropped_components
388                .get(&linted_local_decl.ty)
389                .unwrap()
390                .iter()
391                .filter_map(|&ty| {
392                    if let Some(span) = ty_dtor_span(tcx, ty) {
393                        Some(DestructorLabel { span, name, dtor_kind: "concrete" })
394                    } else if #[allow(non_exhaustive_omitted_patterns)] match ty.kind() {
    ty::Dynamic(..) => true,
    _ => false,
}matches!(ty.kind(), ty::Dynamic(..)) {
395                        if seen_dyn {
396                            None
397                        } else {
398                            seen_dyn = true;
399                            Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
400                        }
401                    } else {
402                        None
403                    }
404                })
405                .collect();
406            local_labels.push(LocalLabel {
407                span: linted_local_decl.source_info.span,
408                destructors,
409                name,
410                is_generated_name,
411                is_dropped_first_edition_2024: true,
412            });
413        }
414
415        // Similarly, custom destructors of the observed drops.
416        for path_idx in all_locals_dropped.iter() {
417            let place = &move_data.move_paths[path_idx].place;
418            // We are not using the type of the local because the drop may be partial.
419            let observer_ty = place.ty(body, tcx).ty;
420
421            let observer_local_decl = &body.local_decls[place.local];
422            let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
423                ::rustc_middle::util::bug::bug_fmt(format_args!("a name should have been assigned"))bug!("a name should have been assigned")
424            };
425            let name = name.as_str();
426
427            let mut seen_dyn = false;
428            let destructors = extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
429                .into_iter()
430                .filter_map(|ty| {
431                    if let Some(span) = ty_dtor_span(tcx, ty) {
432                        Some(DestructorLabel { span, name, dtor_kind: "concrete" })
433                    } else if #[allow(non_exhaustive_omitted_patterns)] match ty.kind() {
    ty::Dynamic(..) => true,
    _ => false,
}matches!(ty.kind(), ty::Dynamic(..)) {
434                        if seen_dyn {
435                            None
436                        } else {
437                            seen_dyn = true;
438                            Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
439                        }
440                    } else {
441                        None
442                    }
443                })
444                .collect();
445            local_labels.push(LocalLabel {
446                span: observer_local_decl.source_info.span,
447                destructors,
448                name,
449                is_generated_name,
450                is_dropped_first_edition_2024: false,
451            });
452        }
453
454        let span = local_labels[0].span;
455        tcx.emit_node_span_lint(
456            lint::builtin::TAIL_EXPR_DROP_ORDER,
457            lint_root.unwrap_or(CRATE_HIR_ID),
458            span,
459            TailExprDropOrderLint { local_labels, drop_span, _epilogue: () },
460        );
461    }
462}
463
464/// Extract binding names if available for diagnosis
465fn collect_user_names(body: &Body<'_>) -> FxIndexMap<Local, Symbol> {
466    let mut names = FxIndexMap::default();
467    for var_debug_info in &body.var_debug_info {
468        if let mir::VarDebugInfoContents::Place(place) = &var_debug_info.value
469            && let Some(local) = place.local_or_deref_local()
470        {
471            names.entry(local).or_insert(var_debug_info.name);
472        }
473    }
474    names
475}
476
477/// Assign names for anonymous or temporary values for diagnosis
478fn assign_observables_names(
479    locals: impl IntoIterator<Item = Local>,
480    user_names: &FxIndexMap<Local, Symbol>,
481) -> FxIndexMap<Local, (String, bool)> {
482    let mut names = FxIndexMap::default();
483    let mut assigned_names = FxHashSet::default();
484    let mut idx = 0u64;
485    let mut fresh_name = || {
486        idx += 1;
487        (::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#{0}", idx))
    })format!("#{idx}"), true)
488    };
489    for local in locals {
490        let name = if let Some(name) = user_names.get(&local) {
491            let name = name.as_str();
492            if assigned_names.contains(name) { fresh_name() } else { (name.to_owned(), false) }
493        } else {
494            fresh_name()
495        };
496        assigned_names.insert(name.0.clone());
497        names.insert(local, name);
498    }
499    names
500}
501
502#[derive(const _: () =
    {
        impl<'_sess, 'a, G> rustc_errors::Diagnostic<'_sess, G> for
            TailExprDropOrderLint<'a> where G: rustc_errors::EmissionGuarantee
            {
            #[track_caller]
            fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'_sess>,
                level: rustc_errors::Level) -> rustc_errors::Diag<'_sess, G> {
                match self {
                    TailExprDropOrderLint {
                        local_labels: __binding_0,
                        drop_span: __binding_1,
                        _epilogue: __binding_2 } => {
                        let mut diag =
                            rustc_errors::Diag::new(dcx, level,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("relative drop order changing in Rust 2024")));
                        ;
                        for __binding_0 in __binding_0 {
                            diag.subdiagnostic(__binding_0);
                        }
                        if let Some(__binding_1) = __binding_1 {
                            diag.span_label(__binding_1,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("now the temporary value is dropped here, before the local variables in the block or statement")));
                        }
                        diag.note(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages")));
                        diag
                    }
                }
            }
        }
    };Diagnostic)]
503#[diag("relative drop order changing in Rust 2024")]
504struct TailExprDropOrderLint<'a> {
505    #[subdiagnostic]
506    local_labels: Vec<LocalLabel<'a>>,
507    #[label(
508        "now the temporary value is dropped here, before the local variables in the block or statement"
509    )]
510    drop_span: Option<Span>,
511    #[note(
512        "most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages"
513    )]
514    _epilogue: (),
515}
516
517struct LocalLabel<'a> {
518    span: Span,
519    name: &'a str,
520    is_generated_name: bool,
521    is_dropped_first_edition_2024: bool,
522    destructors: Vec<DestructorLabel<'a>>,
523}
524
525/// A custom `Subdiagnostic` implementation so that the notes are delivered in a specific order
526impl Subdiagnostic for LocalLabel<'_> {
527    fn add_to_diag<G: rustc_errors::EmissionGuarantee>(self, diag: &mut rustc_errors::Diag<'_, G>) {
528        diag.span_label(
529            self.span,
530            rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{$is_generated_name ->\n                    [true] this value will be stored in a temporary; let us call it `{$name}`\n                    *[false] `{$name}` calls a custom destructor\n                }"))msg!(
531                "{$is_generated_name ->
532                    [true] this value will be stored in a temporary; let us call it `{$name}`
533                    *[false] `{$name}` calls a custom destructor
534                }"
535            )
536            .arg("name", self.name)
537            .arg("is_generated_name", self.is_generated_name)
538            .format(),
539        );
540        for dtor in self.destructors {
541            dtor.add_to_diag(diag);
542        }
543        diag.span_label(self.span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{$is_dropped_first_edition_2024 ->\n                [true] up until Edition 2021 `{$name}` is dropped last but will be dropped earlier in Edition 2024\n                *[false] `{$name}` will be dropped later as of Edition 2024\n            }"))msg!(
544            "{$is_dropped_first_edition_2024 ->
545                [true] up until Edition 2021 `{$name}` is dropped last but will be dropped earlier in Edition 2024
546                *[false] `{$name}` will be dropped later as of Edition 2024
547            }"
548        )
549            .arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024)
550            .arg("name", self.name)
551            .format());
552    }
553}
554
555#[derive(const _: () =
    {
        impl<'a> rustc_errors::Subdiagnostic for DestructorLabel<'a> {
            fn add_to_diag<__G>(self, diag: &mut rustc_errors::Diag<'_, __G>)
                where __G: rustc_errors::EmissionGuarantee {
                match self {
                    DestructorLabel {
                        span: __binding_0, dtor_kind: __binding_1, name: __binding_2
                        } => {
                        let mut sub_args = rustc_errors::DiagArgMap::default();
                        sub_args.insert("dtor_kind".into(),
                            rustc_errors::IntoDiagArg::into_diag_arg(__binding_1,
                                &mut diag.long_ty_path));
                        sub_args.insert("name".into(),
                            rustc_errors::IntoDiagArg::into_diag_arg(__binding_2,
                                &mut diag.long_ty_path));
                        let __message =
                            rustc_errors::format_diag_message(&rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{$dtor_kind ->\n        [dyn] `{$name}` may invoke a custom destructor because it contains a trait object\n        *[concrete] `{$name}` invokes this custom destructor\n    }")),
                                &sub_args);
                        diag.span_note(__binding_0, __message);
                    }
                }
            }
        }
    };Subdiagnostic)]
556#[note(
557    "{$dtor_kind ->
558        [dyn] `{$name}` may invoke a custom destructor because it contains a trait object
559        *[concrete] `{$name}` invokes this custom destructor
560    }"
561)]
562struct DestructorLabel<'a> {
563    #[primary_span]
564    span: Span,
565    dtor_kind: &'static str,
566    name: &'a str,
567}