Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix drop order of pinned fields in project_replace #287

Merged
merged 1 commit into from
Oct 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions pin-project-internal/src/pin_project/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,8 @@ impl<'a> Context<'a> {
}

/// Generates the processing that `project_replace` does for the struct or each variant.
///
/// Note: `pinned_fields` must be in declaration order.
fn proj_own_body(
&self,
variant_ident: Option<&'a Ident>,
Expand All @@ -878,9 +880,13 @@ impl<'a> Context<'a> {
Some(variant_ident) => quote!(#ident::#variant_ident),
None => quote!(#ident),
};
// The fields of the struct and the active enum variant are dropped
// in declaration order.
// Refs: https://doc.rust-lang.org/reference/destructors.html
let pinned_fields = pinned_fields.iter().rev();

quote! {
// First, extract all the unpinned fields
// First, extract all the unpinned fields.
let __result = #proj_own #proj_move;

// Destructors will run in reverse order, so next create a guard to overwrite
Expand All @@ -890,7 +896,7 @@ impl<'a> Context<'a> {
value: ::pin_project::__private::ManuallyDrop::new(__replacement),
};

// Now create guards to drop all the pinned fields
// Now create guards to drop all the pinned fields.
//
// Due to a compiler bug (https://github.com/rust-lang/rust/issues/47949)
// this must be in its own scope, or else `__result` will not be dropped
Expand All @@ -901,7 +907,7 @@ impl<'a> Context<'a> {
)*
}

// Finally, return the result
// Finally, return the result.
__result
}
}
Expand Down
157 changes: 157 additions & 0 deletions tests/drop_order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#![warn(rust_2018_idioms, single_use_lifetimes)]

use pin_project::pin_project;
use std::{cell::Cell, pin::Pin, thread};

struct D<'a>(&'a Cell<usize>, usize);

impl Drop for D<'_> {
fn drop(&mut self) {
if !thread::panicking() {
let old = self.0.replace(self.1);
assert_eq!(old, self.1 - 1);
}
}
}

#[pin_project(project_replace)]
struct StructPinned<'a> {
#[pin]
f1: D<'a>,
#[pin]
f2: D<'a>,
}

#[pin_project(project_replace)]
struct StructUnpinned<'a> {
f1: D<'a>,
f2: D<'a>,
}

#[pin_project(project_replace)]
struct TuplePinned<'a>(#[pin] D<'a>, #[pin] D<'a>);

#[pin_project(project_replace)]
struct TupleUnpinned<'a>(D<'a>, D<'a>);

#[pin_project(project_replace= EnumProj)]
enum Enum<'a> {
StructPinned {
#[pin]
f1: D<'a>,
#[pin]
f2: D<'a>,
},
StructUnpinned {
f1: D<'a>,
f2: D<'a>,
},
TuplePinned(#[pin] D<'a>, #[pin] D<'a>),
TupleUnpinned(D<'a>, D<'a>),
}

#[test]
fn struct_pinned() {
{
let c = Cell::new(0);
let _x = StructPinned { f1: D(&c, 1), f2: D(&c, 2) };
}
{
let c = Cell::new(0);
let mut _x = StructPinned { f1: D(&c, 1), f2: D(&c, 2) };
let _y = Pin::new(&mut _x);
let _z = _y.project_replace(StructPinned { f1: D(&c, 3), f2: D(&c, 4) });
}
}

#[test]
fn struct_unpinned() {
{
let c = Cell::new(0);
let _x = StructUnpinned { f1: D(&c, 1), f2: D(&c, 2) };
}
{
let c = Cell::new(0);
let mut _x = StructUnpinned { f1: D(&c, 1), f2: D(&c, 2) };
let _y = Pin::new(&mut _x);
let _z = _y.project_replace(StructUnpinned { f1: D(&c, 3), f2: D(&c, 4) });
}
}

#[test]
fn tuple_pinned() {
{
let c = Cell::new(0);
let _x = TuplePinned(D(&c, 1), D(&c, 2));
}
{
let c = Cell::new(0);
let mut _x = TuplePinned(D(&c, 1), D(&c, 2));
let _y = Pin::new(&mut _x);
let _z = _y.project_replace(TuplePinned(D(&c, 3), D(&c, 4)));
}
}

#[test]
fn tuple_unpinned() {
{
let c = Cell::new(0);
let _x = TupleUnpinned(D(&c, 1), D(&c, 2));
}
{
let c = Cell::new(0);
let mut _x = TupleUnpinned(D(&c, 1), D(&c, 2));
let _y = Pin::new(&mut _x);
let _z = _y.project_replace(TupleUnpinned(D(&c, 3), D(&c, 4)));
}
}

#[test]
fn enum_struct() {
{
let c = Cell::new(0);
let _x = Enum::StructPinned { f1: D(&c, 1), f2: D(&c, 2) };
}
{
let c = Cell::new(0);
let mut _x = Enum::StructPinned { f1: D(&c, 1), f2: D(&c, 2) };
let _y = Pin::new(&mut _x);
let _z = _y.project_replace(Enum::StructPinned { f1: D(&c, 3), f2: D(&c, 4) });
}

{
let c = Cell::new(0);
let _x = Enum::StructUnpinned { f1: D(&c, 1), f2: D(&c, 2) };
}
{
let c = Cell::new(0);
let mut _x = Enum::StructUnpinned { f1: D(&c, 1), f2: D(&c, 2) };
let _y = Pin::new(&mut _x);
let _z = _y.project_replace(Enum::StructUnpinned { f1: D(&c, 3), f2: D(&c, 4) });
}
}

#[test]
fn enum_tuple() {
{
let c = Cell::new(0);
let _x = Enum::TuplePinned(D(&c, 1), D(&c, 2));
}
{
let c = Cell::new(0);
let mut _x = Enum::TuplePinned(D(&c, 1), D(&c, 2));
let _y = Pin::new(&mut _x);
let _z = _y.project_replace(Enum::TuplePinned(D(&c, 3), D(&c, 4)));
}

{
let c = Cell::new(0);
let _x = Enum::TupleUnpinned(D(&c, 1), D(&c, 2));
}
{
let c = Cell::new(0);
let mut _x = Enum::TupleUnpinned(D(&c, 1), D(&c, 2));
let _y = Pin::new(&mut _x);
let _z = _y.project_replace(Enum::TupleUnpinned(D(&c, 3), D(&c, 4)));
}
}
Loading