Skip to content

Commit

Permalink
Merge #270
Browse files Browse the repository at this point in the history
270: Optimize code generation when used on enums r=taiki-e a=taiki-e

When `#[pin_project]` is used on enums, only named projection types and methods are generated because there is no way to access variants of projected types without naming it. (When `#[pin_project]` is used on structs, both methods are always generated.)

Addresses #268 (comment)

Co-authored-by: Taiki Endo <te316e89@gmail.com>
  • Loading branch information
bors[bot] and taiki-e authored Sep 10, 2020
2 parents 2147bf6 + 060073a commit 58b51c0
Show file tree
Hide file tree
Showing 31 changed files with 695 additions and 557 deletions.
26 changes: 4 additions & 22 deletions examples/enum-default-expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,10 @@ where
#[allow(single_use_lifetimes)]
#[allow(clippy::used_underscore_binding)]
const _: () = {
#[allow(dead_code)]
#[allow(single_use_lifetimes)]
#[allow(clippy::type_repetition_in_bounds)]
enum __EnumProjectionRef<'pin, T, U>
where
Enum<T, U>: 'pin,
{
Pinned(::pin_project::__private::Pin<&'pin (T)>),
Unpinned(&'pin (U)),
}
// When `#[pin_project]` is used on enums, only named projection types and
// methods are generated because there is no way to access variants of
// projected types without naming it.
// (When `#[pin_project]` is used on structs, both methods are always generated.)

impl<T, U> Enum<T, U> {
fn project<'pin>(
Expand All @@ -65,18 +59,6 @@ const _: () = {
}
}
}
fn project_ref<'pin>(
self: ::pin_project::__private::Pin<&'pin Self>,
) -> __EnumProjectionRef<'pin, T, U> {
unsafe {
match self.get_ref() {
Enum::Pinned(_0) => __EnumProjectionRef::Pinned(
::pin_project::__private::Pin::new_unchecked(_0),
),
Enum::Unpinned(_0) => __EnumProjectionRef::Unpinned(_0),
}
}
}
}

// Automatically create the appropriate conditional `Unpin` implementation.
Expand Down
57 changes: 45 additions & 12 deletions pin-project-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,30 +50,37 @@ use proc_macro::TokenStream;
/// ```
///
/// By passing an argument with the same name as the method to the attribute,
/// you can name the projection type returned from the method:
/// you can name the projection type returned from the method. This allows you
/// to use pattern matching on the projected types.
///
/// ```rust
/// use pin_project::pin_project;
/// use std::pin::Pin;
///
/// #[pin_project(project = StructProj)]
/// struct Struct<T> {
/// #[pin]
/// field: T,
/// # use pin_project::pin_project;
/// # use std::pin::Pin;
/// #[pin_project(project = EnumProj)]
/// enum Enum<T> {
/// Variant(#[pin] T),
/// }
///
/// impl<T> Struct<T> {
/// impl<T> Enum<T> {
/// fn method(self: Pin<&mut Self>) {
/// let this: StructProj<'_, T> = self.project();
/// let StructProj { field } = this;
/// let _: Pin<&mut T> = field;
/// let this: EnumProj<'_, T> = self.project();
/// match this {
/// EnumProj::Variant(x) => {
/// let _: Pin<&mut T> = x;
/// }
/// }
/// }
/// }
/// ```
///
/// Note that the projection types returned by `project` and `project_ref` have
/// an additional lifetime at the beginning of generics.
///
/// ```text
/// let this: EnumProj<'_, T> = self.project();
/// ^^
/// ```
///
/// 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
Expand Down Expand Up @@ -221,6 +228,32 @@ use proc_macro::TokenStream;
/// }
/// ```
///
/// When `#[pin_project]` is used on enums, only named projection types and
/// methods are generated because there is no way to access variants of
/// projected types without naming it.
/// For example, in the above example, only `project()` method is generated,
/// and `project_ref()` method is not generated.
/// (When `#[pin_project]` is used on structs, both methods are always generated.)
///
/// ```rust,compile_fail,E0599
/// # use pin_project::pin_project;
/// # use std::pin::Pin;
/// #
/// # #[pin_project(project = EnumProj)]
/// # enum Enum<T, U> {
/// # Tuple(#[pin] T),
/// # Struct { field: U },
/// # Unit,
/// # }
/// #
/// impl<T, U> Enum<T, U> {
/// fn call_project_ref(self: Pin<&Self>) {
/// let _this = self.project_ref();
/// //~^ ERROR no method named `project_ref` found for struct `Pin<&Enum<T, U>>` in the current scope
/// }
/// }
/// ```
///
/// If you want to call the `project()` method multiple times or later use the
/// original [`Pin`] type, it needs to use [`.as_mut()`][`Pin::as_mut`] to avoid
/// consuming the [`Pin`].
Expand Down
100 changes: 68 additions & 32 deletions pin-project-internal/src/pin_project/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,10 @@ impl<'a> Context<'a> {
let Self #proj_pat = &mut *__self_ptr;
#proj_own_body
};
generate.extend(false, self.make_proj_impl(&proj_mut_body, &proj_ref_body, &proj_own_body));
generate.extend(
false,
self.make_proj_impl(false, &proj_mut_body, &proj_ref_body, &proj_own_body),
);

