Skip to content

Commit a348d62

Browse files
Print out destructor
1 parent 9af8985 commit a348d62

11 files changed

+410
-181
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3911,6 +3911,7 @@ dependencies = [
39113911
"rustc_target",
39123912
"rustc_trait_selection",
39133913
"rustc_type_ir",
3914+
"smallvec",
39143915
"tracing",
39153916
"unicode-security",
39163917
]

compiler/rustc_lint/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ rustc_span = { path = "../rustc_span" }
2424
rustc_target = { path = "../rustc_target" }
2525
rustc_trait_selection = { path = "../rustc_trait_selection" }
2626
rustc_type_ir = { path = "../rustc_type_ir" }
27+
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
2728
tracing = "0.1"
2829
unicode-security = "0.1.0"
2930
# tidy-alphabetical-end

compiler/rustc_lint/messages.ftl

+5
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,11 @@ lint_identifier_uncommon_codepoints = identifier contains {$codepoints_len ->
333333
*[other] {" "}{$identifier_type}
334334
} Unicode general security profile
335335
336+
lint_if_let_dtor = {$dtor_kind ->
337+
[dyn] value may invoke a custom destructor because it contains a trait object
338+
*[concrete] value invokes this custom destructor
339+
}
340+
336341
lint_if_let_rescope = `if let` assigns a shorter lifetime since Edition 2024
337342
.label = this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
338343
.help = the value is now dropped here in Edition 2024

compiler/rustc_lint/src/if_let_rescope.rs

+44-13
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ use rustc_errors::{
77
Applicability, Diag, EmissionGuarantee, SubdiagMessageOp, Subdiagnostic, SuggestionStyle,
88
};
99
use rustc_hir::{self as hir, HirIdSet};
10-
use rustc_macros::LintDiagnostic;
11-
use rustc_middle::ty::TyCtxt;
10+
use rustc_macros::{LintDiagnostic, Subdiagnostic};
11+
use rustc_middle::ty::significant_drop_order::{
12+
extract_component_with_significant_dtor, ty_dtor_span,
13+
};
14+
use rustc_middle::ty::{self, Ty, TyCtxt};
1215
use rustc_session::lint::{FutureIncompatibilityReason, LintId};
1316
use rustc_session::{declare_lint, impl_lint_pass};
14-
use rustc_span::Span;
1517
use rustc_span::edition::Edition;
18+
use rustc_span::{DUMMY_SP, Span};
19+
use smallvec::SmallVec;
1620

1721
use crate::{LateContext, LateLintPass};
1822

