Skip to content

Commit

Permalink
Merge #69
Browse files Browse the repository at this point in the history
69: Add 'project_into' method to #[pin_project] types r=taiki-e a=Aaron1011

This method consumes takes a 'Pin<&mut Self>' by value, instead
of by mutable reference. This allows us to give the projection
struct/enum a longer lifetime, which is required when returning it
from a function

Fixes #65

Co-authored-by: Aaron Hill <aa1ronham@gmail.com>
Co-authored-by: Taiki Endo <te316e89@gmail.com>
  • Loading branch information
3 people authored Sep 4, 2019
2 parents 5317091 + e04660c commit 09cb58b
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 26 deletions.
26 changes: 26 additions & 0 deletions pin-project-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ use syn::parse::Nothing;
/// the field.
/// - For the other fields, makes the unpinned reference to the field.
///
/// The following methods are implemented on the original `#[pin_project]` type:
///
/// ```ignore
/// fn project(&mut Pin<&mut Self>) -> ProjectedType;
/// fn project_into(Pin<&mut Self>) -> ProjectedType;
/// ```
///
/// The `project` method takes a mutable reference to a pinned
/// type, and returns a projection struct. This is the method
/// you'll usually want to use - since it takes a mutable reference,
/// it can be called multiple times, and allows you to use
/// the original Pin type later on (e.g. to call [`Pin::set`](core::pin::Pin::set))
///
/// The `project_into` type takes a pinned type by value (consuming it),
/// and returns a projection struct. The difference between this and the `project`
/// method lies in the lifetime. While the type returned by `project` only lives
/// as long as the 'outer' mutable reference, the type returned by this method
/// lives for as long as the original Pin. This can be useful when returning a pin
/// projection from a method:
///
/// ```ignore
/// fn get_pin_mut<'a>(mut self: Pin<&'a mut Self>) -> Pin<&'a mut T> {
/// self.project_into().pinned
/// }
/// ```
///
/// ## Safety
///
/// This attribute is completely safe. In the absence of other `unsafe` code *that you write*,
Expand Down
30 changes: 18 additions & 12 deletions pin-project-internal/src/pin_project/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,33 @@ pub(super) fn parse(cx: &mut Context, mut item: ItemEnum) -> Result<TokenStream>

let (proj_variants, proj_arms) = variants(cx, &mut item)?;

let Context { proj_ident, proj_trait, orig_ident, lifetime, .. } = &cx;
let proj_ident = &cx.proj_ident;
let proj_generics = cx.proj_generics();
let proj_ty_generics = proj_generics.split_for_impl().1;
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
let where_clause = item.generics.split_for_impl().2;

let mut proj_items = quote! {
#[allow(clippy::mut_mut)]
#[allow(dead_code)]
enum #proj_ident #proj_generics #where_clause { #(#proj_variants,)* }
};
proj_items.extend(quote! {
impl #impl_generics #proj_trait #ty_generics for ::core::pin::Pin<&mut #orig_ident #ty_generics> #where_clause {
fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #where_clause {
unsafe {
match self.as_mut().get_unchecked_mut() {
#(#proj_arms,)*
}
}

let project_body = quote! {
unsafe {
match self.as_mut().get_unchecked_mut() {
#(#proj_arms,)*
}
}
});
};

let project_into_body = quote! {
unsafe {
match self.get_unchecked_mut() {
#(#proj_arms,)*
}
}
};

proj_items.extend(cx.make_trait_impl(&project_body, &project_into_body));

let mut item = item.into_token_stream();
item.extend(proj_items);
Expand Down
56 changes: 54 additions & 2 deletions pin-project-internal/src/pin_project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use syn::{

use crate::utils::{
self, crate_path, proj_ident, proj_lifetime_name, proj_trait_ident, DEFAULT_LIFETIME_NAME,
TRAIT_LIFETIME_NAME,
};

mod enums;
Expand Down Expand Up @@ -68,6 +69,9 @@ struct Context {
/// Lifetime added to projected type.
lifetime: Lifetime,

/// Lifetime on the generated projection trait
trait_lifetime: Lifetime,

/// Where-clause for conditional Unpin implementation.
impl_unpin: WhereClause,

Expand All @@ -92,6 +96,10 @@ impl Context {
proj_lifetime_name(&mut lifetime_name, &generics.params);
let lifetime = Lifetime::new(&lifetime_name, Span::call_site());

let mut trait_lifetime_name = String::from(TRAIT_LIFETIME_NAME);
proj_lifetime_name(&mut trait_lifetime_name, &generics.params);
let trait_lifetime = Lifetime::new(&trait_lifetime_name, Span::call_site());

let mut generics = generics.clone();
let mut impl_unpin = generics.make_where_clause().clone();
if let Some(unsafe_unpin) = unsafe_unpin {
Expand All @@ -111,20 +119,58 @@ impl Context {
vis: vis.clone(),
generics,
lifetime,
trait_lifetime,
impl_unpin,
pinned_fields: vec![],
unsafe_unpin: unsafe_unpin.is_some(),
pinned_drop,
})
}

/// Creates an implementation of the projection trait.
/// The provided TokenStream will be used as the body of the
/// 'project' and 'project_into' implementations
fn make_trait_impl(
&self,
project_body: &TokenStream,
project_into_body: &TokenStream,
) -> TokenStream {
let Context { proj_ident, proj_trait, orig_ident, lifetime, trait_lifetime, .. } = &self;
let proj_generics = self.proj_generics();

let project_into_generics = self.project_into_generics();

let proj_ty_generics = proj_generics.split_for_impl().1;
let (impl_generics, project_into_ty_generics, _) = project_into_generics.split_for_impl();
let (_, ty_generics, where_clause) = self.generics.split_for_impl();

quote! {
impl #impl_generics #proj_trait #project_into_ty_generics for ::core::pin::Pin<&#trait_lifetime mut #orig_ident #ty_generics> #where_clause {
fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #where_clause {
#project_body
}

fn project_into(self) -> #proj_ident #project_into_ty_generics #where_clause {
#project_into_body
}
}
}
}

