Skip to content

Commit

Permalink
Allow naming the projected types
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed May 10, 2020
1 parent 7f0d866 commit fe90f03
Show file tree
Hide file tree
Showing 14 changed files with 704 additions and 70 deletions.
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
155 changes: 155 additions & 0 deletions tests/expand/tests/expand/naming-enum.expanded.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use pin_project::pin_project;
# [ pin ( __private ( Replace , project = Proj , project_ref = ProjRef , project_replace = ProjOwn ) ) ]
enum Enum<T, U> {
Struct {
#[pin]
pinned: T,
unpinned: U,
},
Tuple(#[pin] T, U),
Unit,
}
#[allow(clippy::mut_mut)]
#[allow(dead_code)]
#[allow(single_use_lifetimes)]
enum Proj<'pin, T, U>
where
Enum<T, U>: 'pin,
{
Struct {
pinned: ::pin_project::__reexport::pin::Pin<&'pin mut (T)>,
unpinned: &'pin mut (U),
},
Tuple(
::pin_project::__reexport::pin::Pin<&'pin mut (T)>,
&'pin mut (U),
),
Unit,
}
#[allow(dead_code)]
#[allow(single_use_lifetimes)]
enum ProjRef<'pin, T, U>
where
Enum<T, U>: 'pin,
{
Struct {
pinned: ::pin_project::__reexport::pin::Pin<&'pin (T)>,
unpinned: &'pin (U),
},
Tuple(::pin_project::__reexport::pin::Pin<&'pin (T)>, &'pin (U)),
Unit,
}
#[allow(dead_code)]
#[allow(single_use_lifetimes)]
enum ProjOwn<T, U> {
Struct {
pinned: ::pin_project::__reexport::marker::PhantomData<T>,
unpinned: U,
},
Tuple(::pin_project::__reexport::marker::PhantomData<T>, U),
Unit,
}
#[doc(hidden)]
#[allow(non_upper_case_globals)]
#[allow(single_use_lifetimes)]
const __SCOPE_Enum: () = {
impl<T, U> Enum<T, U> {
fn project<'pin>(
self: ::pin_project::__reexport::pin::Pin<&'pin mut Self>,
) -> Proj<'pin, T, U> {
unsafe {
match self.get_unchecked_mut() {
Enum::Struct { pinned, unpinned } => Proj::Struct {
pinned: ::pin_project::__reexport::pin::Pin::new_unchecked(pinned),
unpinned,
},
Enum::Tuple(_0, _1) => {
Proj::Tuple(::pin_project::__reexport::pin::Pin::new_unchecked(_0), _1)
}
Enum::Unit => Proj::Unit,
}
}
}
fn project_ref<'pin>(
self: ::pin_project::__reexport::pin::Pin<&'pin Self>,
) -> ProjRef<'pin, T, U> {
unsafe {
match self.get_ref() {
Enum::Struct { pinned, unpinned } => ProjRef::Struct {
pinned: ::pin_project::__reexport::pin::Pin::new_unchecked(pinned),
unpinned,
},
Enum::Tuple(_0, _1) => {
ProjRef::Tuple(::pin_project::__reexport::pin::Pin::new_unchecked(_0), _1)
}
Enum::Unit => ProjRef::Unit,
}
}
}
fn project_replace(
self: ::pin_project::__reexport::pin::Pin<&mut Self>,
__replacement: Self,
) -> ProjOwn<T, U> {
unsafe {
let __self_ptr: *mut Self = self.get_unchecked_mut();
match &mut *__self_ptr {
Enum::Struct { pinned, unpinned } => {
let __result = ProjOwn::Struct {
pinned: ::pin_project::__reexport::marker::PhantomData,
unpinned: ::pin_project::__reexport::ptr::read(unpinned),
};
let __guard = ::pin_project::__private::UnsafeOverwriteGuard {
target: __self_ptr,
value: ::pin_project::__reexport::mem::ManuallyDrop::new(__replacement),
};
{
let __guard = ::pin_project::__private::UnsafeDropInPlaceGuard(pinned);
}
__result
}
Enum::Tuple(_0, _1) => {
let __result = ProjOwn::Tuple(
::pin_project::__reexport::marker::PhantomData,
::pin_project::__reexport::ptr::read(_1),
);
let __guard = ::pin_project::__private::UnsafeOverwriteGuard {
target: __self_ptr,
value: ::pin_project::__reexport::mem::ManuallyDrop::new(__replacement),
};
{
let __guard = ::pin_project::__private::UnsafeDropInPlaceGuard(_0);
}
__result
}
Enum::Unit => {
let __result = ProjOwn::Unit;
let __guard = ::pin_project::__private::UnsafeOverwriteGuard {
target: __self_ptr,
value: ::pin_project::__reexport::mem::ManuallyDrop::new(__replacement),
};
{}
__result
}
}
}
}
}
struct __Enum<'pin, T, U> {
__pin_project_use_generics: ::pin_project::__private::AlwaysUnpin<'pin, (T, U)>,
__field0: T,
__field1: T,
}
impl<'pin, T, U> ::pin_project::__reexport::marker::Unpin for Enum<T, U> where
__Enum<'pin, T, U>: ::pin_project::__reexport::marker::Unpin
{
}
unsafe impl<T, U> ::pin_project::UnsafeUnpin for Enum<T, U> {}
trait EnumMustNotImplDrop {}
#[allow(clippy::drop_bounds)]
impl<T: ::pin_project::__reexport::ops::Drop> EnumMustNotImplDrop for T {}
impl<T, U> EnumMustNotImplDrop for Enum<T, U> {}
impl<T, U> ::pin_project::__private::PinnedDrop for Enum<T, U> {
unsafe fn drop(self: ::pin_project::__reexport::pin::Pin<&mut Self>) {}
}
};
fn main() {}
14 changes: 14 additions & 0 deletions tests/expand/tests/expand/naming-enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use pin_project::pin_project;

#[pin_project(Replace, project = Proj, project_ref = ProjRef, project_replace = ProjOwn)]
enum Enum<T, U> {
Struct {
#[pin]
pinned: T,
unpinned: U,
},
Tuple(#[pin] T, U),
Unit,
}

fn main() {}
Loading

0 comments on commit fe90f03

Please sign in to comment.