Skip to content

Commit

Permalink
Merge #202
Browse files Browse the repository at this point in the history
202: Allow naming the projected types r=taiki-e a=taiki-e

By passing an argument with the same name as the method to the attribute, you can name the projection type returned from the method:

```rust
#[pin_project(
    Replace,
    project = StructProj,
    project_ref = StructProjRef,
    project_replace = StructProjOwn,
)]
struct Struct<T> {
    #[pin]
    field: T,
}

let mut x = Struct { field: 0 };
let StructProj { field } = Pin::new(&mut x).project();
let StructProjRef { field } = Pin::new(&x).project_ref();
let StructProjOwn { field } = Pin::new(&mut x).project_replace(Struct { field: 1 });
```

cc #43
Closes #124

TODO: 
  * [x] add tests
  * [x] write docs 
  * [x] separate unrelated changes; done in #214
  * [x] msrv (`unrestricted_attribute_tokens` requires 1.34); done in #219 


Co-authored-by: Taiki Endo <[email protected]>
  • Loading branch information
bors[bot] and taiki-e authored May 18, 2020
2 parents 23997ca + 5765137 commit 3de876c
Show file tree
Hide file tree
Showing 17 changed files with 744 additions and 95 deletions.
15 changes: 7 additions & 8 deletions examples/enum-default-expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//
// use pin_project::pin_project;
//
// #[pin_project]
// #[pin_project(project = EnumProj)]
// enum Enum<T, U> {
// Pinned(#[pin] T),
// Unpinned(U),
Expand All @@ -24,11 +24,10 @@ enum Enum<T, U> {
Unpinned(U),
}