/// Makes the generics of projected type from the reference of the original generics.
fn proj_generics(&self) -> Generics {
let mut generics = self.generics.clone();
utils::proj_generics(&mut generics, self.lifetime.clone());
generics
}

/// Makes the generics for the 'project_into' method
fn project_into_generics(&self) -> Generics {
let mut generics = self.generics.clone();
utils::proj_generics(&mut generics, self.trait_lifetime.clone());
generics
}

fn push_unpin_bounds(&mut self, ty: Type) {
self.pinned_fields.push(ty);
}
Expand Down Expand Up @@ -345,11 +391,17 @@ impl Context {
let proj_generics = self.proj_generics();
let proj_ty_generics = proj_generics.split_for_impl().1;

let (orig_generics, _, orig_where_clause) = self.generics.split_for_impl();
// Add trait lifetime to trait generics
let mut trait_generics = self.generics.clone();
utils::proj_generics(&mut trait_generics, self.trait_lifetime.clone());

let (trait_generics, trait_ty_generics, orig_where_clause) =
trait_generics.split_for_impl();

quote! {
trait #proj_trait #orig_generics {
trait #proj_trait #trait_generics {
fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #orig_where_clause;
fn project_into(self) -> #proj_ident #trait_ty_generics #orig_where_clause;
}
}
}
Expand Down
29 changes: 17 additions & 12 deletions pin-project-internal/src/pin_project/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,31 @@ pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result<TokenStrea
Fields::Unnamed(fields) => unnamed(cx, fields)?,
};

let Context { proj_ident, proj_trait, orig_ident, lifetime, .. } = &cx;
let proj_ident = &cx.proj_ident;
let proj_generics = cx.proj_generics();
let proj_ty_generics = proj_generics.split_for_impl().1;
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
let where_clause = item.generics.split_for_impl().2;

let mut proj_items = quote! {
#[allow(clippy::mut_mut)]
#[allow(dead_code)]
struct #proj_ident #proj_generics #where_clause #proj_fields
};
proj_items.extend(quote! {
impl #impl_generics #proj_trait #ty_generics for ::core::pin::Pin<&mut #orig_ident #ty_generics> #where_clause {
fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #where_clause {
unsafe {
let this = self.as_mut().get_unchecked_mut();
#proj_ident #proj_init
}
}

let project_body = quote! {
unsafe {
let this = self.as_mut().get_unchecked_mut();
#proj_ident #proj_init
}
};

let project_into_body = quote! {
unsafe {
let this = self.get_unchecked_mut();
#proj_ident #proj_init
}
});
};

proj_items.extend(cx.make_trait_impl(&project_body, &project_into_body));

let mut item = item.into_token_stream();
item.extend(proj_items);
Expand Down
1 change: 1 addition & 0 deletions pin-project-internal/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use syn::{
};

pub(crate) const DEFAULT_LIFETIME_NAME: &str = "'_pin";
pub(crate) const TRAIT_LIFETIME_NAME: &str = "'_outer_pin";

/// Makes the ident of projected type from the reference of the original ident.
pub(crate) fn proj_ident(ident: &Ident) -> Ident {
Expand Down
33 changes: 33 additions & 0 deletions tests/pin_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,36 @@ fn test_private_type_in_public_type() {
OtherVariant(u8),
}
}

#[test]
fn test_lifetime_project() {
#[pin_project::pin_project]
struct Struct<T, U> {
#[pin]
pinned: T,
unpinned: U,
}

#[pin_project::pin_project]
enum Enum<T, U> {
Variant {
#[pin]
pinned: T,
unpinned: U,
},
}

impl<T, U> Struct<T, U> {
fn get_pin_mut<'a>(self: Pin<&'a mut Self>) -> Pin<&'a mut T> {
self.project_into().pinned
}
}

impl<T, U> Enum<T, U> {
fn get_pin_mut<'a>(self: Pin<&'a mut Self>) -> Pin<&'a mut T> {
match self.project_into() {
__EnumProjection::Variant { pinned, .. } => pinned,
}
}
}
}

0 comments on commit 09cb58b

Please sign in to comment.