Skip to content

Commit 3380761

Browse files
committed
Allow using #[pin_project] type with private field types
Previously, given code such as: ```rust struct Private<T>; \#[pin_project] pub struct Public<T> { #[pin] private: Private<T> } ``` we would generate an Unpin impl like this: ```rust impl Unpin for Public where Private: Unpin {} ``` Unfortunately, since Private is not a public type, this would cause an E0446 ('private type `Private` in public interface) When RFC 2145 is implemented (rust-lang/rust#48054), this will become a lint, rather then a hard error. In the time being, we need a solution that will work with the current type privacy rules. The solution is to generate code like this: ```rust fn __private_scope() { pub struct __UnpinPublic<T> { __field0: Private<T> } impl<T> Unpin for Public<T> where __UnpinPublic<T>: Unpin {} } ``` That is, we generate a new struct, containing all of the pinned fields from our #[pin_project] type. This struct is delcared within a function, which makes it impossible to be named by user code. This guarnatees that it will use the default auto-trait impl for Unpin - that is, it will implement Unpin iff all of its fields implement Unpin. This type can be safely declared as 'public', satisfiying the privacy checker without actually allowing user code to access it. This allows users to apply the #[pin_project] attribute to types regardless of the privacy of the types of their fields.
1 parent c3f6a41 commit 3380761

File tree

6 files changed

+144
-11
lines changed

6 files changed

+144
-11
lines changed

pin-project-internal/src/pin_project/enums.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ fn named(
7979
for Field { attrs, ident, ty, .. } in fields {
8080
if let Some(attr) = attrs.find_remove(PIN) {
8181
let _: Nothing = syn::parse2(attr.tokens)?;
82-
cx.push_unpin_bounds(ty);
82+
cx.push_unpin_bounds(ty.clone());
8383
let lifetime = &cx.lifetime;
8484
proj_body.push(quote!(#ident: ::core::pin::Pin::new_unchecked(#ident)));
8585
proj_field.push(quote!(#ident: ::core::pin::Pin<&#lifetime mut #ty>));
@@ -108,7 +108,7 @@ fn unnamed(
108108
let x = format_ident!("_x{}", i);
109109
if let Some(attr) = attrs.find_remove(PIN) {
110110
let _: Nothing = syn::parse2(attr.tokens)?;
111-
cx.push_unpin_bounds(ty);
111+
cx.push_unpin_bounds(ty.clone());
112112
let lifetime = &cx.lifetime;
113113
proj_body.push(quote!(::core::pin::Pin::new_unchecked(#x)));
114114
proj_field.push(quote!(::core::pin::Pin<&#lifetime mut #ty>));

pin-project-internal/src/pin_project/mod.rs

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ struct Context {
6464
/// Where-clause for conditional Unpin implementation.
6565
impl_unpin: WhereClause,
6666

67+
pinned_fields: Vec<Type>,
68+
6769
unsafe_unpin: bool,
6870
pinned_drop: Option<Span>,
6971
}
@@ -97,6 +99,7 @@ impl Context {
9799
generics,
98100
lifetime,
99101
impl_unpin,
102+
pinned_fields: vec![],
100103
unsafe_unpin: unsafe_unpin.is_some(),
101104
pinned_drop,
102105
})
@@ -109,21 +112,83 @@ impl Context {
109112
generics
110113
}
111114

112-
fn push_unpin_bounds(&mut self, ty: &Type) {
113-
// We only add bounds for automatically generated impls
114-
if !self.unsafe_unpin {
115-
self.impl_unpin.predicates.push(syn::parse_quote!(#ty: ::core::marker::Unpin));
116-
}
115+
fn push_unpin_bounds(&mut self, ty: Type) {
116+
self.pinned_fields.push(ty);
117117
}
118118

119119
/// Makes conditional `Unpin` implementation for original type.
120120
fn make_unpin_impl(&self) -> TokenStream {
121121
let orig_ident = &self.orig_ident;
122122
let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
123+
let type_params: Vec<_> = self.generics.type_params().map(|t| t.ident.clone()).collect();
123124
let where_clause = &self.impl_unpin;
124125

125-
quote! {
126-
impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #where_clause {}
126+
if self.unsafe_unpin {
127+
quote! {
128+
impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #where_clause {}
129+
}
130+
} else {
131+
let struct_ident = format_ident!("__UnpinStruct{}", orig_ident);
132+
let fields: Vec<_> = self
133+
.pinned_fields
134+
.iter()
135+
.enumerate()
136+
.map(|(i, ty)| {
137+
let field_ident = format_ident!("__field{}", i);
138+
quote! {
139+
#field_ident: #ty
140+
}
141+
})
142+
.collect();
143+
144+
let fields_comma = if !fields.is_empty() { quote!(,) } else { quote!() };
145+
146+
let lifetime_fields: Vec<_> = self
147+
.generics
148+
.lifetimes()
149+
.enumerate()
150+
.map(|(i, l)| {
151+
let field_ident = format_ident!("__lifetime{}", i);
152+
quote! {
153+
#field_ident: &#l ()
154+
}
155+
})
156+
.collect();
157+
158+
let fn_ident = format_ident!("__unpin_scope_{}", orig_ident);
159+
160+
let full_generics = &self.generics;
161+
let mut full_where_clause = where_clause.clone();
162+
163+
let unpin_clause: WherePredicate = syn::parse_quote! {
164+
#struct_ident #ty_generics: ::core::marker::Unpin
165+
};
166+
167+
full_where_clause.predicates.push(unpin_clause);
168+
169+
quote! {
170+
// By writing our type inside a function scope,
171+
// we guarantee that it cannot be named by another
172+
// other code
173+
#[allow(non_snake_case)]
174+
fn #fn_ident() {
175+
176+
struct AlwaysUnpin<T: ?Sized> {
177+
val: ::core::marker::PhantomData<T>
178+
}
179+
180+
impl<T: ?Sized> ::core::marker::Unpin for AlwaysUnpin<T> {}
181+
182+
pub struct #struct_ident #full_generics #where_clause {
183+
__pin_project_use_generics: AlwaysUnpin<(#(#type_params),*)>,
184+
185+
#(#fields),* #fields_comma
186+
#(#lifetime_fields),*
187+
}
188+
189+
impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #full_where_clause {}
190+
}
191+
}
127192
}
128193
}
129194

pin-project-internal/src/pin_project/structs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ fn named(
5353
for Field { attrs, ident, ty, .. } in fields {
5454
if let Some(attr) = attrs.find_remove(PIN) {
5555
let _: Nothing = syn::parse2(attr.tokens)?;
56-
cx.push_unpin_bounds(ty);
56+
cx.push_unpin_bounds(ty.clone());
5757
let lifetime = &cx.lifetime;
5858
proj_fields.push(quote!(#ident: ::core::pin::Pin<&#lifetime mut #ty>));
5959
proj_init.push(quote!(#ident: ::core::pin::Pin::new_unchecked(&mut this.#ident)));
@@ -79,7 +79,7 @@ fn unnamed(
7979
let i = Index::from(i);
8080
if let Some(attr) = attrs.find_remove(PIN) {
8181
let _: Nothing = syn::parse2(attr.tokens)?;
82-
cx.push_unpin_bounds(ty);
82+
cx.push_unpin_bounds(ty.clone());
8383
let lifetime = &cx.lifetime;
8484
proj_fields.push(quote!(::core::pin::Pin<&#lifetime mut #ty>));
8585
proj_init.push(quote!(::core::pin::Pin::new_unchecked(&mut this.#i)));

tests/pin_project.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,27 @@ fn combine() {
238238
#[allow(unsafe_code)]
239239
unsafe impl<T: Unpin> UnsafeUnpin for Foo<T> {}
240240
}
241+
242+
#[test]
243+
// This 'allow' is unrelated to the code
244+
// generated by pin-project - it's just to
245+
// allow us to put a private enum in a public enum
246+
#[allow(private_in_public)]
247+
fn test_private_type_in_public_type() {
248+
#[pin_project]
249+
pub struct PublicStruct<T> {
250+
#[pin]
251+
inner: PrivateStruct<T>
252+
}
253+
254+
struct PrivateStruct<T>(T);
255+
256+
#[pin_project]
257+
pub enum PublicEnum {
258+
Variant(#[pin] PrivateEnum)
259+
}
260+
261+
enum PrivateEnum {
262+
OtherVariant(u8)
263+
}
264+
}

tests/ui/pin_project/proper_unpin.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// compile-fail
2+
3+
#![deny(warnings, unsafe_code)]
4+
5+
use pin_project::{pin_project, pinned_drop};
6+
use std::pin::Pin;
7+
8+
struct Inner<T> {
9+
val: T
10+
}
11+
12+
#[pin_project]
13+
struct Foo<T, U> {
14+
#[pin]
15+
inner: Inner<T>,
16+
other: U
17+
}
18+
19+
fn is_unpin<T: Unpin>() {}
20+
21+
fn bar<T, U>() {
22+
is_unpin::<Foo<T, U>>(); //~ ERROR E0277
23+
}
24+
25+
fn main() {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error[E0277]: the trait bound `T: std::marker::Unpin` is not satisfied in `__unpin_scope_Foo::__UnpinStructFoo<T, U>`
2+
--> $DIR/proper_unpin.rs:22:5
3+
|
4+
22 | is_unpin::<Foo<T, U>>(); //~ ERROR E0277
5+
| ^^^^^^^^^^^^^^^^^^^^^ within `__unpin_scope_Foo::__UnpinStructFoo<T, U>`, the trait `std::marker::Unpin` is not implemented for `T`
6+
|
7+
= help: consider adding a `where T: std::marker::Unpin` bound
8+
= note: required because it appears within the type `Inner<T>`
9+
= note: required because it appears within the type `__unpin_scope_Foo::__UnpinStructFoo<T, U>`
10+
= note: required because of the requirements on the impl of `std::marker::Unpin` for `Foo<T, U>`
11+
note: required by `is_unpin`
12+
--> $DIR/proper_unpin.rs:19:1
13+
|
14+
19 | fn is_unpin<T: Unpin>() {}
15+
| ^^^^^^^^^^^^^^^^^^^^^^^
16+
17+
error: aborting due to previous error
18+
19+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)