Revision control
Copy as Markdown
Other Tools
use proc_macro2::{Delimiter, Group, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::{
parse_quote, punctuated::Punctuated, token, visit_mut::VisitMut, Attribute, Data, DataEnum,
DeriveInput, Error, Field, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident, Index,
Lifetime, LifetimeParam, Meta, Result, Token, Type, Variant, Visibility, WhereClause,
};
use super::{
args::{parse_args, Args, ProjReplace, UnpinImpl},
PIN,
};
use crate::utils::{
determine_lifetime_name, determine_visibility, insert_lifetime_and_bound, ReplaceReceiver,
SliceExt, Variants,
};
pub(super) fn parse_derive(input: TokenStream) -> Result<TokenStream> {
let mut input: DeriveInput = syn::parse2(input)?;
let mut cx;
let mut generate = GenerateTokens::default();
let ident = &input.ident;
let ty_generics = input.generics.split_for_impl().1;
let self_ty = parse_quote!(#ident #ty_generics);
let mut visitor = ReplaceReceiver(&self_ty);
visitor.visit_generics_mut(&mut input.generics);
visitor.visit_data_mut(&mut input.data);
match &input.data {
Data::Struct(data) => {
cx = Context::new(&input.attrs, &input.vis, ident, &mut input.generics, Struct)?;
parse_struct(&mut cx, &data.fields, &mut generate)?;
}
Data::Enum(data) => {
cx = Context::new(&input.attrs, &input.vis, ident, &mut input.generics, Enum)?;
parse_enum(&mut cx, data, &mut generate)?;
}
Data::Union(_) => {
bail!(input, "#[pin_project] attribute may only be used on structs or enums");
}
}
Ok(generate.into_tokens(&cx))
}
#[derive(Default)]
struct GenerateTokens {
exposed: TokenStream,
scoped: TokenStream,
}
impl GenerateTokens {
fn extend(&mut self, expose: bool, tokens: TokenStream) {
if expose {
self.exposed.extend(tokens);
} else {
self.scoped.extend(tokens);
}
}
fn into_tokens(self, cx: &Context<'_>) -> TokenStream {
let mut tokens = self.exposed;
let scoped = self.scoped;
let unpin_impl = make_unpin_impl(cx);
let drop_impl = make_drop_impl(cx);
let allowed_lints = global_allowed_lints();
tokens.extend(quote! {
// All items except projected types are generated inside a `const` scope.
// This makes it impossible for user code to refer to these types.
// However, this prevents Rustdoc from displaying docs for any
// of our types. In particular, users cannot see the
// automatically generated `Unpin` impl for the '__UnpinStruct' types
//
// Previously, we provided a flag to correctly document the
// automatically generated `Unpin` impl by using def-site hygiene,
// but it is now removed.
//
// Refs:
#allowed_lints
#[allow(unused_qualifications)]
#[allow(clippy::semicolon_if_nothing_returned)]
#[allow(clippy::use_self)]
#[allow(clippy::used_underscore_binding)]
const _: () = {
#[allow(unused_extern_crates)]
extern crate pin_project as _pin_project;
#scoped
#unpin_impl
#drop_impl
};
});
tokens
}
}
/// Returns attributes that should be applied to all generated code.
fn global_allowed_lints() -> TokenStream {
quote! {
#[allow(box_pointers)] // This lint warns use of the `Box` type.
#[allow(deprecated)]
#[allow(unreachable_pub)] // This lint warns `pub` field in private struct.
#[allow(unused_tuple_struct_fields)]
// This lint warns of `clippy::*` generated by external macros.
// We allow this lint for compatibility with older compilers.
#[allow(clippy::unknown_clippy_lints)]
#[allow(clippy::pattern_type_mismatch)]
#[allow(clippy::redundant_pub_crate)] // This lint warns `pub(crate)` field in private struct.
}
}
/// Returns attributes used on projected types.
fn proj_allowed_lints(cx: &Context<'_>) -> (TokenStream, TokenStream, TokenStream) {
let large_enum_variant = if cx.kind == Enum {
Some(quote! {
#[allow(variant_size_differences)]
#[allow(clippy::large_enum_variant)]
})
} else {
None
};
let global_allowed_lints = global_allowed_lints();
let proj_mut_allowed_lints = if cx.project { Some(&global_allowed_lints) } else { None };
let proj_mut = quote! {
#proj_mut_allowed_lints
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(clippy::mut_mut)] // This lint warns `&mut &mut <ty>`.
};
let proj_ref_allowed_lints = if cx.project_ref { Some(&global_allowed_lints) } else { None };
let proj_ref = quote! {
#proj_ref_allowed_lints
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(clippy::ref_option_ref)] // This lint warns `&Option<&<ty>>`.
};
let proj_own_allowed_lints =
if cx.project_replace.ident().is_some() { Some(&global_allowed_lints) } else { None };
let proj_own = quote! {
#proj_own_allowed_lints
#[allow(dead_code)] // This lint warns unused fields/variants.
#large_enum_variant
};
(proj_mut, proj_ref, proj_own)
}
struct Context<'a> {
/// The original type.
orig: OriginalType<'a>,
/// The projected types.
proj: ProjectedType,
/// Types of the pinned fields.
pinned_fields: Vec<&'a Type>,
/// Kind of the original type: struct or enum
kind: TypeKind,
/// `PinnedDrop` argument.
pinned_drop: Option<Span>,
/// `UnsafeUnpin` or `!Unpin` argument.
unpin_impl: UnpinImpl,
/// `project` argument.
project: bool,
/// `project_ref` argument.
project_ref: bool,
/// `project_replace [= <ident>]` argument.
project_replace: ProjReplace,
}
impl<'a> Context<'a> {
fn new(
attrs: &'a [Attribute],
vis: &'a Visibility,
ident: &'a Ident,
generics: &'a mut Generics,
kind: TypeKind,
) -> Result<Self> {
let Args { pinned_drop, unpin_impl, project, project_ref, project_replace } =
parse_args(attrs)?;
if let Some(name) = [project.as_ref(), project_ref.as_ref(), project_replace.ident()]
.iter()
.filter_map(Option::as_ref)
.find(|name| **name == ident)
{
bail!(name, "name `{}` is the same as the original type name", name);
}
let mut lifetime_name = String::from("'pin");
determine_lifetime_name(&mut lifetime_name, generics);
let lifetime = Lifetime::new(&lifetime_name, Span::call_site());
let ty_generics = generics.split_for_impl().1;
let ty_generics_as_generics = parse_quote!(#ty_generics);
let mut proj_generics = generics.clone();
let pred = insert_lifetime_and_bound(
&mut proj_generics,
lifetime.clone(),
&ty_generics_as_generics,
ident,
);
let mut where_clause = generics.make_where_clause().clone();
where_clause.predicates.push(pred);
let own_ident = project_replace
.ident()
.cloned()
.unwrap_or_else(|| format_ident!("__{}ProjectionOwned", ident));
Ok(Self {
kind,
pinned_drop,
unpin_impl,
project: project.is_some(),
project_ref: project_ref.is_some(),
project_replace,
proj: ProjectedType {
vis: determine_visibility(vis),
mut_ident: project.unwrap_or_else(|| format_ident!("__{}Projection", ident)),
ref_ident: project_ref.unwrap_or_else(|| format_ident!("__{}ProjectionRef", ident)),
own_ident,
lifetime,
generics: proj_generics,
where_clause,
},
orig: OriginalType { attrs, vis, ident, generics },
pinned_fields: Vec::new(),
})
}
}
#[derive(Copy, Clone, PartialEq)]
enum TypeKind {
Enum,
Struct,
}
use TypeKind::{Enum, Struct};
struct OriginalType<'a> {
/// Attributes of the original type.
attrs: &'a [Attribute],
/// Visibility of the original type.
vis: &'a Visibility,
/// Name of the original type.
ident: &'a Ident,
/// Generics of the original type.
generics: &'a Generics,
}
struct ProjectedType {
/// Visibility of the projected types.
vis: Visibility,
/// Name of the projected type returned by `project` method.
mut_ident: Ident,
/// Name of the projected type returned by `project_ref` method.
ref_ident: Ident,
/// Name of the projected type returned by `project_replace` method.
own_ident: Ident,
/// Lifetime on the generated projected types.
lifetime: Lifetime,
/// Generics of the projected types.
generics: Generics,
/// `where` clause of the projected types. This has an additional
/// bound generated by `insert_lifetime_and_bound`
where_clause: WhereClause,
}
struct ProjectedVariants {
proj_variants: TokenStream,
proj_ref_variants: TokenStream,
proj_own_variants: TokenStream,
proj_arms: TokenStream,
proj_ref_arms: TokenStream,
proj_own_arms: TokenStream,
}
#[derive(Default)]
struct ProjectedFields {
proj_pat: TokenStream,
proj_body: TokenStream,
proj_own_body: TokenStream,
proj_fields: TokenStream,
proj_ref_fields: TokenStream,
proj_own_fields: TokenStream,
}
fn validate_struct(ident: &Ident, fields: &Fields) -> Result<()> {
if fields.is_empty() {
let msg = "#[pin_project] attribute may not be used on structs with zero fields";
if let Fields::Unit = fields {
bail!(ident, msg)
}
bail!(fields, msg)
}
Ok(())
}
fn validate_enum(brace_token: token::Brace, variants: &Variants) -> Result<()> {
if variants.is_empty() {
return Err(Error::new(
brace_token.span.join(),
"#[pin_project] attribute may not be used on enums without variants",
));
}
let has_field = variants.iter().try_fold(false, |has_field, v| {
if let Some((_, e)) = &v.discriminant {
bail!(e, "#[pin_project] attribute may not be used on enums with discriminants");
} else if let Some(attr) = v.attrs.find(PIN) {
bail!(attr, "#[pin] attribute may only be used on fields of structs or variants");
} else if v.fields.is_empty() {
Ok(has_field)
} else {
Ok(true)
}
})?;
if has_field {
Ok(())
} else {
bail!(variants, "#[pin_project] attribute may not be used on enums with zero fields");
}
}
fn parse_struct<'a>(
cx: &mut Context<'a>,
fields: &'a Fields,
generate: &mut GenerateTokens,
) -> Result<()> {
// Do this first for a better error message.
let packed_check = ensure_not_packed(&cx.orig, Some(fields))?;
validate_struct(cx.orig.ident, fields)?;
let ProjectedFields {
proj_pat,
proj_body,
proj_fields,
proj_ref_fields,
proj_own_fields,
proj_own_body,
} = match fields {
Fields::Named(_) => visit_fields(cx, None, fields, Delimiter::Brace)?,
Fields::Unnamed(_) => visit_fields(cx, None, fields, Delimiter::Parenthesis)?,
Fields::Unit => unreachable!(),
};
let proj_ident = &cx.proj.mut_ident;
let proj_ref_ident = &cx.proj.ref_ident;
let proj_own_ident = &cx.proj.own_ident;
let vis = &cx.proj.vis;
let mut orig_generics = cx.orig.generics.clone();
let orig_where_clause = orig_generics.where_clause.take();
let proj_generics = &cx.proj.generics;
let proj_where_clause = &cx.proj.where_clause;
// For tuple structs, we need to generate `(T1, T2) where Foo: Bar`
// For non-tuple structs, we need to generate `where Foo: Bar { field1: T }`
let (where_clause_fields, where_clause_ref_fields, where_clause_own_fields) = match fields {
Fields::Named(_) => (
quote!(#proj_where_clause #proj_fields),
quote!(#proj_where_clause #proj_ref_fields),
quote!(#orig_where_clause #proj_own_fields),
),
Fields::Unnamed(_) => (
quote!(#proj_fields #proj_where_clause;),
quote!(#proj_ref_fields #proj_where_clause;),
quote!(#proj_own_fields #orig_where_clause;),
),
Fields::Unit => unreachable!(),
};
let (proj_attrs, proj_ref_attrs, proj_own_attrs) = proj_allowed_lints(cx);
generate.extend(cx.project, quote! {
#proj_attrs
#vis struct #proj_ident #proj_generics #where_clause_fields
});
generate.extend(cx.project_ref, quote! {
#proj_ref_attrs
#vis struct #proj_ref_ident #proj_generics #where_clause_ref_fields
});
if cx.project_replace.span().is_some() {
generate.extend(cx.project_replace.ident().is_some(), quote! {
#proj_own_attrs
#vis struct #proj_own_ident #orig_generics #where_clause_own_fields
});
}
let proj_mut_body = quote! {
let Self #proj_pat = self.get_unchecked_mut();
#proj_ident #proj_body
};
let proj_ref_body = quote! {
let Self #proj_pat = self.get_ref();
#proj_ref_ident #proj_body
};
let proj_own_body = quote! {
let Self #proj_pat = &mut *__self_ptr;
#proj_own_body
};
generate.extend(false, make_proj_impl(cx, &proj_mut_body, &proj_ref_body, &proj_own_body));
generate.extend(false, packed_check);
Ok(())
}
fn parse_enum<'a>(
cx: &mut Context<'a>,
DataEnum { brace_token, variants, .. }: &'a DataEnum,
generate: &mut GenerateTokens,
) -> Result<()> {
if let ProjReplace::Unnamed { span } = &cx.project_replace {
return Err(Error::new(
*span,
"`project_replace` argument requires a value when used on enums",
));
}
// #[repr(packed)] cannot be apply on enums and will be rejected by rustc.
// However, we should not rely on the behavior of rustc that rejects this.
//
// Do this first for a better error message.
ensure_not_packed(&cx.orig, None)?;
validate_enum(*brace_token, variants)?;
let ProjectedVariants {
proj_variants,
proj_ref_variants,
proj_own_variants,
proj_arms,
proj_ref_arms,
proj_own_arms,
} = visit_variants(cx, variants)?;
let proj_ident = &cx.proj.mut_ident;
let proj_ref_ident = &cx.proj.ref_ident;
let proj_own_ident = &cx.proj.own_ident;
let vis = &cx.proj.vis;
let mut orig_generics = cx.orig.generics.clone();
let orig_where_clause = orig_generics.where_clause.take();
let proj_generics = &cx.proj.generics;
let proj_where_clause = &cx.proj.where_clause;
let (proj_attrs, proj_ref_attrs, proj_own_attrs) = proj_allowed_lints(cx);
if cx.project {
generate.extend(true, quote! {
#proj_attrs
#vis enum #proj_ident #proj_generics #proj_where_clause {
#proj_variants
}
});
}
if cx.project_ref {
generate.extend(true, quote! {
#proj_ref_attrs
#vis enum #proj_ref_ident #proj_generics #proj_where_clause {
#proj_ref_variants
}
});
}
if cx.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
}
});
}
let proj_mut_body = quote! {
match self.get_unchecked_mut() {
#proj_arms
}
};
let proj_ref_body = quote! {
match self.get_ref() {
#proj_ref_arms
}
};
let proj_own_body = quote! {
match &mut *__self_ptr {
#proj_own_arms
}
};
generate.extend(false, make_proj_impl(cx, &proj_mut_body, &proj_ref_body, &proj_own_body));
Ok(())
}
fn visit_variants<'a>(cx: &mut Context<'a>, variants: &'a Variants) -> Result<ProjectedVariants> {
let mut proj_variants = TokenStream::new();
let mut proj_ref_variants = TokenStream::new();
let mut proj_own_variants = TokenStream::new();
let mut proj_arms = TokenStream::new();
let mut proj_ref_arms = TokenStream::new();
let mut proj_own_arms = TokenStream::new();
for Variant { ident, fields, .. } in variants {
let ProjectedFields {
proj_pat,
proj_body,
proj_fields,
proj_ref_fields,
proj_own_fields,
proj_own_body,
} = match fields {
Fields::Named(_) => visit_fields(cx, Some(ident), fields, Delimiter::Brace)?,
Fields::Unnamed(_) => visit_fields(cx, Some(ident), fields, Delimiter::Parenthesis)?,
Fields::Unit => ProjectedFields {
proj_own_body: proj_own_body(cx, Some(ident), None, &[]),
..Default::default()
},
};
let proj_ident = &cx.proj.mut_ident;
let proj_ref_ident = &cx.proj.ref_ident;
proj_variants.extend(quote! {
#ident #proj_fields,
});
proj_ref_variants.extend(quote! {
#ident #proj_ref_fields,
});
proj_own_variants.extend(quote! {
#ident #proj_own_fields,
});
proj_arms.extend(quote! {
Self::#ident #proj_pat => #proj_ident::#ident #proj_body,
});
proj_ref_arms.extend(quote! {
Self::#ident #proj_pat => #proj_ref_ident::#ident #proj_body,
});
proj_own_arms.extend(quote! {
Self::#ident #proj_pat => { #proj_own_body }
});
}
Ok(ProjectedVariants {
proj_variants,
proj_ref_variants,
proj_own_variants,
proj_arms,
proj_ref_arms,
proj_own_arms,
})
}
fn visit_fields<'a>(
cx: &mut Context<'a>,
variant_ident: Option<&Ident>,
fields: &'a Fields,
delim: Delimiter,
) -> Result<ProjectedFields> {
fn surround(delim: Delimiter, tokens: TokenStream) -> TokenStream {
Group::new(delim, tokens).into_token_stream()
}
let mut proj_pat = TokenStream::new();
let mut proj_body = TokenStream::new();
let mut proj_fields = TokenStream::new();
let mut proj_ref_fields = TokenStream::new();
let mut proj_own_fields = TokenStream::new();
let mut proj_move = TokenStream::new();
let mut pinned_bindings = Vec::with_capacity(fields.len());
for (i, Field { attrs, vis, ident, colon_token, ty, mutability: _ }) in
fields.iter().enumerate()
{
let binding = ident.clone().unwrap_or_else(|| format_ident!("_{}", i));
proj_pat.extend(quote!(#binding,));
let lifetime = &cx.proj.lifetime;
if attrs.position_exact(PIN)?.is_some() {
proj_fields.extend(quote! {
#vis #ident #colon_token ::pin_project::__private::Pin<&#lifetime mut (#ty)>,
});
proj_ref_fields.extend(quote! {
#vis #ident #colon_token ::pin_project::__private::Pin<&#lifetime (#ty)>,
});
proj_own_fields.extend(quote! {
#vis #ident #colon_token ::pin_project::__private::PhantomData<#ty>,
});
proj_body.extend(quote! {
#ident #colon_token _pin_project::__private::Pin::new_unchecked(#binding),
});
proj_move.extend(quote! {
#ident #colon_token _pin_project::__private::PhantomData,
});
cx.pinned_fields.push(ty);
pinned_bindings.push(binding);
} else {
proj_fields.extend(quote! {
#vis #ident #colon_token &#lifetime mut (#ty),
});
proj_ref_fields.extend(quote! {
#vis #ident #colon_token &#lifetime (#ty),
});
proj_own_fields.extend(quote! {
#vis #ident #colon_token #ty,
});
proj_body.extend(quote! {
#binding,
});
proj_move.extend(quote! {
#ident #colon_token _pin_project::__private::ptr::read(#binding),
});
}
}
let proj_pat = surround(delim, proj_pat);
let proj_body = surround(delim, proj_body);
let proj_fields = surround(delim, proj_fields);
let proj_ref_fields = surround(delim, proj_ref_fields);
let proj_own_fields = surround(delim, proj_own_fields);
let proj_move = Group::new(delim, proj_move);
let proj_own_body = proj_own_body(cx, variant_ident, Some(&proj_move), &pinned_bindings);
Ok(ProjectedFields {
proj_pat,
proj_body,
proj_own_body,
proj_fields,
proj_ref_fields,
proj_own_fields,
})
}
/// Generates the processing that `project_replace` does for the struct or each variant.
///
/// Note: `pinned_fields` must be in declaration order.
fn proj_own_body(
cx: &Context<'_>,
variant_ident: Option<&Ident>,
proj_move: Option<&Group>,
pinned_fields: &[Ident],
) -> TokenStream {
let ident = &cx.proj.own_ident;
let proj_own = match variant_ident {
Some(variant_ident) => quote!(#ident::#variant_ident),
None => quote!(#ident),
};
// The fields of the struct and the active enum variant are dropped
// in declaration order.
let pinned_fields = pinned_fields.iter().rev();
quote! {
// First, extract all the unpinned fields.
let __result = #proj_own #proj_move;
// Now create guards to drop all the pinned fields.
//
// this must be in its own scope, or else `__result` will not be dropped
// if any of the destructors panic.
{
#(
let __guard = _pin_project::__private::UnsafeDropInPlaceGuard::new(#pinned_fields);
)*
}
// Finally, return the result.
__result
}
}
/// Creates `Unpin` implementation for the original type.
///
/// The kind of `Unpin` impl generated depends on `unpin_impl` field:
/// - `UnpinImpl::Unsafe` - Implements `Unpin` via `UnsafeUnpin` impl.
/// - `UnpinImpl::Negative` - Generates `Unpin` impl with bounds that will never be true.
/// - `UnpinImpl::Default` - Generates `Unpin` impl that requires `Unpin` for all pinned fields.
fn make_unpin_impl(cx: &Context<'_>) -> TokenStream {
match cx.unpin_impl {
UnpinImpl::Unsafe(span) => {
let mut proj_generics = cx.proj.generics.clone();
let orig_ident = cx.orig.ident;
let lifetime = &cx.proj.lifetime;
// Make the error message highlight `UnsafeUnpin` argument.
proj_generics.make_where_clause().predicates.push(parse_quote_spanned! { span =>
_pin_project::__private::Wrapper<#lifetime, Self>: _pin_project::UnsafeUnpin
});
let (impl_generics, _, where_clause) = proj_generics.split_for_impl();
let ty_generics = cx.orig.generics.split_for_impl().1;
quote_spanned! { span =>
impl #impl_generics _pin_project::__private::Unpin for #orig_ident #ty_generics
#where_clause
{
}
}
}
UnpinImpl::Negative(span) => {
let mut proj_generics = cx.proj.generics.clone();
let orig_ident = cx.orig.ident;
let lifetime = &cx.proj.lifetime;
proj_generics.make_where_clause().predicates.push(parse_quote! {
_pin_project::__private::Wrapper<
#lifetime, _pin_project::__private::PhantomPinned
>: _pin_project::__private::Unpin
});
let (proj_impl_generics, _, proj_where_clause) = proj_generics.split_for_impl();
let ty_generics = cx.orig.generics.split_for_impl().1;
// For interoperability with `forbid(unsafe_code)`, `unsafe` token should be
// call-site span.
let unsafety = <Token![unsafe]>::default();
quote_spanned! { span =>
impl #proj_impl_generics _pin_project::__private::Unpin
for #orig_ident #ty_generics
#proj_where_clause
{
}
// Generate a dummy impl of `UnsafeUnpin`, to ensure that the user cannot implement it.
//
// To ensure that users don't accidentally write a non-functional `UnsafeUnpin`
// impls, we emit one ourselves. If the user ends up writing an `UnsafeUnpin`
// impl, they'll get a "conflicting implementations of trait" error when
// coherence checks are run.
#[doc(hidden)]
#unsafety impl #proj_impl_generics _pin_project::UnsafeUnpin
for #orig_ident #ty_generics
#proj_where_clause
{
}
}
}
UnpinImpl::Default => {
let mut full_where_clause = cx.orig.generics.where_clause.clone().unwrap();
// Generate a field in our new struct for every
// pinned field in the original type.
let fields = cx.pinned_fields.iter().enumerate().map(|(i, ty)| {
let field_ident = format_ident!("__field{}", i);
quote!(#field_ident: #ty)
});
// We could try to determine the subset of type parameters
// and lifetimes that are actually used by the pinned fields
// (as opposed to those only used by unpinned fields).
// However, this would be tricky and error-prone, since
// it's possible for users to create types that would alias
// with generic parameters (e.g. 'struct T').
//
// Instead, we generate a use of every single type parameter
// and lifetime used in the original struct. For type parameters,
// we generate code like this:
//
// ```rust
// struct AlwaysUnpin<T: ?Sized>(PhantomData<T>) {}
// impl<T: ?Sized> Unpin for AlwaysUnpin<T> {}
//
// ...
// _field: AlwaysUnpin<(A, B, C)>
// ```
//
// This ensures that any unused type parameters
// don't end up with `Unpin` bounds.
let lifetime_fields = cx.orig.generics.lifetimes().enumerate().map(
|(i, LifetimeParam { lifetime, .. })| {
let field_ident = format_ident!("__lifetime{}", i);
quote!(#field_ident: &#lifetime ())
},
);
let orig_ident = cx.orig.ident;
let struct_ident = format_ident!("__{}", orig_ident);
let vis = cx.orig.vis;
let lifetime = &cx.proj.lifetime;
let type_params = cx.orig.generics.type_params().map(|t| &t.ident);
let proj_generics = &cx.proj.generics;
let (proj_impl_generics, proj_ty_generics, _) = proj_generics.split_for_impl();
let (_, ty_generics, where_clause) = cx.orig.generics.split_for_impl();
full_where_clause.predicates.push(parse_quote! {
#struct_ident #proj_ty_generics: _pin_project::__private::Unpin
});
quote! {
// This needs to have the same visibility as the original type,
// due to the limitations of the 'public in private' error.
//
// Our goal is to implement the public trait `Unpin` for
// a potentially public user type. Because of this, rust
// requires that any types mentioned in the where clause of
// our `Unpin` impl also be public. This means that our generated
// `__UnpinStruct` type must also be public.
// However, we ensure that the user can never actually reference
// this 'public' type by creating this type in the inside of `const`.
#[allow(missing_debug_implementations)]
#vis struct #struct_ident #proj_generics #where_clause {
__pin_project_use_generics: _pin_project::__private::AlwaysUnpin<
#lifetime, (#(_pin_project::__private::PhantomData<#type_params>),*)
>,
#(#fields,)*
#(#lifetime_fields,)*
}
impl #proj_impl_generics _pin_project::__private::Unpin
for #orig_ident #ty_generics
#full_where_clause
{
}
// Generate a dummy impl of `UnsafeUnpin`, to ensure that the user cannot implement it.
//
// To ensure that users don't accidentally write a non-functional `UnsafeUnpin`
// impls, we emit one ourselves. If the user ends up writing an `UnsafeUnpin`
// impl, they'll get a "conflicting implementations of trait" error when
// coherence checks are run.
#[doc(hidden)]
unsafe impl #proj_impl_generics _pin_project::UnsafeUnpin
for #orig_ident #ty_generics
#full_where_clause
{
}
}
}
}
}
/// Creates `Drop` implementation for the original type.
///
/// The kind of `Drop` impl generated depends on `pinned_drop` field:
/// - `Some` - implements `Drop` via `PinnedDrop` impl.
/// - `None` - generates code that ensures that `Drop` trait is not implemented,
/// instead of generating `Drop` impl.
fn make_drop_impl(cx: &Context<'_>) -> TokenStream {
let ident = cx.orig.ident;
let (impl_generics, ty_generics, where_clause) = cx.orig.generics.split_for_impl();
if let Some(span) = cx.pinned_drop {
// For interoperability with `forbid(unsafe_code)`, `unsafe` token should be
// call-site span.
let unsafety = <Token![unsafe]>::default();
quote_spanned! { span =>
impl #impl_generics _pin_project::__private::Drop for #ident #ty_generics
#where_clause
{
fn drop(&mut self) {
#unsafety {
// Safety - we're in 'drop', so we know that 'self' will
// never move again.
let __pinned_self = _pin_project::__private::Pin::new_unchecked(self);
// We call `pinned_drop` only once. Since `PinnedDrop::drop`
// is an unsafe method and a private API, it is never called again in safe
// code *unless the user uses a maliciously crafted macro*.
_pin_project::__private::PinnedDrop::drop(__pinned_self);
}
}
}
}
} else {
// If the user does not provide a `PinnedDrop` impl,
// we need to ensure that they don't provide a `Drop` impl of their
// own.
// Based on https://github.com/upsuper/assert-impl/blob/f503255b292ab0ba8d085b657f4065403cfa46eb/src/lib.rs#L80-L87
//
// We create a new identifier for each struct, so that the traits
// for different types do not conflict with each other.
//
// Another approach would be to provide an empty Drop impl,
// which would conflict with a user-provided Drop impl.
// However, this would trigger the compiler's special handling
// of Drop types (e.g. fields cannot be moved out of a Drop type).
// This approach prevents the creation of needless Drop impls,
// giving users more flexibility.
let trait_ident = format_ident!("{}MustNotImplDrop", ident);
quote! {
// There are two possible cases:
// 1. The user type does not implement Drop. In this case,
// the first blanked impl will not apply to it. This code
// will compile, as there is only one impl of MustNotImplDrop for the user type
// 2. The user type does impl Drop. This will make the blanket impl applicable,
// which will then conflict with the explicit MustNotImplDrop impl below.
// This will result in a compilation error, which is exactly what we want.
trait #trait_ident {}
#[allow(clippy::drop_bounds, drop_bounds)]
impl<T: _pin_project::__private::Drop> #trait_ident for T {}
impl #impl_generics #trait_ident for #ident #ty_generics #where_clause {}
// Generate a dummy impl of `PinnedDrop`, to ensure that the user cannot implement it.
// Since the user did not pass `PinnedDrop` to `#[pin_project]`, any `PinnedDrop`
// impl will not actually be called. Unfortunately, we can't detect this situation
// directly from either the `#[pin_project]` or `#[pinned_drop]` attributes, since
// we don't know what other attributes/impl may exist.
//
// To ensure that users don't accidentally write a non-functional `PinnedDrop`
// impls, we emit one ourselves. If the user ends up writing a `PinnedDrop` impl,
// they'll get a "conflicting implementations of trait" error when coherence
// checks are run.
#[doc(hidden)]
impl #impl_generics _pin_project::__private::PinnedDrop for #ident #ty_generics
#where_clause
{
unsafe fn drop(self: _pin_project::__private::Pin<&mut Self>) {}
}
}
}
}
/// Creates an implementation of the projection methods.
///
/// On structs, both the `project` and `project_ref` methods are always generated,
/// and the `project_replace` method is only generated if `ProjReplace::span` is `Some`.
///
/// On enums, only methods that the returned projected type is named will be generated.
fn make_proj_impl(
cx: &Context<'_>,
proj_body: &TokenStream,
proj_ref_body: &TokenStream,
proj_own_body: &TokenStream,
) -> TokenStream {
let vis = &cx.proj.vis;
let lifetime = &cx.proj.lifetime;
let orig_ident = cx.orig.ident;
let proj_ident = &cx.proj.mut_ident;
let proj_ref_ident = &cx.proj.ref_ident;
let proj_own_ident = &cx.proj.own_ident;
let orig_ty_generics = cx.orig.generics.split_for_impl().1;
let proj_ty_generics = cx.proj.generics.split_for_impl().1;
let (impl_generics, ty_generics, where_clause) = cx.orig.generics.split_for_impl();
// TODO: For enums and project_replace, dead_code warnings should not be
// allowed because methods are not generated unless explicitly specified.
// However, there is currently no good way to allow warnings for generated
// code, so we allow warnings for all methods for now.
let allow_dead_code = quote! { #[allow(dead_code)] };
let mut project = Some(quote! {
#allow_dead_code
#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! {
#allow_dead_code
#[allow(clippy::missing_const_for_fn)]
#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 = cx.project_replace.span().map(|span| {
// It is enough to only set the span of the signature.
let sig = quote_spanned! { span =>
#allow_dead_code
#vis fn project_replace(
self: _pin_project::__private::Pin<&mut Self>,
__replacement: Self,
) -> #proj_own_ident #orig_ty_generics
};
quote! {
#sig {
unsafe {
let __self_ptr: *mut Self = self.get_unchecked_mut();
// Destructors will run in reverse order, so next create a guard to overwrite
// `self` with the replacement value without calling destructors.
let __guard = _pin_project::__private::UnsafeOverwriteGuard::new(
__self_ptr,
__replacement,
);
#proj_own_body
}
}
}
});
if cx.kind == Enum {
if !cx.project {
project = None;
}
if !cx.project_ref {
project_ref = None;
}
if cx.project_replace.ident().is_none() {
project_replace = None;
}
}
quote! {
impl #impl_generics #orig_ident #ty_generics #where_clause {
#project
#project_ref
#project_replace
}
}
}
/// Checks that the `[repr(packed)]` attribute is not included.
///
/// This currently does two checks:
/// - Checks the attributes of structs to ensure there is no `[repr(packed)]`.
/// - Generates a function that borrows fields without an unsafe block and
/// forbidding `unaligned_references` lint.
fn ensure_not_packed(orig: &OriginalType<'_>, fields: Option<&Fields>) -> Result<TokenStream> {
for attr in orig.attrs {
if let Meta::List(ref list) = attr.meta {
if list.path.is_ident("repr") {
for repr in list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)? {
if repr.path().is_ident("packed") {
let msg = if fields.is_none() {
// #[repr(packed)] cannot be apply on enums and will be rejected by rustc.
// However, we should not rely on the behavior of rustc that rejects this.
"#[repr(packed)] attribute should be applied to a struct or union"
} else if repr.require_name_value().is_ok() {
// #[repr(packed = "")] is not valid format of #[repr(packed)] and will be
// rejected by rustc.
// However, we should not rely on the behavior of rustc that rejects this.
"#[repr(packed)] attribute should not be name-value pair"
} else {
"#[pin_project] attribute may not be used on #[repr(packed)] types"
};
bail!(repr, msg);
}
}
}
}
}
let fields = match fields {
Some(fields) => fields,
None => return Ok(TokenStream::new()),
};
// Through the tricky use of proc macros, it's possible to bypass
// the above check for the `repr` attribute.
// To ensure that it's impossible to use pin projections on a `#[repr(packed)]`
// struct, we generate code like this:
//
// ```rust
// #[forbid(unaligned_references)]
// fn assert_not_repr_packed(val: &MyStruct) {
// let _field_1 = &val.field_1;
// let _field_2 = &val.field_2;
// ...
// let _field_n = &val.field_n;
// }
// ```
//
// Taking a reference to a packed field is UB, and applying
// `#[forbid(unaligned_references)]` makes sure that doing this is a hard error.
//
// If the struct ends up having `#[repr(packed)]` applied somehow,
// this will generate an (unfriendly) error message. Under all reasonable
// circumstances, we'll detect the `#[repr(packed)]` attribute, and generate
// a much nicer error above.
//
// There is one exception: If the type of a struct field has an alignment of 1
// (e.g. u8), it is always safe to take a reference to it, even if the struct
// is `#[repr(packed)]`. If the struct is composed entirely of types of
// alignment 1, our generated method will not trigger an error if the
// struct is `#[repr(packed)]`.
//
// Fortunately, this should have no observable consequence - `#[repr(packed)]`
// is essentially a no-op on such a type. Nevertheless, we include a test
// to ensure that the compiler doesn't ever try to copy the fields on
// such a struct when trying to drop it - which is reason we prevent
// `#[repr(packed)]` in the first place.
//
//
// Note:
// - Lint-based tricks aren't perfect, but they're much better than nothing:
//
// - Enable both unaligned_references and safe_packed_borrows lints
// because unaligned_references lint does not exist in older compilers:
let mut field_refs = vec![];
match fields {
Fields::Named(FieldsNamed { named, .. }) => {
for Field { ident, .. } in named {
field_refs.push(quote!(&this.#ident));
}
}
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
for (index, _) in unnamed.iter().enumerate() {
let index = Index::from(index);
field_refs.push(quote!(&this.#index));
}
}
Fields::Unit => {}
}
let (impl_generics, ty_generics, where_clause) = orig.generics.split_for_impl();
let ident = orig.ident;
Ok(quote! {
#[forbid(unaligned_references, safe_packed_borrows)]
fn __assert_not_repr_packed #impl_generics (this: &#ident #ty_generics) #where_clause {
#(let _ = #field_refs;)*
}
})
}