Skip to content

Commit 25db95e

Browse files
authored
Rollup merge of #137455 - compiler-errors:drop-lint-dtor, r=oli-obk
Reuse machinery from `tail_expr_drop_order` for `if_let_rescope` Namely, it defines its own `extract_component_with_significant_dtor` which is a bit more accurate than `Ty::has_significant_drop`, since it has a hard-coded list of types from the ecosystem which are opted out of the lint.[^a] Also, since we extract the dtors themselves, adopt the same *label* we use in `tail_expr_drop_order` to point out the destructor impl. This makes it much clear what's actually being dropped, so it should be clearer to know when it's a false positive. This conflicts with #137444, but I will rebase whichever lands first. [^a]: Side-note, it's kinda a shame that now there are two functions that presumably do the same thing. But this isn't my circus, nor are these my monkeys.
2 parents 3499846 + 864cca8 commit 25db95e

11 files changed

+425
-186
lines changed

Diff for: Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3916,6 +3916,7 @@ dependencies = [
39163916
"rustc_target",
39173917
"rustc_trait_selection",
39183918
"rustc_type_ir",
3919+
"smallvec",
39193920
"tracing",
39203921
"unicode-security",
39213922
]

Diff for: 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

Diff for: 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

Diff for: compiler/rustc_lint/src/if_let_rescope.rs

+59-18
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ 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};
1211
use rustc_middle::ty::adjustment::Adjust;
12+
use rustc_middle::ty::significant_drop_order::{
13+
extract_component_with_significant_dtor, ty_dtor_span,
14+
};
15+
use rustc_middle::ty::{self, Ty, TyCtxt};
1316
use rustc_session::lint::{FutureIncompatibilityReason, LintId};
1417
use rustc_session::{declare_lint, impl_lint_pass};
15-
use rustc_span::Span;
1618
use rustc_span::edition::Edition;
19+
use rustc_span::{DUMMY_SP, Span};
20+
use smallvec::SmallVec;
1721

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