Ok(())
}
Expand All @@ -620,6 +623,13 @@ impl<'a> Context<'a> {
DataEnum { brace_token, variants, .. }: &DataEnum,
generate: &mut GenerateTokens,
) -> Result<()> {
if let ProjReplace::Unnamed { span } = &self.project_replace {
return Err(Error::new(
*span,
"`project_replace` argument requires a value when used on enums",
));
}

validate_enum(*brace_token, variants)?;

let ProjectedVariants {
Expand All @@ -641,20 +651,24 @@ impl<'a> Context<'a> {
let proj_where_clause = &self.proj.where_clause;

let (proj_attrs, proj_ref_attrs, proj_own_attrs) = self.proj_attrs();
generate.extend(self.project, quote! {
#proj_attrs
#vis enum #proj_ident #proj_generics #proj_where_clause {
#proj_variants
}
});
generate.extend(self.project_ref, quote! {
#proj_ref_attrs
#vis enum #proj_ref_ident #proj_generics #proj_where_clause {
#proj_ref_variants
}
});
if self.project_replace.span().is_some() {
generate.extend(self.project_replace.ident().is_some(), quote! {
if self.project {
generate.extend(true, quote! {
#proj_attrs
#vis enum #proj_ident #proj_generics #proj_where_clause {
#proj_variants
}
});
}
if self.project_ref {
generate.extend(true, quote! {
#proj_ref_attrs
#vis enum #proj_ref_ident #proj_generics #proj_where_clause {
#proj_ref_variants
}
});
}
if self.project_replace.ident().is_some() {
generate.extend(true, quote! {
#proj_own_attrs
#vis enum #proj_own_ident #orig_generics #orig_where_clause {
#proj_own_variants
Expand All @@ -678,7 +692,10 @@ impl<'a> Context<'a> {
#proj_own_arms
}
};
generate.extend(false, self.make_proj_impl(&proj_mut_body, &proj_ref_body, &proj_own_body));
generate.extend(
false,
self.make_proj_impl(true, &proj_mut_body, &proj_ref_body, &proj_own_body),
);

Ok(())
}
Expand Down Expand Up @@ -1104,6 +1121,7 @@ impl<'a> Context<'a> {
/// Creates an implementation of the projection method.
fn make_proj_impl(
&self,
is_enum: bool,
proj_body: &TokenStream,
proj_ref_body: &TokenStream,
proj_own_body: &TokenStream,
Expand All @@ -1119,7 +1137,25 @@ impl<'a> Context<'a> {
let proj_ty_generics = self.proj.generics.split_for_impl().1;
let (impl_generics, ty_generics, where_clause) = self.orig.generics.split_for_impl();

let replace_impl = self.project_replace.span().map(|span| {
let mut project = Some(quote! {
#vis fn project<#lifetime>(
self: ::pin_project::__private::Pin<&#lifetime mut Self>,
) -> #proj_ident #proj_ty_generics {
unsafe {
#proj_body
}
}
});
let mut project_ref = Some(quote! {
#vis fn project_ref<#lifetime>(
self: ::pin_project::__private::Pin<&#lifetime Self>,
) -> #proj_ref_ident #proj_ty_generics {
unsafe {
#proj_ref_body
}
}
});
let mut project_replace = self.project_replace.span().map(|span| {
// For interoperability with `forbid(unsafe_code)`, `unsafe` token should be
// call-site span.
let unsafety = <Token![unsafe]>::default();
Expand All @@ -1135,23 +1171,23 @@ impl<'a> Context<'a> {
}
});

if is_enum {
if !self.project {
project = None;
}
if !self.project_ref {
project_ref = None;
}
if self.project_replace.ident().is_none() {
project_replace = None;
}
}

quote! {
impl #impl_generics #orig_ident #ty_generics #where_clause {
#vis fn project<#lifetime>(
self: ::pin_project::__private::Pin<&#lifetime mut Self>,
) -> #proj_ident #proj_ty_generics {
unsafe {
#proj_body
}
}
#vis fn project_ref<#lifetime>(
self: ::pin_project::__private::Pin<&#lifetime Self>,
) -> #proj_ref_ident #proj_ty_generics {
unsafe {
#proj_ref_body
}
}
#replace_impl
#project
#project_ref
#project_replace
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions tests/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ fn cfg() {

// enums

#[pin_project(project_replace)]
#[pin_project(
project = VariantProj,
project_ref = VariantProjRef,
project_replace = VariantProjOwn,
)]
enum Variant {
#[cfg(target_os = "linux")]
Inner(#[pin] Linux),
Expand All @@ -110,7 +114,11 @@ fn cfg() {
#[cfg(not(target_os = "linux"))]
let _x = Variant::Other(Other);

#[pin_project(project_replace)]
#[pin_project(
project = FieldProj,
project_ref = FieldProjRef,
project_replace = FieldProjOwn,
)]
enum Field {
SameName {
#[cfg(target_os = "linux")]
Expand Down
Loading

0 comments on commit 58b51c0

Please sign in to comment.