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

Optimize code generation when used on enums #270

Merged
merged 1 commit into from
Sep 10, 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
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