From 1db85fa1676819949c75b6cfdeed001e6f3c270b Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Sun, 26 Apr 2020 22:42:12 +0100 Subject: [PATCH] Implement `project_replace` method. --- pin-project-internal/src/lib.rs | 19 +- .../src/pin_project/derive.rs | 298 +++++++++++++++--- pin-project-internal/src/project.rs | 6 +- pin-project-internal/src/utils.rs | 11 +- src/lib.rs | 35 +- tests/cfg.rs | 16 +- tests/no_infer_outlives.rs | 2 +- tests/pin_project.rs | 66 ++-- tests/project_replace.rs | 75 +++++ 9 files changed, 441 insertions(+), 87 deletions(-) create mode 100644 tests/project_replace.rs diff --git a/pin-project-internal/src/lib.rs b/pin-project-internal/src/lib.rs index aa0eb27f..a8a8c668 100644 --- a/pin-project-internal/src/lib.rs +++ b/pin-project-internal/src/lib.rs @@ -26,7 +26,7 @@ mod project; use proc_macro::TokenStream; -use utils::{Immutable, Mutable}; +use utils::{Immutable, Mutable, Owned}; /// An attribute that creates a projection struct covering all the fields. /// @@ -520,7 +520,7 @@ pub fn project(args: TokenStream, input: TokenStream) -> TokenStream { /// `project_ref` method. /// /// This is the same as [`project`] attribute except it refers to the projected -/// type returned by `project_ref` method. +/// type returned by the `project_ref` method. /// /// See [`project`] attribute for more details. /// @@ -531,6 +531,21 @@ pub fn project_ref(args: TokenStream, input: TokenStream) -> TokenStream { project::attribute(&args.into(), input, Immutable).into() } +/// An attribute to provide way to refer to the projected type returned by +/// `project_replace` method. +/// +/// This is the same as [`project`] attribute except it refers to the projected +/// type returned by the `project_replace` method. +/// +/// See [`project`] attribute for more details. +/// +/// [`project`]: ./attr.project.html +#[proc_macro_attribute] +pub fn project_replace(args: TokenStream, input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input); + project::attribute(&args.into(), input, Owned).into() +} + /// An internal helper macro. #[doc(hidden)] #[proc_macro_derive(__PinProjectInternalDerive, attributes(pin))] diff --git a/pin-project-internal/src/pin_project/derive.rs b/pin-project-internal/src/pin_project/derive.rs index 7d573c26..2a1c3149 100644 --- a/pin-project-internal/src/pin_project/derive.rs +++ b/pin-project-internal/src/pin_project/derive.rs @@ -129,6 +129,7 @@ fn validate_enum(brace_token: token::Brace, variants: &Variants) -> Result<()> { struct Args { pinned_drop: Option, unsafe_unpin: Option, + replace: Option, } const DUPLICATE_PIN: &str = "duplicate #[pin] attribute"; @@ -194,9 +195,25 @@ impl Parse for Args { "PinnedDrop" => { if args.pinned_drop.is_some() { return Err(error!(ident, "duplicate `PinnedDrop` argument")); + } else if args.replace.is_some() { + return Err(error!( + ident, + "arguments `PinnedDrop` and `Replace` are mutually exclusive" + )); } args.pinned_drop = Some(ident.span()); } + "Replace" => { + if args.replace.is_some() { + return Err(error!(ident, "duplicate `Replace` argument")); + } else if args.pinned_drop.is_some() { + return Err(error!( + ident, + "arguments `PinnedDrop` and `Replace` are mutually exclusive" + )); + } + args.replace = Some(ident.span()); + } "UnsafeUnpin" => { if args.unsafe_unpin.is_some() { return Err(error!(ident, "duplicate `UnsafeUnpin` argument")); @@ -233,6 +250,8 @@ struct ProjectedType { mut_ident: Ident, /// Name of the projected type returned by `project_ref` method. ref_ident: Ident, + /// Name of the projected type returned by `project_replace` method. + own_ident: Ident, /// Lifetime on the generated projected type. lifetime: Lifetime, /// Generics of the projected type. @@ -242,6 +261,26 @@ struct ProjectedType { where_clause: WhereClause, } +struct ProjectedVariants { + proj_variants: TokenStream, + proj_ref_variants: TokenStream, + proj_own_variants: TokenStream, + proj_arms: TokenStream, + proj_ref_arms: TokenStream, + proj_own_arms: TokenStream, +} + +#[derive(Default)] +struct ProjectedFields { + proj_pat: TokenStream, + proj_body: TokenStream, + proj_fields: TokenStream, + proj_ref_fields: TokenStream, + proj_own_fields: TokenStream, + proj_move: TokenStream, + proj_drop: TokenStream, +} + struct Context<'a> { orig: OriginalType<'a>, proj: ProjectedType, @@ -251,6 +290,8 @@ struct Context<'a> { pinned_drop: Option, /// `UnsafeUnpin` attribute. unsafe_unpin: Option, + // `Replace` attribute (requires Sized bound) + replace: Option, } impl<'a> Context<'a> { @@ -260,7 +301,7 @@ impl<'a> Context<'a> { ident: &'a Ident, generics: &'a mut Generics, ) -> Result { - let Args { pinned_drop, unsafe_unpin } = Args::get(attrs)?; + let Args { pinned_drop, unsafe_unpin, replace } = Args::get(attrs)?; { let ty_generics = generics.split_for_impl().1; @@ -290,6 +331,7 @@ impl<'a> Context<'a> { vis: determine_visibility(vis), mut_ident: proj_ident(ident, Mutable), ref_ident: proj_ident(ident, Immutable), + own_ident: proj_ident(ident, Owned), lifetime, generics: proj_generics, where_clause, @@ -297,12 +339,21 @@ impl<'a> Context<'a> { orig: OriginalType { attrs, vis, ident, generics }, pinned_drop, unsafe_unpin, + replace, pinned_fields: Vec::new(), }) } fn parse_struct(&mut self, fields: &Fields) -> Result<(TokenStream, TokenStream)> { - let (proj_pat, proj_init, proj_fields, proj_ref_fields) = match fields { + let ProjectedFields { + proj_pat, + proj_body, + proj_fields, + proj_ref_fields, + proj_own_fields, + proj_move, + proj_drop, + } = match fields { Fields::Named(fields) => self.visit_named(fields)?, Fields::Unnamed(fields) => self.visit_unnamed(fields)?, Fields::Unit => unreachable!(), @@ -311,23 +362,31 @@ impl<'a> Context<'a> { let orig_ident = self.orig.ident; let proj_ident = &self.proj.mut_ident; let proj_ref_ident = &self.proj.ref_ident; + let proj_own_ident = &self.proj.own_ident; let vis = &self.proj.vis; + let mut orig_generics = self.orig.generics.clone(); + let orig_where_clause = orig_generics.where_clause.take(); let proj_generics = &self.proj.generics; let where_clause = &self.proj.where_clause; + let private = Ident::new(CURRENT_PRIVATE_MODULE, Span::call_site()); // For tuple structs, we need to generate `(T1, T2) where Foo: Bar` // For non-tuple structs, we need to generate `where Foo: Bar { field1: T }` - let (where_clause_fields, where_clause_ref_fields) = match fields { - Fields::Named(_) => { - (quote!(#where_clause #proj_fields), quote!(#where_clause #proj_ref_fields)) - } - Fields::Unnamed(_) => { - (quote!(#proj_fields #where_clause;), quote!(#proj_ref_fields #where_clause;)) - } + let (where_clause_fields, where_clause_ref_fields, where_clause_own_fields) = match fields { + Fields::Named(_) => ( + quote!(#where_clause #proj_fields), + quote!(#where_clause #proj_ref_fields), + quote!(#orig_where_clause #proj_own_fields), + ), + Fields::Unnamed(_) => ( + quote!(#proj_fields #where_clause;), + quote!(#proj_ref_fields #where_clause;), + quote!(#proj_own_fields #orig_where_clause;), + ), Fields::Unit => unreachable!(), }; - let proj_items = quote! { + let mut proj_items = quote! { #[allow(clippy::mut_mut)] // This lint warns `&mut &mut `. #[allow(dead_code)] // This lint warns unused fields/variants. #vis struct #proj_ident #proj_generics #where_clause_fields @@ -335,26 +394,62 @@ impl<'a> Context<'a> { #vis struct #proj_ref_ident #proj_generics #where_clause_ref_fields }; - let proj_body = quote! { + if let Some(replace) = self.replace { + proj_items.extend(quote_spanned! { replace => + #[allow(dead_code)] // This lint warns unused fields/variants. + #vis struct #proj_own_ident #orig_generics #where_clause_own_fields + }) + } + + let proj_mut_body = quote! { let #orig_ident #proj_pat = self.get_unchecked_mut(); - #proj_ident #proj_init + #proj_ident #proj_body }; let proj_ref_body = quote! { let #orig_ident #proj_pat = self.get_ref(); - #proj_ref_ident #proj_init + #proj_ref_ident #proj_body + }; + let proj_own_body = quote! { + let __self_ptr: *mut Self = self.get_unchecked_mut(); + let #orig_ident #proj_pat = &mut *__self_ptr; + + // First, extract all the unpinned fields + let __result = #proj_own_ident #proj_move; + + // Destructors will run in reverse order, so next create a guard to overwrite + // `self` with the replacement value without calling destructors. + let __guard = ::pin_project::#private::UnsafeOverwriteGuard { + target: __self_ptr, + value: ::core::mem::ManuallyDrop::new(__replacement), + }; + + // Now create guards to drop all the pinned fields + #proj_drop + + // Finally, return the result + __result }; - let proj_impl = self.make_proj_impl(&proj_body, &proj_ref_body); + let proj_impl = self.make_proj_impl(&proj_mut_body, &proj_ref_body, &proj_own_body); Ok((proj_items, proj_impl)) } fn parse_enum(&mut self, variants: &Variants) -> Result<(TokenStream, TokenStream)> { - let (proj_variants, proj_ref_variants, proj_arms, proj_ref_arms) = - self.visit_variants(variants)?; + let ProjectedVariants { + proj_variants, + proj_ref_variants, + proj_own_variants, + proj_arms, + proj_ref_arms, + proj_own_arms, + } = self.visit_variants(variants)?; let proj_ident = &self.proj.mut_ident; let proj_ref_ident = &self.proj.ref_ident; + let proj_own_ident = &self.proj.own_ident; let vis = &self.proj.vis; + let mut orig_generics = self.orig.generics.clone(); + let orig_where_clause = orig_generics.where_clause.take(); let proj_generics = &self.proj.generics; let where_clause = &self.proj.where_clause; @@ -368,9 +463,13 @@ impl<'a> Context<'a> { #vis enum #proj_ref_ident #proj_generics #where_clause { #proj_ref_variants } + #[allow(dead_code)] // This lint warns unused fields/variants. + #vis enum #proj_own_ident #orig_generics #orig_where_clause { + #proj_own_variants + } }; - let proj_body = quote! { + let proj_mut_body = quote! { match self.get_unchecked_mut() { #proj_arms } @@ -380,37 +479,54 @@ impl<'a> Context<'a> { #proj_ref_arms } }; - let proj_impl = self.make_proj_impl(&proj_body, &proj_ref_body); + let proj_own_body = quote! { + let __self_ptr: *mut Self = self.get_unchecked_mut(); + match &mut *__self_ptr { + #proj_own_arms + } + }; + let proj_impl = self.make_proj_impl(&proj_mut_body, &proj_ref_body, &proj_own_body); Ok((proj_items, proj_impl)) } - fn visit_variants( - &mut self, - variants: &Variants, - ) -> Result<(TokenStream, TokenStream, TokenStream, TokenStream)> { + fn visit_variants(&mut self, variants: &Variants) -> Result { let mut proj_variants = TokenStream::new(); let mut proj_ref_variants = TokenStream::new(); + let mut proj_own_variants = TokenStream::new(); let mut proj_arms = TokenStream::new(); let mut proj_ref_arms = TokenStream::new(); + let mut proj_own_arms = TokenStream::new(); + let private = Ident::new(CURRENT_PRIVATE_MODULE, Span::call_site()); + for Variant { ident, fields, .. } in variants { - let (proj_pat, proj_body, proj_fields, proj_ref_fields) = match fields { + let ProjectedFields { + proj_pat, + proj_body, + proj_fields, + proj_ref_fields, + proj_own_fields, + proj_move, + proj_drop, + } = match fields { Fields::Named(fields) => self.visit_named(fields)?, Fields::Unnamed(fields) => self.visit_unnamed(fields)?, - Fields::Unit => { - (TokenStream::new(), TokenStream::new(), TokenStream::new(), TokenStream::new()) - } + Fields::Unit => ProjectedFields::default(), }; let orig_ident = self.orig.ident; let proj_ident = &self.proj.mut_ident; let proj_ref_ident = &self.proj.ref_ident; + let proj_own_ident = &self.proj.own_ident; proj_variants.extend(quote! { #ident #proj_fields, }); proj_ref_variants.extend(quote! { #ident #proj_ref_fields, }); + proj_own_variants.extend(quote! { + #ident #proj_own_fields, + }); proj_arms.extend(quote! { #orig_ident::#ident #proj_pat => { #proj_ident::#ident #proj_body @@ -421,19 +537,50 @@ impl<'a> Context<'a> { #proj_ref_ident::#ident #proj_body } }); + proj_own_arms.extend(quote! { + #orig_ident::#ident #proj_pat => { + // First, extract all the unpinned fields + let __result = #proj_own_ident::#ident #proj_move; + + // Destructors will run in reverse order, so next create a guard to overwrite + // `self` with the replacement value without calling destructors. + let __guard = ::pin_project::#private::UnsafeOverwriteGuard { + target: __self_ptr, + value: ::core::mem::ManuallyDrop::new(__replacement), + }; + + // Now create guards to drop all the pinned fields + #proj_drop + + // Finally, return the result + __result + } + }); } - Ok((proj_variants, proj_ref_variants, proj_arms, proj_ref_arms)) + Ok(ProjectedVariants { + proj_variants, + proj_ref_variants, + proj_own_variants, + proj_arms, + proj_ref_arms, + proj_own_arms, + }) } fn visit_named( &mut self, FieldsNamed { named: fields, .. }: &FieldsNamed, - ) -> Result<(TokenStream, TokenStream, TokenStream, TokenStream)> { + ) -> Result { let mut proj_pat = Vec::with_capacity(fields.len()); let mut proj_body = Vec::with_capacity(fields.len()); let mut proj_fields = Vec::with_capacity(fields.len()); let mut proj_ref_fields = Vec::with_capacity(fields.len()); + let mut proj_own_fields = Vec::with_capacity(fields.len()); + let mut proj_move = Vec::with_capacity(fields.len()); + let mut proj_drop = Vec::with_capacity(fields.len()); + let private = Ident::new(CURRENT_PRIVATE_MODULE, Span::call_site()); + for Field { attrs, vis, ident, ty, .. } in fields { if attrs.find_exact(PIN)?.is_some() { self.pinned_fields.push(ty.clone()); @@ -445,9 +592,18 @@ impl<'a> Context<'a> { proj_ref_fields.push(quote! { #vis #ident: ::core::pin::Pin<&#lifetime (#ty)> }); + proj_own_fields.push(quote! { + #vis #ident: ::core::marker::PhantomData<#ty> + }); proj_body.push(quote! { #ident: ::core::pin::Pin::new_unchecked(#ident) }); + proj_move.push(quote! { + #ident: ::core::marker::PhantomData + }); + proj_drop.push(quote! { + let __guard = ::pin_project::#private::UnsafeDropInPlaceGuard(#ident); + }); } else { let lifetime = &self.proj.lifetime; proj_fields.push(quote! { @@ -456,9 +612,15 @@ impl<'a> Context<'a> { proj_ref_fields.push(quote! { #vis #ident: &#lifetime (#ty) }); + proj_own_fields.push(quote! { + #vis #ident: #ty + }); proj_body.push(quote! { #ident }); + proj_move.push(quote! { + #ident: ::core::ptr::read(#ident) + }); } proj_pat.push(ident); } @@ -467,18 +629,34 @@ impl<'a> Context<'a> { let proj_body = quote!({ #(#proj_body),* }); let proj_fields = quote!({ #(#proj_fields),* }); let proj_ref_fields = quote!({ #(#proj_ref_fields),* }); - - Ok((proj_pat, proj_body, proj_fields, proj_ref_fields)) + let proj_own_fields = quote!({ #(#proj_own_fields),* }); + let proj_move = quote!({ #(#proj_move),* }); + let proj_drop = quote!(#(#proj_drop)*); + + Ok(ProjectedFields { + proj_pat, + proj_body, + proj_fields, + proj_ref_fields, + proj_own_fields, + proj_move, + proj_drop, + }) } fn visit_unnamed( &mut self, FieldsUnnamed { unnamed: fields, .. }: &FieldsUnnamed, - ) -> Result<(TokenStream, TokenStream, TokenStream, TokenStream)> { + ) -> Result { let mut proj_pat = Vec::with_capacity(fields.len()); let mut proj_body = Vec::with_capacity(fields.len()); let mut proj_fields = Vec::with_capacity(fields.len()); let mut proj_ref_fields = Vec::with_capacity(fields.len()); + let mut proj_own_fields = Vec::with_capacity(fields.len()); + let mut proj_move = Vec::with_capacity(fields.len()); + let mut proj_drop = Vec::with_capacity(fields.len()); + let private = Ident::new(CURRENT_PRIVATE_MODULE, Span::call_site()); + for (i, Field { attrs, vis, ty, .. }) in fields.iter().enumerate() { let id = format_ident!("_{}", i); if attrs.find_exact(PIN)?.is_some() { @@ -491,9 +669,18 @@ impl<'a> Context<'a> { proj_ref_fields.push(quote! { #vis ::core::pin::Pin<&#lifetime (#ty)> }); + proj_own_fields.push(quote! { + #vis ::core::marker::PhantomData<#ty> + }); proj_body.push(quote! { ::core::pin::Pin::new_unchecked(#id) }); + proj_move.push(quote! { + ::core::marker::PhantomData + }); + proj_drop.push(quote! { + let __guard = ::pin_project::#private::UnsafeDropInPlaceGuard(#id); + }); } else { let lifetime = &self.proj.lifetime; proj_fields.push(quote! { @@ -502,19 +689,36 @@ impl<'a> Context<'a> { proj_ref_fields.push(quote! { #vis &#lifetime (#ty) }); + proj_own_fields.push(quote! { + #vis #ty + }); proj_body.push(quote! { #id }); + proj_move.push(quote! { + ::core::ptr::read(#id) + }); } proj_pat.push(id); } let proj_pat = quote!((#(#proj_pat),*)); let proj_body = quote!((#(#proj_body),*)); - let (proj_fields, proj_ref_fields) = - (quote!((#(#proj_fields),*)), quote!((#(#proj_ref_fields),*))); - - Ok((proj_pat, proj_body, proj_fields, proj_ref_fields)) + let proj_fields = quote!((#(#proj_fields),*)); + let proj_ref_fields = quote!((#(#proj_ref_fields),*)); + let proj_own_fields = quote!((#(#proj_own_fields),*)); + let proj_move = quote!((#(#proj_move),*)); + let proj_drop = quote!(#(#proj_drop)*); + + Ok(ProjectedFields { + proj_pat, + proj_body, + proj_fields, + proj_ref_fields, + proj_own_fields, + proj_move, + proj_drop, + }) } /// Creates conditional `Unpin` implementation for original type. @@ -709,16 +913,37 @@ impl<'a> Context<'a> { } /// Creates an implementation of the projection method. - fn make_proj_impl(&self, proj_body: &TokenStream, proj_ref_body: &TokenStream) -> TokenStream { + fn make_proj_impl( + &self, + proj_body: &TokenStream, + proj_ref_body: &TokenStream, + proj_own_body: &TokenStream, + ) -> TokenStream { let vis = &self.proj.vis; let lifetime = &self.proj.lifetime; let orig_ident = self.orig.ident; let proj_ident = &self.proj.mut_ident; let proj_ref_ident = &self.proj.ref_ident; + let proj_own_ident = &self.proj.own_ident; + let orig_ty_generics = self.orig.generics.split_for_impl().1; let proj_ty_generics = self.proj.generics.split_for_impl().1; let (impl_generics, ty_generics, where_clause) = self.orig.generics.split_for_impl(); + let replace_impl = self.replace.map(|replace| { + quote_spanned! { replace => + #[allow(unsafe_code)] + #vis fn project_replace( + self: ::core::pin::Pin<&mut Self>, + __replacement: Self, + ) -> #proj_own_ident #orig_ty_generics { + unsafe { + #proj_own_body + } + } + } + }); + quote! { impl #impl_generics #orig_ident #ty_generics #where_clause { #vis fn project<#lifetime>( @@ -735,6 +960,7 @@ impl<'a> Context<'a> { #proj_ref_body } } + #replace_impl } } } diff --git a/pin-project-internal/src/project.rs b/pin-project-internal/src/project.rs index 92627d43..1ab70b9d 100644 --- a/pin-project-internal/src/project.rs +++ b/pin-project-internal/src/project.rs @@ -211,7 +211,11 @@ struct FnVisitor { impl FnVisitor { /// Returns the attribute name. fn name(&self) -> &str { - if self.mutability == Mutable { "project" } else { "project_ref" } + match self.mutability { + Mutable => "project", + Immutable => "project_ref", + Owned => "project_replace", + } } fn visit_stmt(&mut self, node: &mut Stmt) -> Result<()> { diff --git a/pin-project-internal/src/utils.rs b/pin-project-internal/src/utils.rs index 63a5176d..0971e22c 100644 --- a/pin-project-internal/src/utils.rs +++ b/pin-project-internal/src/utils.rs @@ -15,7 +15,7 @@ pub(crate) const CURRENT_PRIVATE_MODULE: &str = "__private"; pub(crate) type Variants = Punctuated; -pub(crate) use Mutability::{Immutable, Mutable}; +pub(crate) use Mutability::{Immutable, Mutable, Owned}; macro_rules! error { ($span:expr, $msg:expr) => { @@ -30,14 +30,15 @@ macro_rules! error { pub(crate) enum Mutability { Mutable, Immutable, + Owned, } /// Creates the ident of projected type from the ident of the original type. pub(crate) fn proj_ident(ident: &Ident, mutability: Mutability) -> Ident { - if mutability == Mutable { - format_ident!("__{}Projection", ident) - } else { - format_ident!("__{}ProjectionRef", ident) + match mutability { + Mutability::Mutable => format_ident!("__{}Projection", ident), + Mutability::Immutable => format_ident!("__{}ProjectionRef", ident), + Mutability::Owned => format_ident!("__{}ProjectionOwned", ident), } } diff --git a/src/lib.rs b/src/lib.rs index 7d2de266..709fcf20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,9 @@ pub use pin_project_internal::project; #[doc(inline)] pub use pin_project_internal::project_ref; +#[doc(inline)] +pub use pin_project_internal::project_replace; + /// A trait used for custom implementations of [`Unpin`]. /// This trait is used in conjunction with the `UnsafeUnpin` /// argument to [`pin_project`] @@ -119,7 +122,7 @@ pub unsafe trait UnsafeUnpin {} #[doc(hidden)] pub mod __private { use super::UnsafeUnpin; - use core::{marker::PhantomData, pin::Pin}; + use core::{marker::PhantomData, mem::ManuallyDrop, pin::Pin, ptr}; #[doc(hidden)] pub use pin_project_internal::__PinProjectInternalDerive; @@ -202,4 +205,34 @@ pub mod __private { pub struct AlwaysUnpin<'a, T: ?Sized>(PhantomData<&'a ()>, PhantomData); impl Unpin for AlwaysUnpin<'_, T> {} + + // This is an internal helper used to ensure a value is dropped. + #[doc(hidden)] + pub struct UnsafeDropInPlaceGuard(pub *mut T); + + impl Drop for UnsafeDropInPlaceGuard { + #[allow(unsafe_code)] + fn drop(&mut self) { + unsafe { + ptr::drop_in_place(self.0); + } + } + } + + // This is an internal helper used to ensure a value is overwritten without + // its destructor being called. + #[doc(hidden)] + pub struct UnsafeOverwriteGuard { + pub value: ManuallyDrop, + pub target: *mut T, + } + + impl Drop for UnsafeOverwriteGuard { + #[allow(unsafe_code)] + fn drop(&mut self) { + unsafe { + ptr::write(self.target, ptr::read(&*self.value)); + } + } + } } diff --git a/tests/cfg.rs b/tests/cfg.rs index fa632275..10587006 100644 --- a/tests/cfg.rs +++ b/tests/cfg.rs @@ -22,7 +22,7 @@ pub struct Any(PhantomPinned); fn cfg() { // structs - #[pin_project] + #[pin_project(Replace)] pub struct SameName { #[cfg(target_os = "linux")] #[pin] @@ -42,7 +42,7 @@ fn cfg() { #[cfg(not(target_os = "linux"))] let _x = SameName { inner: Other }; - #[pin_project] + #[pin_project(Replace)] pub struct DifferentName { #[cfg(target_os = "linux")] #[pin] @@ -62,7 +62,7 @@ fn cfg() { #[cfg(not(target_os = "linux"))] let _x = DifferentName { o: Other }; - #[pin_project] + #[pin_project(Replace)] pub struct TupleStruct( #[cfg(target_os = "linux")] #[pin] @@ -84,7 +84,7 @@ fn cfg() { // enums - #[pin_project] + #[pin_project(Replace)] pub enum Variant { #[cfg(target_os = "linux")] Inner(#[pin] Linux), @@ -111,7 +111,7 @@ fn cfg() { #[cfg(not(target_os = "linux"))] let _x = Variant::Other(Other); - #[pin_project] + #[pin_project(Replace)] pub enum Field { SameName { #[cfg(target_os = "linux")] @@ -168,7 +168,7 @@ fn cfg() { #[test] fn cfg_attr() { - #[pin_project] + #[pin_project(Replace)] pub struct SameCfg { #[cfg(target_os = "linux")] #[cfg_attr(target_os = "linux", pin)] @@ -194,7 +194,7 @@ fn cfg_attr() { #[cfg(not(target_os = "linux"))] let _: Pin<&mut Other> = x.inner; - #[pin_project] + #[pin_project(Replace)] pub struct DifferentCfg { #[cfg(target_os = "linux")] #[cfg_attr(target_os = "linux", pin)] @@ -234,7 +234,7 @@ fn cfg_attr() { #[test] fn cfg_attr_any_packed() { // Since `cfg(any())` can never be true, it is okay for this to pass. - #[pin_project] + #[pin_project(Replace)] #[cfg_attr(any(), repr(packed))] struct Struct { #[pin] diff --git a/tests/no_infer_outlives.rs b/tests/no_infer_outlives.rs index 6602b62c..67856476 100644 --- a/tests/no_infer_outlives.rs +++ b/tests/no_infer_outlives.rs @@ -10,7 +10,7 @@ impl Bar for Example { type Y = Option; } -#[pin_project] +#[pin_project(Replace)] struct Foo { _x: as Bar>::Y, } diff --git a/tests/pin_project.rs b/tests/pin_project.rs index 5d0c2c9b..c040623e 100644 --- a/tests/pin_project.rs +++ b/tests/pin_project.rs @@ -8,7 +8,7 @@ use pin_project::{pin_project, pinned_drop, UnsafeUnpin}; #[test] fn test_pin_project() { - #[pin_project] + #[pin_project(Replace)] struct Struct { #[pin] field1: T, @@ -37,7 +37,7 @@ fn test_pin_project() { let _: Pin<&mut i32> = field1; let _: &mut i32 = field2; - #[pin_project] + #[pin_project(Replace)] struct TupleStruct(#[pin] T, U); let mut bar = TupleStruct(1, 2); @@ -50,7 +50,7 @@ fn test_pin_project() { let y: &mut i32 = bar.1; assert_eq!(*y, 2); - #[pin_project] + #[pin_project(Replace)] #[derive(Eq, PartialEq, Debug)] enum Enum { Variant1(#[pin] A, B), @@ -114,7 +114,7 @@ fn test_pin_project() { #[test] fn enum_project_set() { - #[pin_project] + #[pin_project(Replace)] #[derive(Eq, PartialEq, Debug)] enum Bar { Variant1(#[pin] u8), @@ -138,7 +138,7 @@ fn enum_project_set() { #[test] fn where_clause_and_associated_type_fields() { - #[pin_project] + #[pin_project(Replace)] struct Struct1 where I: Iterator, @@ -148,7 +148,7 @@ fn where_clause_and_associated_type_fields() { field2: I::Item, } - #[pin_project] + #[pin_project(Replace)] struct Struct2 where I: Iterator, @@ -158,7 +158,7 @@ fn where_clause_and_associated_type_fields() { field2: J, } - #[pin_project] + #[pin_project(Replace)] pub struct Struct3 where T: 'static, @@ -170,7 +170,7 @@ fn where_clause_and_associated_type_fields() { impl Static for Struct3 {} - #[pin_project] + #[pin_project(Replace)] enum Enum where I: Iterator, @@ -195,7 +195,7 @@ fn unsized_in_where_clause() { #[test] fn derive_copy() { - #[pin_project] + #[pin_project(Replace)] #[derive(Clone, Copy)] struct Struct { val: T, @@ -210,7 +210,7 @@ fn derive_copy() { fn move_out() { struct NotCopy; - #[pin_project] + #[pin_project(Replace)] struct Struct { val: NotCopy, } @@ -218,7 +218,7 @@ fn move_out() { let foo = Struct { val: NotCopy }; let _val: NotCopy = foo.val; - #[pin_project] + #[pin_project(Replace)] enum Enum { Variant(NotCopy), } @@ -231,39 +231,39 @@ fn move_out() { #[test] fn trait_bounds_on_type_generics() { - #[pin_project] + #[pin_project(Replace)] pub struct Struct1<'a, T: ?Sized> { field: &'a mut T, } - #[pin_project] + #[pin_project(Replace)] pub struct Struct2<'a, T: ::core::fmt::Debug> { field: &'a mut T, } - #[pin_project] + #[pin_project(Replace)] pub struct Struct3<'a, T: core::fmt::Debug> { field: &'a mut T, } - #[pin_project] + #[pin_project(Replace)] pub struct Struct4<'a, T: core::fmt::Debug + core::fmt::Display> { field: &'a mut T, } - #[pin_project] + #[pin_project(Replace)] pub struct Struct5<'a, T: core::fmt::Debug + ?Sized> { field: &'a mut T, } - #[pin_project] + #[pin_project(Replace)] pub struct Struct6<'a, T: core::fmt::Debug = [u8; 16]> { field: &'a mut T, } let _: Struct6<'_> = Struct6 { field: &mut [0u8; 16] }; - #[pin_project] + #[pin_project(Replace)] pub struct Struct7 { field: T, } @@ -272,16 +272,16 @@ fn trait_bounds_on_type_generics() { impl Static for Struct7 {} - #[pin_project] + #[pin_project(Replace)] pub struct Struct8<'a, 'b: 'a> { field1: &'a u8, field2: &'b u8, } - #[pin_project] + #[pin_project(Replace)] pub struct TupleStruct<'a, T: ?Sized>(&'a mut T); - #[pin_project] + #[pin_project(Replace)] enum Enum<'a, T: ?Sized> { Variant(&'a mut T), } @@ -289,7 +289,7 @@ fn trait_bounds_on_type_generics() { #[test] fn overlapping_lifetime_names() { - #[pin_project] + #[pin_project(Replace)] pub struct Foo<'pin, T> { #[pin] field: &'pin mut T, @@ -316,7 +316,7 @@ fn combine() { #[test] fn private_type_in_public_type() { - #[pin_project] + #[pin_project(Replace)] pub struct PublicStruct { #[pin] inner: PrivateStruct, @@ -327,21 +327,21 @@ fn private_type_in_public_type() { #[test] fn lifetime_project() { - #[pin_project] + #[pin_project(Replace)] struct Struct1 { #[pin] pinned: T, unpinned: U, } - #[pin_project] + #[pin_project(Replace)] struct Struct2<'a, T, U> { #[pin] pinned: &'a mut T, unpinned: U, } - #[pin_project] + #[pin_project(Replace)] enum Enum { Variant { #[pin] @@ -385,21 +385,21 @@ fn lifetime_project() { #[rustversion::since(1.36)] // https://github.com/rust-lang/rust/pull/61207 #[test] fn lifetime_project_elided() { - #[pin_project] + #[pin_project(Replace)] struct Struct1 { #[pin] pinned: T, unpinned: U, } - #[pin_project] + #[pin_project(Replace)] struct Struct2<'a, T, U> { #[pin] pinned: &'a mut T, unpinned: U, } - #[pin_project] + #[pin_project(Replace)] enum Enum { Variant { #[pin] @@ -443,7 +443,7 @@ fn lifetime_project_elided() { mod visibility { use pin_project::pin_project; - #[pin_project] + #[pin_project(Replace)] pub(crate) struct A { pub b: u8, } @@ -461,7 +461,7 @@ fn visibility() { #[test] fn trivial_bounds() { - #[pin_project] + #[pin_project(Replace)] pub struct NoGenerics { #[pin] field: PhantomPinned, @@ -523,7 +523,7 @@ fn dyn_type() { fn self_in_where_clause() { pub trait Trait {} - #[pin_project] + #[pin_project(Replace)] pub struct Struct1 where Self: Trait, @@ -537,7 +537,7 @@ fn self_in_where_clause() { type Foo; } - #[pin_project] + #[pin_project(Replace)] pub struct Struct2 where Self: Trait2>, diff --git a/tests/project_replace.rs b/tests/project_replace.rs new file mode 100644 index 00000000..4b01962c --- /dev/null +++ b/tests/project_replace.rs @@ -0,0 +1,75 @@ +#![warn(unsafe_code)] +#![warn(rust_2018_idioms, single_use_lifetimes)] +#![allow(dead_code)] + +use pin_project::{pin_project, project_replace}; +use std::{marker::PhantomData, pin::Pin}; + +#[project_replace] // Nightly does not need a dummy attribute to the function. +#[test] +fn project_replace_stmt_expr() { + // struct + + #[pin_project(Replace)] + struct Foo { + #[pin] + field1: T, + field2: U, + } + + let mut foo = Foo { field1: 1, field2: 2 }; + + #[project_replace] + let Foo { field1, field2 } = Pin::new(&mut foo).project_replace(Foo { field1: 42, field2: 43 }); + + let _x: PhantomData = field1; + + let y: i32 = field2; + assert_eq!(y, 2); + + // tuple struct + + #[pin_project(Replace)] + struct Bar(#[pin] T, U); + + let mut bar = Bar(1, 2); + + #[project_replace] + let Bar(x, y) = Pin::new(&mut bar).project_replace(Bar(42, 43)); + + let _x: PhantomData = x; + let y: i32 = y; + assert_eq!(y, 2); + + // enum + + #[pin_project(Replace)] + enum Baz { + Variant1(#[pin] A, B), + Variant2 { + #[pin] + field1: C, + field2: D, + }, + None, + } + + let mut baz = Baz::Variant1(1, 2); + + let baz = Pin::new(&mut baz).project_replace(Baz::None); + + #[project_replace] + match baz { + Baz::Variant1(x, y) => { + let _x: PhantomData = x; + let y: i32 = y; + assert_eq!(y, 2); + } + Baz::Variant2 { field1, field2 } => { + let _x: PhantomData = field1; + let _y: i32 = field2; + panic!() + } + Baz::None => panic!(), + } +}