Skip to content

Commit 1497185

Browse files
committed
Implement Drop::pin_drop for !Unpin types
1 parent 283a074 commit 1497185

File tree

10 files changed

+902
-10
lines changed

10 files changed

+902
-10
lines changed

compiler/rustc_hir_analysis/messages.ftl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ hir_analysis_coercion_between_struct_same_note = expected coercion between the s
117117
118118
hir_analysis_coercion_between_struct_single_note = expected a single field to be coerced, none found
119119
120+
hir_analysis_conflict_impl_drop_and_pin_drop = conflict implementation of `Drop::drop` and `Drop::pin_drop`
121+
.drop_label = `drop(&mut self)` implemented here
122+
.pin_drop_label = `pin_drop(&pin mut self)` implemented here
123+
.suggestion = remove this implementation
124+
120125
hir_analysis_const_bound_for_non_const_trait = `{$modifier}` can only be applied to `const` traits
121126
.label = can't be applied to `{$trait_name}`
122127
.note = `{$trait_name}` can't be used with `{$modifier}` because it isn't `const`
@@ -215,6 +220,11 @@ hir_analysis_functions_names_duplicated = functions names are duplicated
215220
216221
hir_analysis_generic_args_on_overridden_impl = could not resolve generic parameters on overridden impl
217222
223+
hir_analysis_impl_drop_for_negative_unpin_type = could not impl `Drop::drop(&mut self)` for a type that implements `!Unpin`
224+
.note = `Unpin` implemented for `{$self_ty}`
225+
.help = `impl !Unpin` is intended for pinned projections, which requires the value pinned until it's deallocated
226+
.suggestion = consider implement `Drop::pin_drop(&pin mut self)` instead
227+
218228
hir_analysis_impl_not_marked_default = `{$ident}` specializes an item from a parent `impl`, but that item is not marked `default`
219229
.label = cannot specialize default item `{$ident}`
220230
.ok_label = parent `impl` is here
@@ -223,6 +233,10 @@ hir_analysis_impl_not_marked_default = `{$ident}` specializes an item from a par
223233
hir_analysis_impl_not_marked_default_err = `{$ident}` specializes an item from a parent `impl`, but that item is not marked `default`
224234
.note = parent implementation is in crate `{$cname}`
225235
236+
hir_analysis_impl_pin_drop_for_not_negative_unpin_type = implementing `Drop::pin_drop(&pin mut self)` requires `Self: !Unpin`
237+
.suggestion = impl `Drop::drop(&mut self)` instead
238+
.impl_negative_unpin_sugg = or impl `!Unpin` for `{$self_ty}` instead
239+
226240
hir_analysis_inherent_dyn = cannot define inherent `impl` for a dyn auto trait
227241
.label = impl requires at least one non-auto trait
228242
.note = define and implement a new trait or type instead

compiler/rustc_hir_analysis/src/check/always_applicable.rs

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
use rustc_data_structures::fx::FxHashSet;
88
use rustc_errors::codes::*;
99
use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
10+
use rustc_hir as hir;
1011
use rustc_infer::infer::{RegionResolutionError, TyCtxtInferExt};
1112
use rustc_infer::traits::{ObligationCause, ObligationCauseCode};
1213
use rustc_middle::span_bug;
1314
use rustc_middle::ty::util::CheckRegions;
1415
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypingMode};
16+
use rustc_span::{Span, sym};
1517
use rustc_trait_selection::regions::InferCtxtRegionExt;
1618
use rustc_trait_selection::traits::{self, ObligationCtxt};
1719

