Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow naming the projected types #202

Merged
merged 1 commit into from
May 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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