@@ -130,13 +134,15 @@ impl IfLetRescope {
130134
hir::ExprKind::If(_cond, _conseq, Some(alt)) => alt.span.shrink_to_hi(),
131135
_ => return,
132136
};
137+
let mut seen_dyn = false;
133138
let mut add_bracket_to_match_head = match_head_needs_bracket(tcx, expr);
134139
let mut significant_droppers = vec![];
135140
let mut lifetime_ends = vec![];
136141
let mut closing_brackets = 0;
137142
let mut alt_heads = vec![];
138143
let mut match_heads = vec![];
139144
let mut consequent_heads = vec![];
145+
let mut destructors = vec![];
140146
let mut first_if_to_lint = None;
141147
let mut first_if_to_rewrite = false;
142148
let mut empty_alt = false;
@@ -160,11 +166,25 @@ impl IfLetRescope {
160166
let before_conseq = conseq.span.shrink_to_lo();
161167
let lifetime_end = source_map.end_point(conseq.span);
162168

163-
if let ControlFlow::Break(significant_dropper) =
169+
if let ControlFlow::Break((drop_span, drop_tys)) =
164170
(FindSignificantDropper { cx }).check_if_let_scrutinee(init)
165171
{
172+
destructors.extend(drop_tys.into_iter().filter_map(|ty| {
173+
if let Some(span) = ty_dtor_span(tcx, ty) {
174+
Some(DestructorLabel { span, dtor_kind: "concrete" })
175+
} else if matches!(ty.kind(), ty::Dynamic(..)) {
176+
if seen_dyn {
177+
None
178+
} else {
179+
seen_dyn = true;
180+
Some(DestructorLabel { span: DUMMY_SP, dtor_kind: "dyn" })
181+
}
182+
} else {
183+
None
184+
}
185+
}));
166186
first_if_to_lint = first_if_to_lint.or_else(|| Some((span, expr.hir_id)));
167-
significant_droppers.push(significant_dropper);
187+
significant_droppers.push(drop_span);
168188
lifetime_ends.push(lifetime_end);
169189
if ty_ascription.is_some()
170190
|| !expr.span.can_be_used_for_suggestions()
@@ -227,6 +247,7 @@ impl IfLetRescope {
227247
hir_id,
228248
span,
229249
IfLetRescopeLint {
250+
destructors,
230251
significant_droppers,
231252
lifetime_ends,
232253
rewrite: first_if_to_rewrite.then_some(IfLetRescopeRewrite {
@@ -288,6 +309,8 @@ impl<'tcx> LateLintPass<'tcx> for IfLetRescope {
288309
#[derive(LintDiagnostic)]
289310
#[diag(lint_if_let_rescope)]
290311
struct IfLetRescopeLint {
312+
#[subdiagnostic]
313+
destructors: Vec<DestructorLabel>,
291314
#[label]
292315
significant_droppers: Vec<Span>,
293316
#[help]
@@ -347,6 +370,14 @@ impl Subdiagnostic for IfLetRescopeRewrite {
347370
}
348371
}
349372

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

352383
struct ConsequentRewrite {
@@ -374,7 +405,10 @@ impl<'tcx> FindSignificantDropper<'_, 'tcx> {
374405
/// of the scrutinee itself, and also recurses into the expression to find any ref
375406
/// exprs (or autoref) which would promote temporaries that would be scoped to the
376407
/// end of this `if`.
377-
fn check_if_let_scrutinee(&mut self, init: &'tcx hir::Expr<'tcx>) -> ControlFlow<Span> {
408+
fn check_if_let_scrutinee(
409+
&mut self,
410+
init: &'tcx hir::Expr<'tcx>,
411+
) -> ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)> {
378412
self.check_promoted_temp_with_drop(init)?;
379413
self.visit_expr(init)
380414
}
@@ -385,28 +419,35 @@ impl<'tcx> FindSignificantDropper<'_, 'tcx> {
385419
/// An expression is a promoted temporary if it has an addr taken (i.e. `&expr` or autoref)
386420
/// or is the scrutinee of the `if let`, *and* the expression is not a place
387421
/// expr, and it has a significant drop.
388-
fn check_promoted_temp_with_drop(&self, expr: &'tcx hir::Expr<'tcx>) -> ControlFlow<Span> {
389-
if !expr.is_place_expr(|base| {
422+
fn check_promoted_temp_with_drop(
423+
&self,
424+
expr: &'tcx hir::Expr<'tcx>,
425+
) -> ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)> {
426+
if expr.is_place_expr(|base| {
390427
self.cx
391428
.typeck_results()
392429
.adjustments()
393430
.get(base.hir_id)
394431
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
395-
}) && self
396-
.cx
397-
.typeck_results()
398-
.expr_ty(expr)
399-
.has_significant_drop(self.cx.tcx, self.cx.typing_env())
400-
{
401-
ControlFlow::Break(expr.span)
402-
} else {
403-
ControlFlow::Continue(())
432+
}) {
433+
return ControlFlow::Continue(());
404434
}
435+
436+
let drop_tys = extract_component_with_significant_dtor(
437+
self.cx.tcx,
438+
self.cx.typing_env(),
439+
self.cx.typeck_results().expr_ty(expr),
440+
);
441+
if drop_tys.is_empty() {
442+
return ControlFlow::Continue(());
443+
}
444+
445+
ControlFlow::Break((expr.span, drop_tys))
405446
}
406447
}
407448

408449
impl<'tcx> Visitor<'tcx> for FindSignificantDropper<'_, 'tcx> {
409-
type Result = ControlFlow<Span>;
450+
type Result = ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)>;
410451

411452
fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) -> Self::Result {
412453
// Blocks introduce temporary terminating scope for all of its

Diff for: compiler/rustc_middle/src/ty/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ pub mod normalize_erasing_regions;
122122
pub mod pattern;
123123
pub mod print;
124124
pub mod relate;
125+
pub mod significant_drop_order;
125126
pub mod trait_def;
126127
pub mod util;
127128
pub mod visit;
+172
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)