@@ -70,7 +72,9 @@ pub(crate) fn check_drop_impl(
7072
drop_impl_did,
7173
adt_def.did(),
7274
adt_to_impl_args,
73-
)
75+
)?;
76+
77+
ensure_coherent_unpin(tcx, drop_impl_did, adt_def.did(), adt_to_impl_args)
7478
}
7579
_ => {
7680
span_bug!(tcx.def_span(drop_impl_did), "incoherent impl of Drop");
@@ -294,3 +298,102 @@ fn ensure_impl_predicates_are_implied_by_item_defn<'tcx>(
294298

295299
Ok(())
296300
}
301+
302+
/// This function checks whether `Self: !Unpin` for implementation of `Drop`.
303+
/// - If `Drop::drop` is implemented, there must be no `Self: !Unpin` implementation.
304+
/// - If `Drop::pin_drop` is implemented, there must be a `Self: !Unpin` implementation.
305+
fn ensure_coherent_unpin<'tcx>(
306+
tcx: TyCtxt<'tcx>,
307+
drop_impl_did: LocalDefId,
308+
adt_def_id: DefId,
309+
adt_to_impl_args: GenericArgsRef<'tcx>,
310+
) -> Result<(), ErrorGuaranteed> {
311+
let item_impl = tcx.hir_expect_item(drop_impl_did).expect_impl();
312+
let mut drop_spans = None;
313+
let mut pin_drop_spans = None;
314+
for &impl_item_id in item_impl.items {
315+
let impl_item = tcx.hir_impl_item(impl_item_id);
316+
if let hir::ImplItemKind::Fn(fn_sig, _) = impl_item.kind {
317+
match impl_item.ident.name {
318+
sym::drop => drop_spans = Some((fn_sig.span, impl_item.span)),
319+
sym::pin_drop => pin_drop_spans = Some((fn_sig.span, impl_item.span)),
320+
_ => {}
321+
}
322+
}
323+
}
324+
let self_ty = Ty::new_adt(tcx, tcx.adt_def(adt_def_id), adt_to_impl_args);
325+
let negative_unpin_impl = find_negative_unpin_impl(tcx, self_ty, tcx.def_span(drop_impl_did));
326+
327+
match (drop_spans, pin_drop_spans) {
328+
(None, None) => {
329+
return Err(tcx
330+
.dcx()
331+
.span_delayed_bug(tcx.def_span(drop_impl_did), "unexpected empty impl of `Drop`"));
332+
}
333+
(Some((span, _)), None) => {
334+
if let Some(negative_unpin_impl) = negative_unpin_impl {
335+
return Err(tcx.dcx().emit_err(crate::errors::ImplDropForNegativeUnpinType {
336+
span,
337+
negative_unpin_span: tcx.def_span(negative_unpin_impl),
338+
self_ty,
339+
}));
340+
}
341+
}
342+
(None, Some((span, _))) => {
343+
debug_assert!(
344+
tcx.features().pin_ergonomics(),
345+
"`Drop::pin_drop` should be guarded by the library feature gate"
346+
);
347+
if negative_unpin_impl.is_none() {
348+
return Err(tcx.dcx().emit_err(
349+
crate::errors::ImplPinDropForNotNegativeUnpinType {
350+
span,
351+
impl_sugg_span: tcx.def_span(drop_impl_did).shrink_to_lo(),
352+
impl_generics_snippet: tcx
353+
.sess
354+
.source_map()
355+
.span_to_snippet(item_impl.generics.span)
356+
.unwrap_or_default(),
357+
self_ty,
358+
},
359+
));
360+
}
361+
}
362+
(
363+
Some((drop_span, drop_span_with_body)),
364+
Some((pin_drop_span, pin_drop_span_with_body)),
365+
) => {
366+
let remove_sugg = match negative_unpin_impl {
367+
// without `impl !Unpin`, should pick `drop(&mut self)`, and remove `pin_drop(&pin mut self)`
368+
None => pin_drop_span_with_body,
369+
// with `impl !Unpin`, should pick `pin_drop(&pin mut self)`, and remove `drop(&mut self)`
370+
Some(_) => drop_span_with_body,
371+
};
372+
return Err(tcx.dcx().emit_err(crate::errors::ConflictImplDropAndPinDrop {
373+
span: tcx.def_span(drop_impl_did),
374+
drop_span,
375+
pin_drop_span,
376+
remove_sugg,
377+
}));
378+
}
379+
}
380+
Ok(())
381+
}
382+
383+
fn find_negative_unpin_impl<'tcx>(
384+
tcx: TyCtxt<'tcx>,
385+
self_ty: Ty<'tcx>,
386+
span: Span,
387+
) -> Option<LocalDefId> {
388+
let mut negative_unpin_impl_did = None;
389+
tcx.for_each_relevant_impl(tcx.require_lang_item(hir::LangItem::Unpin, span), self_ty, |did| {
390+
if tcx.impl_polarity(did) == ty::ImplPolarity::Negative {
391+
if negative_unpin_impl_did.is_some() {
392+
tcx.dcx()
393+
.span_delayed_bug(tcx.def_span(did), "duplicated `!Unpin` implementations");
394+
}
395+
negative_unpin_impl_did = Some(did.expect_local());
396+
}
397+
});
398+
negative_unpin_impl_did
399+
}