@@ -129,13 +133,15 @@ impl IfLetRescope {
129133
hir::ExprKind::If(_cond, _conseq, Some(alt)) => alt.span.shrink_to_hi(),
130134
_ => return,
131135
};
136+
let mut seen_dyn = false;
132137
let mut add_bracket_to_match_head = match_head_needs_bracket(tcx, expr);
133138
let mut significant_droppers = vec![];
134139
let mut lifetime_ends = vec![];
135140
let mut closing_brackets = 0;
136141
let mut alt_heads = vec![];
137142
let mut match_heads = vec![];
138143
let mut consequent_heads = vec![];
144+
let mut destructors = vec![];
139145
let mut first_if_to_lint = None;
140146
let mut first_if_to_rewrite = false;
141147
let mut empty_alt = false;
@@ -159,11 +165,25 @@ impl IfLetRescope {
159165
let before_conseq = conseq.span.shrink_to_lo();
160166
let lifetime_end = source_map.end_point(conseq.span);
161167

162-
if let ControlFlow::Break(significant_dropper) =
168+
if let ControlFlow::Break((drop_span, drop_tys)) =
163169
(FindSignificantDropper { cx }).visit_expr(init)
164170
{
171+
destructors.extend(drop_tys.into_iter().filter_map(|ty| {
172+
if let Some(span) = ty_dtor_span(tcx, ty) {
173+
Some(DestructorLabel { span, dtor_kind: "concrete" })
174+
} else if matches!(ty.kind(), ty::Dynamic(..)) {
175+
if seen_dyn {
176+
None
177+
} else {
178+
seen_dyn = true;
179+
Some(DestructorLabel { span: DUMMY_SP, dtor_kind: "dyn" })
180+
}
181+
} else {
182+
None
183+
}
184+
}));
165185
first_if_to_lint = first_if_to_lint.or_else(|| Some((span, expr.hir_id)));
166-
significant_droppers.push(significant_dropper);
186+
significant_droppers.push(drop_span);
167187
lifetime_ends.push(lifetime_end);
168188
if ty_ascription.is_some()
169189
|| !expr.span.can_be_used_for_suggestions()
@@ -226,6 +246,7 @@ impl IfLetRescope {
226246
hir_id,
227247
span,
228248
IfLetRescopeLint {
249+
destructors,
229250
significant_droppers,
230251
lifetime_ends,
231252
rewrite: first_if_to_rewrite.then_some(IfLetRescopeRewrite {
@@ -287,6 +308,8 @@ impl<'tcx> LateLintPass<'tcx> for IfLetRescope {
287308
#[derive(LintDiagnostic)]
288309
#[diag(lint_if_let_rescope)]
289310
struct IfLetRescopeLint {
311+
#[subdiagnostic]
312+
destructors: Vec<DestructorLabel>,
290313
#[label]
291314
significant_droppers: Vec<Span>,
292315
#[help]
@@ -346,6 +369,14 @@ impl Subdiagnostic for IfLetRescopeRewrite {
346369
}
347370
}
348371

372+
#[derive(Subdiagnostic)]
373+
#[note(lint_if_let_dtor)]
374+
struct DestructorLabel {
375+
#[primary_span]
376+
span: Span,
377+
dtor_kind: &'static str,
378+
}
379+
349380
struct AltHead(Span);
350381

351382
struct ConsequentRewrite {
@@ -368,16 +399,16 @@ struct FindSignificantDropper<'tcx, 'a> {
368399
}
369400

370401
impl<'tcx, 'a> Visitor<'tcx> for FindSignificantDropper<'tcx, 'a> {
371-
type Result = ControlFlow<Span>;
402+
type Result = ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)>;
372403

373404
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Self::Result {
374-
if self
375-
.cx
376-
.typeck_results()
377-
.expr_ty(expr)
378-
.has_significant_drop(self.cx.tcx, self.cx.typing_env())
379-
{
380-
return ControlFlow::Break(expr.span);
405+
let drop_tys = extract_component_with_significant_dtor(
406+
self.cx.tcx,
407+
self.cx.typing_env(),
408+
self.cx.typeck_results().expr_ty(expr),
409+
);
410+
if !drop_tys.is_empty() {
411+
return ControlFlow::Break((expr.span, drop_tys));
381412
}
382413
match expr.kind {
383414
hir::ExprKind::ConstBlock(_)

compiler/rustc_middle/src/ty/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ pub mod normalize_erasing_regions;
121121
pub mod pattern;
122122
pub mod print;
123123
pub mod relate;
124+
pub mod significant_drop_order;
124125
pub mod trait_def;
125126
pub mod util;
126127
pub mod visit;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use rustc_data_structures::fx::FxHashSet;
2+
use rustc_data_structures::unord::UnordSet;
3+
use rustc_hir::def_id::DefId;
4+
use rustc_span::Span;
5+
use smallvec::{SmallVec, smallvec};
6+
use tracing::{debug, instrument};
7+
8+
use crate::ty::{self, Ty, TyCtxt};
9+
10+
/// An additional filter to exclude well-known types from the ecosystem
11+
/// because their drops are trivial.
12+
/// This returns additional types to check if the drops are delegated to those.
13+
/// A typical example is `hashbrown::HashMap<K, V>`, whose drop is delegated to `K` and `V`.
14+
fn true_significant_drop_ty<'tcx>(
15+
tcx: TyCtxt<'tcx>,
16+
ty: Ty<'tcx>,
17+
) -> Option<SmallVec<[Ty<'tcx>; 2]>> {
18+
if let ty::Adt(def, args) = ty.kind() {
19+
let mut did = def.did();
20+
let mut name_rev = vec![];
21+
loop {
22+
let key = tcx.def_key(did);
23+
24+
match key.disambiguated_data.data {
25+
rustc_hir::definitions::DefPathData::CrateRoot => {
26+
name_rev.push(tcx.crate_name(did.krate))
27+
}
28+
rustc_hir::definitions::DefPathData::TypeNs(symbol) => name_rev.push(symbol),
29+
_ => return None,
30+
}
31+
if let Some(parent) = key.parent {
32+
did = DefId { krate: did.krate, index: parent };
33+
} else {
34+
break;
35+
}
36+
}
37+
let name_str: Vec<_> = name_rev.iter().rev().map(|x| x.as_str()).collect();
38+
debug!(?name_str);
39+
match name_str[..] {
40+
// These are the types from Rust core ecosystem
41+
["syn" | "proc_macro2", ..]
42+
| ["core" | "std", "task", "LocalWaker" | "Waker"]
43+
| ["core" | "std", "task", "wake", "LocalWaker" | "Waker"] => Some(smallvec![]),
44+
// These are important types from Rust ecosystem
45+
["tracing", "instrument", "Instrumented"] | ["bytes", "Bytes"] => Some(smallvec![]),
46+
["hashbrown", "raw", "RawTable" | "RawIntoIter"] => {
47+
if let [ty, ..] = &***args
48+
&& let Some(ty) = ty.as_type()
49+
{
50+
Some(smallvec![ty])
51+
} else {
52+
None
53+
}
54+
}
55+
["hashbrown", "raw", "RawDrain"] => {
56+
if let [_, ty, ..] = &***args
57+
&& let Some(ty) = ty.as_type()
58+
{
59+
Some(smallvec![ty])
60+
} else {
61+
None
62+
}
63+
}
64+
_ => None,
65+
}
66+
} else {
67+
None
68+
}
69+
}
70+
71+
/// Returns the list of types with a "potentially sigificant" that may be dropped
72+
/// by dropping a value of type `ty`.
73+
#[instrument(level = "trace", skip(tcx, typing_env))]
74+
pub fn extract_component_raw<'tcx>(
75+
tcx: TyCtxt<'tcx>,
76+
typing_env: ty::TypingEnv<'tcx>,
77+
ty: Ty<'tcx>,
78+
ty_seen: &mut UnordSet<Ty<'tcx>>,
79+
) -> SmallVec<[Ty<'tcx>; 4]> {
80+
// Droppiness does not depend on regions, so let us erase them.
81+
let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
82+
83+
let tys = tcx.list_significant_drop_tys(typing_env.as_query_input(ty));
84+
debug!(?ty, "components");
85+
let mut out_tys = smallvec![];
86+
for ty in tys {
87+
if let Some(tys) = true_significant_drop_ty(tcx, ty) {
88+
// Some types can be further opened up because the drop is simply delegated
89+
for ty in tys {
90+
if ty_seen.insert(ty) {
91+
out_tys.extend(extract_component_raw(tcx, typing_env, ty, ty_seen));
92+
}
93+
}
94+
} else {
95+
if ty_seen.insert(ty) {
96+
out_tys.push(ty);
97+
}
98+
}
99+
}
100+
out_tys
101+
}
102+
103+
#[instrument(level = "trace", skip(tcx, typing_env))]
104+
pub fn extract_component_with_significant_dtor<'tcx>(
105+
tcx: TyCtxt<'tcx>,
106+
typing_env: ty::TypingEnv<'tcx>,
107+
ty: Ty<'tcx>,
108+
) -> SmallVec<[Ty<'tcx>; 4]> {
109+
let mut tys = extract_component_raw(tcx, typing_env, ty, &mut Default::default());
110+
let mut deduplicate = FxHashSet::default();
111+
tys.retain(|oty| deduplicate.insert(*oty));
112+
tys.into_iter().collect()
113+
}
114+
115+
/// Extract the span of the custom destructor of a type
116+
/// especially the span of the `impl Drop` header or its entire block
117+
/// when we are working with current local crate.
118+
#[instrument(level = "trace", skip(tcx))]
119+
pub fn ty_dtor_span<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Span> {
120+
match ty.kind() {
121+
ty::Bool
122+
| ty::Char
123+
| ty::Int(_)
124+
| ty::Uint(_)
125+
| ty::Float(_)
126+
| ty::Error(_)
127+
| ty::Str
128+
| ty::Never
129+
| ty::RawPtr(_, _)
130+
| ty::Ref(_, _, _)
131+
| ty::FnPtr(_, _)
132+
| ty::Tuple(_)
133+
| ty::Dynamic(_, _, _)
134+
| ty::Alias(_, _)
135+
| ty::Bound(_, _)
136+
| ty::Pat(_, _)
137+
| ty::Placeholder(_)
138+
| ty::Infer(_)
139+
| ty::Slice(_)
140+
| ty::Array(_, _)
141+
| ty::UnsafeBinder(_) => None,
142+
143+
ty::Adt(adt_def, _) => {
144+
let did = adt_def.did();
145+
let try_local_did_span = |did: DefId| {
146+
if let Some(local) = did.as_local() {
147+
tcx.source_span(local)
148+
} else {
149+
tcx.def_span(did)
150+
}
151+
};
152+
let dtor = if let Some(dtor) = tcx.adt_destructor(did) {
153+
dtor.did
154+
} else if let Some(dtor) = tcx.adt_async_destructor(did) {
155+
dtor.future
156+
} else {
157+
return Some(try_local_did_span(did));
158+
};
159+
let def_key = tcx.def_key(dtor);
160+
let Some(parent_index) = def_key.parent else { return Some(try_local_did_span(dtor)) };
161+
let parent_did = DefId { index: parent_index, krate: dtor.krate };
162+
Some(try_local_did_span(parent_did))
163+
}
164+
ty::Coroutine(did, _)
165+
| ty::CoroutineWitness(did, _)
166+
| ty::CoroutineClosure(did, _)
167+
| ty::Closure(did, _)
168+
| ty::FnDef(did, _)
169+
| ty::Foreign(did) => Some(tcx.def_span(did)),
170+
ty::Param(_) => None,
171+
}
172+
}

0 commit comments

Comments
 (0)