diff --git a/pin-project-internal/src/pin_project/enums.rs b/pin-project-internal/src/pin_project/enums.rs index 03f753af..648e2be6 100644 --- a/pin-project-internal/src/pin_project/enums.rs +++ b/pin-project-internal/src/pin_project/enums.rs @@ -79,7 +79,7 @@ fn named( for Field { attrs, ident, ty, .. } in fields { if let Some(attr) = attrs.find_remove(PIN) { let _: Nothing = syn::parse2(attr.tokens)?; - cx.push_unpin_bounds(ty); + cx.push_unpin_bounds(ty.clone()); let lifetime = &cx.lifetime; proj_body.push(quote!(#ident: ::core::pin::Pin::new_unchecked(#ident))); proj_field.push(quote!(#ident: ::core::pin::Pin<&#lifetime mut #ty>)); @@ -108,7 +108,7 @@ fn unnamed( let x = format_ident!("_x{}", i); if let Some(attr) = attrs.find_remove(PIN) { let _: Nothing = syn::parse2(attr.tokens)?; - cx.push_unpin_bounds(ty); + cx.push_unpin_bounds(ty.clone()); let lifetime = &cx.lifetime; proj_body.push(quote!(::core::pin::Pin::new_unchecked(#x))); proj_field.push(quote!(::core::pin::Pin<&#lifetime mut #ty>)); diff --git a/pin-project-internal/src/pin_project/mod.rs b/pin-project-internal/src/pin_project/mod.rs index 5d35a8ea..d53ffbc7 100644 --- a/pin-project-internal/src/pin_project/mod.rs +++ b/pin-project-internal/src/pin_project/mod.rs @@ -64,6 +64,8 @@ struct Context { /// Where-clause for conditional Unpin implementation. impl_unpin: WhereClause, + pinned_fields: Vec, + unsafe_unpin: bool, pinned_drop: Option, } @@ -97,6 +99,7 @@ impl Context { generics, lifetime, impl_unpin, + pinned_fields: vec![], unsafe_unpin: unsafe_unpin.is_some(), pinned_drop, }) @@ -109,21 +112,107 @@ impl Context { generics } - fn push_unpin_bounds(&mut self, ty: &Type) { - // We only add bounds for automatically generated impls - if !self.unsafe_unpin { - self.impl_unpin.predicates.push(syn::parse_quote!(#ty: ::core::marker::Unpin)); - } + fn push_unpin_bounds(&mut self, ty: Type) { + self.pinned_fields.push(ty); } /// Makes conditional `Unpin` implementation for original type. fn make_unpin_impl(&self) -> TokenStream { let orig_ident = &self.orig_ident; let (impl_generics, ty_generics, _) = self.generics.split_for_impl(); + let type_params: Vec<_> = self.generics.type_params().map(|t| t.ident.clone()).collect(); let where_clause = &self.impl_unpin; - quote! { - impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #where_clause {} + if self.unsafe_unpin { + quote! { + impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #where_clause {} + } + } else { + let struct_ident = format_ident!("__UnpinStruct{}", orig_ident); + + // Generate a field in our new struct for every + // pinned field in the original type + let fields: Vec<_> = self + .pinned_fields + .iter() + .enumerate() + .map(|(i, ty)| { + let field_ident = format_ident!("__field{}", i); + quote! { + #field_ident: #ty + } + }) + .collect(); + + let fields_comma = if fields.is_empty() { quote!() } else { quote!(,) }; + + // We could try to determine the subset of type parameters + // and lifetimes that are actually used by the pinned fields + // (as opposed to those only used by unpinned fields). + // However, this would be tricky and error-prone, since + // it's possible for users to create types that would alias + // with generic parameters (e.g. 'struct T'). + // + // Instead, we generate a use of every single type parameter + // and lifetime used in the original struct. For type parameters, + // we generate code like this: + // + // ```rust + // struct AlwaysUnpin(PhantomData) {} + // impl Unpin for AlwaysUnpin {} + // + // ... + // _field: AlwaysUnpin<(A, B, C)> + // ``` + // + // This ensures that any unused type paramters + // don't end up with Unpin bounds + let lifetime_fields: Vec<_> = self + .generics + .lifetimes() + .enumerate() + .map(|(i, l)| { + let field_ident = format_ident!("__lifetime{}", i); + quote! { + #field_ident: &#l () + } + }) + .collect(); + + let fn_ident = format_ident!("__unpin_scope_{}", orig_ident); + + let full_generics = &self.generics; + let mut full_where_clause = where_clause.clone(); + + let unpin_clause: WherePredicate = syn::parse_quote! { + #struct_ident #ty_generics: ::core::marker::Unpin + }; + + full_where_clause.predicates.push(unpin_clause); + + quote! { + // By writing our type inside a function scope, + // we guarantee that it cannot be named by another + // other code + #[allow(non_snake_case)] + fn #fn_ident() { + + struct AlwaysUnpin { + val: ::core::marker::PhantomData + } + + impl ::core::marker::Unpin for AlwaysUnpin {} + + pub struct #struct_ident #full_generics #where_clause { + __pin_project_use_generics: AlwaysUnpin<(#(#type_params),*)>, + + #(#fields),* #fields_comma + #(#lifetime_fields),* + } + + impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #full_where_clause {} + } + } } } diff --git a/pin-project-internal/src/pin_project/structs.rs b/pin-project-internal/src/pin_project/structs.rs index b60a3dd8..af805d30 100644 --- a/pin-project-internal/src/pin_project/structs.rs +++ b/pin-project-internal/src/pin_project/structs.rs @@ -53,7 +53,7 @@ fn named( for Field { attrs, ident, ty, .. } in fields { if let Some(attr) = attrs.find_remove(PIN) { let _: Nothing = syn::parse2(attr.tokens)?; - cx.push_unpin_bounds(ty); + cx.push_unpin_bounds(ty.clone()); let lifetime = &cx.lifetime; proj_fields.push(quote!(#ident: ::core::pin::Pin<&#lifetime mut #ty>)); proj_init.push(quote!(#ident: ::core::pin::Pin::new_unchecked(&mut this.#ident))); @@ -79,7 +79,7 @@ fn unnamed( let i = Index::from(i); if let Some(attr) = attrs.find_remove(PIN) { let _: Nothing = syn::parse2(attr.tokens)?; - cx.push_unpin_bounds(ty); + cx.push_unpin_bounds(ty.clone()); let lifetime = &cx.lifetime; proj_fields.push(quote!(::core::pin::Pin<&#lifetime mut #ty>)); proj_init.push(quote!(::core::pin::Pin::new_unchecked(&mut this.#i))); diff --git a/tests/pin_project.rs b/tests/pin_project.rs index 716a80de..b20afa13 100644 --- a/tests/pin_project.rs +++ b/tests/pin_project.rs @@ -238,3 +238,27 @@ fn combine() { #[allow(unsafe_code)] unsafe impl UnsafeUnpin for Foo {} } + +#[test] +// This 'allow' is unrelated to the code +// generated by pin-project - it's just to +// allow us to put a private enum in a public enum +#[allow(private_in_public)] +fn test_private_type_in_public_type() { + #[pin_project] + pub struct PublicStruct { + #[pin] + inner: PrivateStruct, + } + + struct PrivateStruct(T); + + #[pin_project] + pub enum PublicEnum { + Variant(#[pin] PrivateEnum), + } + + enum PrivateEnum { + OtherVariant(u8), + } +} diff --git a/tests/ui/pin_project/proper_unpin.rs b/tests/ui/pin_project/proper_unpin.rs new file mode 100644 index 00000000..1eac4780 --- /dev/null +++ b/tests/ui/pin_project/proper_unpin.rs @@ -0,0 +1,25 @@ +// compile-fail + +#![deny(warnings, unsafe_code)] + +use pin_project::{pin_project, pinned_drop}; +use std::pin::Pin; + +struct Inner { + val: T +} + +#[pin_project] +struct Foo { + #[pin] + inner: Inner, + other: U +} + +fn is_unpin() {} + +fn bar() { + is_unpin::>(); //~ ERROR E0277 +} + +fn main() {} diff --git a/tests/ui/pin_project/proper_unpin.stderr b/tests/ui/pin_project/proper_unpin.stderr new file mode 100644 index 00000000..1066084e --- /dev/null +++ b/tests/ui/pin_project/proper_unpin.stderr @@ -0,0 +1,19 @@ +error[E0277]: the trait bound `T: std::marker::Unpin` is not satisfied in `__unpin_scope_Foo::__UnpinStructFoo` + --> $DIR/proper_unpin.rs:22:5 + | +22 | is_unpin::>(); //~ ERROR E0277 + | ^^^^^^^^^^^^^^^^^^^^^ within `__unpin_scope_Foo::__UnpinStructFoo`, the trait `std::marker::Unpin` is not implemented for `T` + | + = help: consider adding a `where T: std::marker::Unpin` bound + = note: required because it appears within the type `Inner` + = note: required because it appears within the type `__unpin_scope_Foo::__UnpinStructFoo` + = note: required because of the requirements on the impl of `std::marker::Unpin` for `Foo` +note: required by `is_unpin` + --> $DIR/proper_unpin.rs:19:1 + | +19 | fn is_unpin() {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`.