compiler/rustc_hir_analysis/src/errors.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,3 +1717,53 @@ pub(crate) struct AsyncDropWithoutSyncDrop {
17171717
#[primary_span]
17181718
pub span: Span,
17191719
}
1720+
1721+
#[derive(Diagnostic)]
1722+
#[diag(hir_analysis_impl_drop_for_negative_unpin_type)]
1723+
#[help]
1724+
pub(crate) struct ImplDropForNegativeUnpinType<'tcx> {
1725+
#[suggestion(
1726+
applicability = "machine-applicable",
1727+
code = "fn pin_drop(&pin mut self)",
1728+
style = "short"
1729+
)]
1730+
#[primary_span]
1731+
pub span: Span,
1732+
#[note]
1733+
pub negative_unpin_span: Span,
1734+
pub self_ty: Ty<'tcx>,
1735+
}
1736+
1737+
#[derive(Diagnostic)]
1738+
#[diag(hir_analysis_impl_pin_drop_for_not_negative_unpin_type)]
1739+
pub(crate) struct ImplPinDropForNotNegativeUnpinType<'tcx> {
1740+
#[suggestion(
1741+
applicability = "machine-applicable",
1742+
code = "fn drop(&mut self)",
1743+
style = "short"
1744+
)]
1745+
#[primary_span]
1746+
pub span: Span,
1747+
#[suggestion(
1748+
hir_analysis_impl_negative_unpin_sugg,
1749+
applicability = "machine-applicable",
1750+
code = "impl{impl_generics_snippet} !Unpin for {self_ty}` {}\n",
1751+
style = "short"
1752+
)]
1753+
pub impl_sugg_span: Span,
1754+
pub impl_generics_snippet: String,
1755+
pub self_ty: Ty<'tcx>,
1756+
}
1757+
1758+
#[derive(Diagnostic)]
1759+
#[diag(hir_analysis_conflict_impl_drop_and_pin_drop)]
1760+
pub(crate) struct ConflictImplDropAndPinDrop {
1761+
#[primary_span]
1762+
pub span: Span,
1763+
#[label(hir_analysis_drop_label)]
1764+
pub drop_span: Span,
1765+
#[label(hir_analysis_pin_drop_label)]
1766+
pub pin_drop_span: Span,
1767+
#[suggestion(applicability = "machine-applicable", code = "", style = "normal")]
1768+
pub remove_sugg: Span,
1769+
}

