1use std::cell::RefCell;
2use std::collections::hash_map;
3use std::rc::Rc;
45use itertools::Itertoolsas _;
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::{
17self, BasicBlock, Body, ClearCrossCrate, Local, Location, MirDumper, Place, StatementKind,
18TerminatorKind,
19};
20use rustc_middle::ty::significant_drop_order::{
21extract_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;
3132fn place_has_common_prefix<'tcx>(left: &Place<'tcx>, right: &Place<'tcx>) -> bool {
33left.local == right.local
34 && left.projection.iter().zip(right.projection).all(|(left, right)| left == right)
35}
3637/// 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
41Unknown,
42/// We know that the `drop` here has no effect
43None,
44/// We know that the `drop` here will invoke a destructor
45Some(MovePathIndex),
46}
4748struct 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}
5859impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
60fn visit(&mut self, block: BasicBlock) {
61let move_set_size = self.move_data.move_paths.len();
62let make_new_path_set = || Rc::new(RefCell::new(MixedBitSet::new_empty(move_set_size)));
6364let data = &self.body.basic_blocks[block];
65let 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.
69let dropped_local_here =
70Rc::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`.
73match self.block_drop_value_info[block] {
74 MovePathIndexAtBlock::Some(dropped) => {
75dropped_local_here.borrow_mut().insert(dropped);
76 }
77 MovePathIndexAtBlock::Unknown => {
78if let TerminatorKind::Drop { place, .. } = &terminator.kind
79 && let LookupResult::Exact(idx) | LookupResult::Parent(Some(idx)) =
80self.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.
86self.maybe_init.seek_before_primary_effect(Location {
87block,
88 statement_index: data.statements.len(),
89 });
9091// 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.
95if 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.
100self.block_drop_value_info[block] = MovePathIndexAtBlock::Some(idx);
101dropped_local_here.borrow_mut().insert(idx);
102 } else {
103self.block_drop_value_info[block] = MovePathIndexAtBlock::None;
104 }
105 }
106 }
107 MovePathIndexAtBlock::None => {}
108 }
109110for succ in terminator.successors() {
111let target = &self.body.basic_blocks[succ];
112if target.is_cleanup {
113continue;
114 }
115116// As long as we are passing through a new block, or new dropped places to propagate,
117 // we will proceed with `succ`
118let dropped_local_there = match self.visited.entry(succ) {
119 hash_map::Entry::Occupied(occupied_entry) => {
120if 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
125continue;
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 };
133if 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.
146self.collected_drops.union(&*dropped_local_there.borrow());
147if 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 {
157self.visit(succ)
158 }
159 }
160 }
161}
162163/// 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>(
167mut idx: MovePathIndex,
168 move_data: &MoveData<'tcx>,
169 bids: &UnordSet<&Place<'tcx>>,
170) -> bool {
171loop {
172let path = &move_data.move_paths[idx];
173if bids.contains(&path.place) {
174return true;
175 }
176if let Some(parent) = path.parent {
177idx = parent;
178 } else {
179return false;
180 }
181 }
182}
183184/// The core of the lint `tail-expr-drop-order`
185pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<'tcx>) {
186if #[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
188return;
189 }
190if body.span.edition().at_least_rust_2024()
191 || tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
192 {
193return;
194 }
195196// FIXME(typing_env): This should be able to reveal the opaques local to the
197 // body using the typeck results.
198let typing_env = ty::TypingEnv::non_body_analysis(tcx, def_id);
199200// ## 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.
205let mut bid_per_block = FxIndexMap::default();
206let mut bid_places = UnordSet::new();
207208let mut ty_dropped_components = UnordMap::default();
209for (block, data) in body.basic_blocks.iter_enumerated() {
210for (statement_index, stmt) in data.statements.iter().enumerate() {
211if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } = &stmt.kind {
212let ty = place.ty(body, tcx).ty;
213if ty_dropped_components
214 .entry(ty)
215 .or_insert_with(|| extract_component_with_significant_dtor(tcx, typing_env, ty))
216 .is_empty()
217 {
218continue;
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 }
228if bid_per_block.is_empty() {
229return;
230 }
231232if let Some(dumper) = MirDumper::new(tcx, "lint_tail_expr_drop_order", body) {
233dumper.dump_mir(body);
234 }
235236let locals_with_user_names = collect_user_names(body);
237let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
238239// 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.
242let move_data = MoveData::gather_moves(body, tcx, |_| true);
243let mut maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data)
244 .iterate_to_fixpoint(tcx, body, None)
245 .into_results_cursor(body);
246let mut block_drop_value_info =
247IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
248for (&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`.
251let mut all_locals_dropped = MixedBitSet::new_empty(move_data.move_paths.len());
252let mut drop_span = None;
253for &(_, place) in candidates.iter() {
254let 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.
261DropsReachable {
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`.
277all_locals_dropped.union(&collected_drops);
278 }
279280// We shall now exclude some local bindings for the following cases.
281{
282let 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.
285for path_idx in all_locals_dropped.iter() {
286let move_path = &move_data.move_paths[path_idx];
287let 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
295if 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);
298continue;
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.
303if 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);
306continue;
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.
322if 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);
325continue;
326 }
327let observer_ty = move_path.place.ty(body, tcx).ty;
328// d) The collected local has no custom destructor that passes our ecosystem filter.
329if 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);
338continue;
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.
344if let Ok(local) = candidates.iter().map(|&(_, place)| place.local).all_equal_value() {
345for path_idx in all_locals_dropped.iter() {
346if 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 }
353if all_locals_dropped.is_empty() {
354// No drop effect is observable, so let us move on.
355continue;
356 }
357358// ## The final work to assemble the diagnosis ##
359 // First collect or generate fresh names for local variable bindings and temporary values.
360let 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 );
367368let mut lint_root = None;
369let mut local_labels = ::alloc::vec::Vec::new()vec![];
370// We now collect the types with custom destructors.
371for &(_, place) in candidates {
372let linted_local_decl = &body.local_decls[place.local];
373let 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 };
376let name = name.as_str();
377378if 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 }
384385// Collect spans of the custom destructors.
386let mut seen_dyn = false;
387let destructors = ty_dropped_components
388 .get(&linted_local_decl.ty)
389 .unwrap()
390 .iter()
391 .filter_map(|&ty| {
392if let Some(span) = ty_dtor_span(tcx, ty) {
393Some(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(..)) {
395if seen_dyn {
396None
397} else {
398 seen_dyn = true;
399Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
400 }
401 } else {
402None
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 }
414415// Similarly, custom destructors of the observed drops.
416for path_idx in all_locals_dropped.iter() {
417let place = &move_data.move_paths[path_idx].place;
418// We are not using the type of the local because the drop may be partial.
419let observer_ty = place.ty(body, tcx).ty;
420421let observer_local_decl = &body.local_decls[place.local];
422let 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 };
425let name = name.as_str();
426427let mut seen_dyn = false;
428let destructors = extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
429 .into_iter()
430 .filter_map(|ty| {
431if let Some(span) = ty_dtor_span(tcx, ty) {
432Some(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(..)) {
434if seen_dyn {
435None
436} else {
437 seen_dyn = true;
438Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
439 }
440 } else {
441None
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 }
453454let 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}
463464/// Extract binding names if available for diagnosis
465fn collect_user_names(body: &Body<'_>) -> FxIndexMap<Local, Symbol> {
466let mut names = FxIndexMap::default();
467for var_debug_info in &body.var_debug_info {
468if 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 }
474names475}
476477/// 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)> {
482let mut names = FxIndexMap::default();
483let mut assigned_names = FxHashSet::default();
484let mut idx = 0u64;
485let mut fresh_name = || {
486idx += 1;
487 (::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}", idx))
})format!("#{idx}"), true)
488 };
489for local in locals {
490let name = if let Some(name) = user_names.get(&local) {
491let name = name.as_str();
492if 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 }
499names500}
501502#[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]
506local_labels: Vec<LocalLabel<'a>>,
507#[label(
508"now the temporary value is dropped here, before the local variables in the block or statement"
509)]
510drop_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}
516517struct 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}
524525/// A custom `Subdiagnostic` implementation so that the notes are delivered in a specific order
526impl Subdiagnosticfor LocalLabel<'_> {
527fn add_to_diag<G: rustc_errors::EmissionGuarantee>(self, diag: &mut rustc_errors::Diag<'_, G>) {
528diag.span_label(
529self.span,
530rustc_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 );
540for dtor in self.destructors {
541 dtor.add_to_diag(diag);
542 }
543diag.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}
554555#[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]
564span: Span,
565 dtor_kind: &'static str,
566 name: &'a str,
567}