#[doc(hidden)]
#[allow(clippy::mut_mut)] // This lint warns `&mut &mut <ty>`.
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)]
enum __EnumProjection<'pin, T, U>
enum EnumProj<'pin, T, U>
where
Enum<T, U>: 'pin,
{
Expand All @@ -53,13 +52,13 @@ const __SCOPE_Enum: () = {
impl<T, U> Enum<T, U> {
fn project<'pin>(
self: ::pin_project::__reexport::pin::Pin<&'pin mut Self>,
) -> __EnumProjection<'pin, T, U> {
) -> EnumProj<'pin, T, U> {
unsafe {
match self.get_unchecked_mut() {
Enum::Pinned(_0) => __EnumProjection::Pinned(
::pin_project::__reexport::pin::Pin::new_unchecked(_0),
),
Enum::Unpinned(_0) => __EnumProjection::Unpinned(_0),
Enum::Pinned(_0) => {
EnumProj::Pinned(::pin_project::__reexport::pin::Pin::new_unchecked(_0))
}
Enum::Unpinned(_0) => EnumProj::Unpinned(_0),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/enum-default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use pin_project::pin_project;

#[pin_project]
#[pin_project(project = EnumProj)]
enum Enum<T, U> {
Pinned(#[pin] T),
Unpinned(U),
Expand Down
48 changes: 32 additions & 16 deletions pin-project-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ use crate::utils::{Immutable, Mutable, Owned};
/// # }
/// ```
///
/// By passing an argument with the same name as the method to the attribute,
/// you can name the projection type returned from the method:
///
/// ```rust
/// use pin_project::pin_project;
/// use std::pin::Pin;
///
/// #[pin_project(project = EnumProj)]
/// enum Enum<T> {
/// Variant(#[pin] T),
/// }
///
/// fn func<T>(x: Pin<&mut Enum<T>>) {
/// match x.project() {
/// EnumProj::Variant(y) => {
/// let _: Pin<&mut T> = y;
/// }
/// }
/// }
/// ```
///
/// The visibility of the projected type and projection method is based on the original type.
/// However, if the visibility of the original type is `pub`, the visibility of the projected type
/// and the projection method is downgraded to `pub(crate)`.
Expand All @@ -67,7 +88,7 @@ use crate::utils::{Immutable, Mutable, Owned};
/// To enforce this, this attribute will automatically generate an [`Unpin`] implementation
/// for you, which will require that all structurally pinned fields be [`Unpin`]
/// If you wish to provide an manual [`Unpin`] impl, you can do so via the
/// `UnsafeUnpin` argument.
/// [`UnsafeUnpin`][unsafe-unpin] argument.
///
/// 2. The destructor of the struct must not move structural fields out of its argument.
///
Expand All @@ -84,8 +105,8 @@ use crate::utils::{Immutable, Mutable, Owned};
/// then apply to your type, causing a compile-time error due to
/// the conflict with the second impl.
///
/// If you wish to provide a custom [`Drop`] impl, you can annotate a function
/// with [`#[pinned_drop]`][pinned-drop]. This function takes a pinned version of your struct -
/// If you wish to provide a custom [`Drop`] impl, you can annotate an impl
/// with [`#[pinned_drop]`][pinned-drop]. This impl takes a pinned version of your struct -
/// that is, [`Pin`]`<&mut MyStruct>` where `MyStruct` is the type of your struct.
///
/// You can call `project()` on this type as usual, along with any other
Expand Down Expand Up @@ -184,35 +205,30 @@ use crate::utils::{Immutable, Mutable, Owned};
///
/// [Enums](https://doc.rust-lang.org/reference/items/enumerations.html):
///
/// `#[pin_project]` supports enums, but to use it, you need to use with the
/// [`project`] attribute.
///
/// The attribute at the expression position is not stable, so you need to use
/// a dummy [`project`] attribute for the function.
/// `#[pin_project]` supports enums, but to use it, you need to name the
/// projection type returned from the method or to use with the [`project`] attribute.
///
/// ```rust
/// use pin_project::{pin_project, project};
/// use pin_project::pin_project;
/// use std::pin::Pin;
///
/// #[pin_project]
/// #[pin_project(project = EnumProj)]
/// enum Enum<T, U> {
/// Tuple(#[pin] T),
/// Struct { field: U },
/// Unit,
/// }
///
/// impl<T, U> Enum<T, U> {
/// #[project] // Nightly does not need a dummy attribute to the function.
/// fn method(self: Pin<&mut Self>) {
/// #[project]
/// match self.project() {
/// Enum::Tuple(x) => {
/// EnumProj::Tuple(x) => {
/// let _: Pin<&mut T> = x;
/// }
/// Enum::Struct { field } => {
/// EnumProj::Struct { field } => {
/// let _: &mut U = field;
/// }
/// Enum::Unit => {}
/// EnumProj::Unit => {}
/// }
/// }
/// }
Expand Down Expand Up @@ -410,7 +426,7 @@ use crate::utils::{Immutable, Mutable, Owned};
/// [repr-packed]: https://doc.rust-lang.org/nomicon/other-reprs.html#reprpacked
/// [pin-projection]: https://doc.rust-lang.org/nightly/std/pin/index.html#projections-and-structural-pinning
/// [undefined-behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
/// [unsafe-unpin]: ./attr.pin_project.html#pinned_drop
/// [unsafe-unpin]: ./attr.pin_project.html#unsafeunpin
#[proc_macro_attribute]
pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream {
pin_project::attribute(&args.into(), input.into()).into()
Expand Down
77 changes: 66 additions & 11 deletions pin-project-internal/src/pin_project/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ struct Args {
replace: Option<Span>,
/// `UnsafeUnpin` or `!Unpin` argument.
unpin_impl: UnpinImpl,
/// `project = <ident>`.
project: Option<Ident>,
/// `project_ref = <ident>`.
project_ref: Option<Ident>,
/// `project_replace = <ident>`.
project_replace: Option<Ident>,
}

const DUPLICATE_PIN: &str = "duplicate #[pin] attribute";
Expand Down Expand Up @@ -187,6 +193,9 @@ impl Parse for Args {
let mut replace = None;
let mut unsafe_unpin = None;
let mut not_unpin = None;
let mut project = None;
let mut project_ref = None;
let mut project_replace: Option<(Span, Ident)> = None;
while !input.is_empty() {
if input.peek(token::Bang) {
let t: token::Bang = input.parse()?;
Expand All @@ -201,6 +210,18 @@ impl Parse for Args {
"PinnedDrop" => update(&mut pinned_drop, token.span(), &token)?,
"Replace" => update(&mut replace, token.span(), &token)?,
"UnsafeUnpin" => update(&mut unsafe_unpin, token.span(), &token)?,
"project" => {
let _: token::Eq = input.parse()?;
update(&mut project, input.parse()?, &token)?;
}
"project_ref" => {
let _: token::Eq = input.parse()?;
update(&mut project_ref, input.parse()?, &token)?;
}
"project_replace" => {
let _: token::Eq = input.parse()?;
update(&mut project_replace, (token.span(), input.parse()?), &token)?;
}
_ => return Err(error!(token, "unexpected argument: {}", token)),
}
}
Expand Down Expand Up @@ -228,7 +249,21 @@ impl Parse for Args {
(None, Some(span)) => UnpinImpl::Negative(span.span),
};

Ok(Self { pinned_drop, replace, unpin_impl })
if let (Some((span, _)), None) = (&project_replace, replace) {
Err(Error::new(
*span,
"`project_replace` argument can only be used together with `Replace` argument",
))
} else {
Ok(Self {
pinned_drop,
replace,
unpin_impl,
project,
project_ref,
project_replace: project_replace.map(|(_, i)| i),
})
}
}
}

Expand Down Expand Up @@ -294,6 +329,12 @@ struct Context<'a> {
replace: Option<Span>,
/// `UnsafeUnpin` or `!Unpin` argument.
unpin_impl: UnpinImpl,
/// `project` argument.
project: bool,
/// `project_ref` argument.
project_ref: bool,
/// `project_replace` argument.
project_replace: bool,
}

#[derive(Clone, Copy)]
Expand All @@ -312,7 +353,8 @@ impl<'a> Context<'a> {
ident: &'a Ident,
generics: &'a mut Generics,
) -> Result<Self> {
let Args { pinned_drop, replace, unpin_impl } = Args::get(attrs)?;
let Args { pinned_drop, unpin_impl, replace, project, project_ref, project_replace } =
Args::get(attrs)?;

let ty_generics = generics.split_for_impl().1;
let self_ty = syn::parse_quote!(#ident #ty_generics);
Expand All @@ -339,11 +381,14 @@ impl<'a> Context<'a> {
pinned_drop,
replace,
unpin_impl,
project: project.is_some(),
project_ref: project_ref.is_some(),
project_replace: project_replace.is_some(),
proj: ProjectedType {
vis: determine_visibility(vis),
mut_ident: Mutable.proj_ident(ident),
ref_ident: Immutable.proj_ident(ident),
own_ident: Owned.proj_ident(ident),
mut_ident: project.unwrap_or_else(|| Mutable.proj_ident(ident)),
ref_ident: project_ref.unwrap_or_else(|| Immutable.proj_ident(ident)),
own_ident: project_replace.unwrap_or_else(|| Owned.proj_ident(ident)),
lifetime,
generics: proj_generics,
where_clause,
Expand Down Expand Up @@ -398,21 +443,26 @@ impl<'a> Context<'a> {
Fields::Unit => unreachable!(),
};

// If the user gave it a name, it should appear in the document.
let doc_attr = quote!(#[doc(hidden)]);
let doc_proj = if self.project { None } else { Some(&doc_attr) };
let doc_proj_ref = if self.project_ref { None } else { Some(&doc_attr) };
let doc_proj_own = if self.project_replace { None } else { Some(&doc_attr) };
let mut proj_items = quote! {
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj
#[allow(clippy::mut_mut)] // This lint warns `&mut &mut <ty>`.
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis struct #proj_ident #proj_generics #where_clause_fields
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj_ref
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis struct #proj_ref_ident #proj_generics #where_clause_ref_fields
};
if self.replace.is_some() {
// Currently, using quote_spanned here does not seem to have any effect on the diagnostics.
proj_items.extend(quote! {
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj_own
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis struct #proj_own_ident #orig_generics #where_clause_own_fields
Expand Down Expand Up @@ -482,15 +532,20 @@ impl<'a> Context<'a> {
let proj_generics = &self.proj.generics;
let where_clause = &self.proj.where_clause;

// If the user gave it a name, it should appear in the document.
let doc_attr = quote!(#[doc(hidden)]);
let doc_proj = if self.project { None } else { Some(&doc_attr) };
let doc_proj_ref = if self.project_ref { None } else { Some(&doc_attr) };
let doc_proj_own = if self.project_replace { None } else { Some(&doc_attr) };
let mut proj_items = quote! {
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj
#[allow(clippy::mut_mut)] // This lint warns `&mut &mut <ty>`.
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis enum #proj_ident #proj_generics #where_clause {
#proj_variants
}
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj_ref
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis enum #proj_ref_ident #proj_generics #where_clause {
Expand All @@ -500,7 +555,7 @@ impl<'a> Context<'a> {
if self.replace.is_some() {
// Currently, using quote_spanned here does not seem to have any effect on the diagnostics.
proj_items.extend(quote! {
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj_own
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis enum #proj_own_ident #orig_generics #orig_where_clause {
Expand Down
Loading

0 comments on commit 3de876c

Please sign in to comment.