compiler/rustc_hir_typeck/src/callee.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ pub(crate) fn check_legal_trait_for_method_call(
3636
receiver: Option<Span>,
3737
expr_span: Span,
3838
trait_id: DefId,
39-
_body_id: DefId,
39+
body_id: DefId,
4040
) -> Result<(), ErrorGuaranteed> {
41-
if tcx.is_lang_item(trait_id, LangItem::Drop) {
41+
if tcx.is_lang_item(trait_id, LangItem::Drop)
42+
// Allow calling `Drop::pin_drop` in `Drop::drop`
43+
&& let Some(parent) = tcx.opt_parent(body_id)
44+
&& !tcx.is_lang_item(parent, LangItem::Drop)
45+
{
4246
let sugg = if let Some(receiver) = receiver.filter(|s| !s.is_empty()) {
4347
errors::ExplicitDestructorCallSugg::Snippet {
4448
lo: expr_span.shrink_to_lo(),

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,6 +1616,7 @@ symbols! {
16161616
pic,
16171617
pie,
16181618
pin,
1619+
pin_drop,
16191620
pin_ergonomics,
16201621
pin_macro,
16211622
platform_intrinsics,

library/core/src/ops/drop.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::pin::Pin;
2+
13
/// Custom code within the destructor.
24
///
35
/// When a value is no longer needed, Rust will run a "destructor" on that value.
@@ -205,6 +207,7 @@
205207
#[stable(feature = "rust1", since = "1.0.0")]
206208
#[const_trait]
207209
#[rustc_const_unstable(feature = "const_destruct", issue = "133214")]
210+
#[rustc_must_implement_one_of(drop, pin_drop)]
208211
pub trait Drop {
209212
/// Executes the destructor for this type.
210213
///
@@ -238,5 +241,29 @@ pub trait Drop {
238241
/// [`mem::drop`]: drop
239242
/// [`ptr::drop_in_place`]: crate::ptr::drop_in_place
240243
#[stable(feature = "rust1", since = "1.0.0")]
241-
fn drop(&mut self);
244+
fn drop(&mut self) {
245+
// SAFETY: `self` is pinned till after dropped.
246+
Drop::pin_drop(unsafe { Pin::new_unchecked(self) })
247+
}
248+
249+
/// Execute the destructor for this type, but different to [`Drop::drop`], it requires `self`
250+
/// to be pinned.
251+
///
252+
/// By implementing this method instead of [`Drop::drop`], the receiver type [`Pin<&mut Self>`]
253+
/// makes sure that the value is pinned until it is deallocated (See [`std::pin` module docs] for
254+
/// more details), which enables us to support field projections of `Self` type safely.
255+
///
256+
/// Currently, this method is used together with a negative implentation of [`Unpin`], which means
257+
/// that the current type is never [`Unpin`]. For a given type `Foo`, you can impl `pin_drop` for it
258+
/// *if and only if* you have `impl !Unpin for Foo`.
259+
///
260+
/// See also [`Drop::drop`].
261+
///
262+
/// [`Drop::drop`]: crate::ops::Drop::drop
263+
/// [`Unpin`]: crate::marker::Unpin
264+
/// [`!Unpin`]: crate::marker::Unpin
265+
/// [`Pin<&mut Self>`]: crate::pin::Pin
266+
/// [`std::pin` module docs]: crate::pin
267+
#[unstable(feature = "pin_ergonomics", issue = "130494")]
268+
fn pin_drop(self: Pin<&mut Self>) {}
242269
}

tests/ui/async-await/async-drop/unexpected-sort.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
#![feature(async_drop)]
77
use std::future::AsyncDrop;
88
struct a;
9-
impl Drop for a { //~ ERROR: not all trait items implemented, missing: `drop`
9+
impl Drop for a {
10+
//~ ERROR: not all trait items implemented, missing one of: `drop`, `pin_drop`
1011
fn b() {} //~ ERROR: method `b` is not a member of trait `Drop`
1112
}
12-
impl AsyncDrop for a { //~ ERROR: not all trait items implemented, missing: `drop`
13+
impl AsyncDrop for a {
14+
//~ ERROR: not all trait items implemented, missing: `drop`
1315
type c = ();
1416
//~^ ERROR: type `c` is not a member of trait `AsyncDrop`
1517
}

tests/ui/async-await/async-drop/unexpected-sort.stderr

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ error[E0437]: type `c` is not a member of trait `AsyncDrop`
1010
LL | type c = ();
1111
| ^^^^^^^^^^^^ not a member of trait `AsyncDrop`
1212

13-
error[E0046]: not all trait items implemented, missing: `drop`
13+
error[E0046]: not all trait items implemented, missing one of: `drop`, `pin_drop`
1414
--> $DIR/unexpected-sort.rs:9:1
1515
|
1616
LL | impl Drop for a {
17-
| ^^^^^^^^^^^^^^^ missing `drop` in implementation
18-
|
19-
= help: implement the missing item: `fn drop(&mut self) { todo!() }`
17+
| ^^^^^^^^^^^^^^^ missing one of `drop`, `pin_drop` in implementation
2018

2119
error[E0046]: not all trait items implemented, missing: `drop`
2220
--> $DIR/unexpected-sort.rs:12:1

0 commit comments

Comments
 (0)