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