Skip to content

Commit

Permalink
Remove pin_project! macro and add #[pinned_drop] attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Aug 10, 2019
1 parent 320fe24 commit e98b9e8
Show file tree
Hide file tree
Showing 33 changed files with 587 additions and 487 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ The current version of pin-project requires Rust 1.33 or later.

## Examples

[`pin_projectable`] attribute creates a projection struct covering all the fields.
[`pin_project`] attribute creates a projection struct covering all the fields.

```rust
use pin_project::pin_projectable;
use pin_project::pin_project;
use std::pin::Pin;

#[pin_projectable]
#[pin_project]
struct Foo<T, U> {
#[pin]
future: T,
Expand All @@ -58,7 +58,7 @@ impl<T, U> Foo<T, U> {

[Code like this will be generated](doc/struct-example-1.md)

[`pin_projectable`]: https://docs.rs/pin-project/0.3/pin_project/attr.pin_projectable.html
[`pin_project`]: https://docs.rs/pin-project/0.3/pin_project/attr.pin_project.html

## License

Expand Down
19 changes: 9 additions & 10 deletions pin-project-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ extern crate proc_macro;
#[macro_use]
mod utils;

mod pin_projectable;
mod pin_project;
mod pinned_drop;
#[cfg(feature = "project_attr")]
mod project;

use proc_macro::TokenStream;

use crate::utils::Nothing;
use utils::Nothing;

#[cfg(feature = "project_attr")]
#[proc_macro_attribute]
Expand All @@ -27,17 +28,15 @@ pub fn project(args: TokenStream, input: TokenStream) -> TokenStream {
project::attribute(input.into()).into()
}

/// This is a doc comment from the defining crate!
#[proc_macro]
pub fn pin_project(input: TokenStream) -> TokenStream {
pin_projectable::pin_project(input.into()).unwrap_or_else(|e| e.to_compile_error()).into()
#[proc_macro_attribute]
pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream {
pin_project::attribute(args.into(), input.into()).into()
}

#[proc_macro_attribute]
pub fn pin_projectable(args: TokenStream, input: TokenStream) -> TokenStream {
pin_projectable::attribute(args.into(), input.into())
.unwrap_or_else(|e| e.to_compile_error())
.into()
pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
let _: Nothing = syn::parse_macro_input!(args);
pinned_drop::attribute(input.into()).into()
}

#[cfg(feature = "renamed")]
Expand Down
10 changes: 3 additions & 7 deletions pin-project-internal/src/pin_project/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,8 @@ use crate::utils::{proj_ident, VecExt};

use super::*;

pub(super) fn parse(
args: TokenStream,
mut item: ItemEnum,
pinned_drop: Option<ItemFn>,
) -> Result<TokenStream> {
let impl_drop = ImplDrop::new(item.generics.clone(), pinned_drop)?;
let mut impl_unpin = ImplUnpin::new(args, &item.generics)?;
pub(super) fn parse(args: Args, mut item: ItemEnum) -> Result<TokenStream> {
let mut impl_unpin = args.impl_unpin(&item.generics);

if item.variants.is_empty() {
return Err(error!(item, "cannot be implemented for enums without variants"));
Expand All @@ -34,6 +29,7 @@ pub(super) fn parse(
let (proj_item_body, proj_arms) = variants(&mut item, &proj_ident, &mut impl_unpin)?;

let ident = &item.ident;
let impl_drop = args.impl_drop(&item.generics);
let proj_generics = proj_generics(&item.generics);
let proj_ty_generics = proj_generics.split_for_impl().1;
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
Expand Down
223 changes: 75 additions & 148 deletions pin-project-internal/src/pin_project/mod.rs
Original file line number Diff line number Diff line change
@@ -1,113 +1,70 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use quote::{quote, quote_spanned};
use syn::{
Fields, FieldsNamed, FieldsUnnamed, Generics, Index, Item, ItemEnum, ItemFn, ItemStruct, Meta,
NestedMeta, Result, ReturnType, Type, TypeTuple,
parse::{Parse, ParseStream},
Fields, FieldsNamed, FieldsUnnamed, Generics, Index, Item, ItemStruct, Meta, NestedMeta,
Result, Type,
};

use crate::utils::{Nothing, VecExt};
use crate::utils::{crate_path, Nothing};

mod enums;
mod structs;

/// The annotation for pinned type.
const PIN: &str = "pin";

const PINNED_DROP: &str = "pinned_drop";
pub(super) fn attribute(args: TokenStream, input: TokenStream) -> TokenStream {
parse(args, input).unwrap_or_else(|e| e.to_compile_error())
}

#[derive(Clone, Copy)]
struct Args {
pinned_drop: Option<Span>,
unsafe_unpin: Option<Span>,
}

struct PinProject {
items: Vec<Item>,
impl Args {
fn impl_drop(self, generics: &Generics) -> ImplDrop<'_> {
ImplDrop::new(generics, self.pinned_drop)
}

fn impl_unpin(self, generics: &Generics) -> ImplUnpin {
ImplUnpin::new(generics, self.unsafe_unpin)
}
}

impl Parse for PinProject {
impl Parse for Args {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let mut items = vec![];
let mut pinned_drop = None;
let mut unsafe_unpin = None;
while !input.is_empty() {
items.push(input.parse()?);
let i = input.parse::<Ident>()?;
match &*i.to_string() {
"PinnedDrop" => pinned_drop = Some(i.span()),
"unsafe_Unpin" => unsafe_unpin = Some(i.span()),
_ => return Err(error!(i, "an invalid argument was passed")),
}
}
Ok(Self { items })
Ok(Self { pinned_drop, unsafe_unpin })
}
}

fn handle_type(args: TokenStream, item: Item, pinned_drop: Option<ItemFn>) -> Result<TokenStream> {
match item {
fn parse(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
let args = syn::parse2(args)?;
match syn::parse2(input)? {
Item::Struct(item) => {
let packed_check = ensure_not_packed(&item)?;
let mut res = structs::parse(args, item, pinned_drop)?;
let mut res = structs::parse(args, item)?;
res.extend(packed_check);
Ok(res)
}
Item::Enum(item) => {
// We don't need to check for '#[repr(packed)]',
// since it does not apply to enums
enums::parse(args, item, pinned_drop)
}
_ => unreachable!(),
}
}

pub(super) fn pin_project(input: TokenStream) -> Result<TokenStream> {
let span = span!(input);
let items: Vec<Item> = syn::parse2::<PinProject>(input)?.items;

let mut found_type = None;
let mut found_pinned_drop = None;

for mut item in items {
match &mut item {
Item::Struct(ItemStruct { attrs, .. }) | Item::Enum(ItemEnum { attrs, .. }) => {
if found_type.is_none() {
if let Some(attr) = attrs.find_remove("pin_projectable") {
let args = match attr.parse_meta()? {
Meta::List(l) => l.nested.into_token_stream(),
Meta::Word(_) => TokenStream::new(),
_ => return Err(error!(span!(attr), "invalid arguments"))
};

found_type = Some((item.clone(), args));
} else {
return Err(error!(item, "type declared in pin_project! must have pin_projectable attribute"))
}
} else {
return Err(error!(item, "cannot declare multiple types within pinned module"))
}
},
Item::Fn(fn_) => {
if let Some(attr)= fn_.attrs.find_remove(PINNED_DROP) {
let _: Nothing = syn::parse2(attr.tts)?;
if found_pinned_drop.is_none() {
if let ReturnType::Type(_, ty) = &fn_.decl.output {
match &**ty {
Type::Tuple(TypeTuple { elems, .. }) if elems.is_empty() => {}
_ => return Err(error!(ty, "#[pinned_drop] function must return the unit type")),
}
}
found_pinned_drop = Some(fn_.clone());
} else {
return Err(error!(fn_, "cannot declare multiple functions within pinned module"));
}
} else {
return Err(error!(fn_, "only #[pinned_drop] functions can be declared within a pin_project! block"));
}
},
_ => return Err(error!(item, "pin_project! block may only contain a struct/enum with an optional #[pinned_drop] function"))
enums::parse(args, item)
}
}

let (type_, args) = match found_type {
Some(t) => t,
None => return Err(error!(span, "No #[pin_projectable] type found!")),
};

handle_type(args, type_, found_pinned_drop)
}

pub(super) fn attribute(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
let item = syn::parse2(input)?;
match &item {
Item::Struct(_) | Item::Enum(_) => handle_type(args, item, None),
_ => Err(error!(item, "may only be used on structs or enums")),
item => Err(error!(item, "may only be used on structs or enums")),
}
}

Expand All @@ -120,7 +77,7 @@ fn ensure_not_packed(item: &ItemStruct) -> Result<TokenStream> {
if w == "packed" {
return Err(error!(
w,
"pin_projectable may not be used on #[repr(packed)] types"
"pin_project may not be used on #[repr(packed)] types"
));
}
}
Expand Down Expand Up @@ -203,36 +160,40 @@ fn proj_generics(generics: &Generics) -> Generics {
generics
}

struct ImplDrop {
generics: Generics,
pinned_drop: Option<ItemFn>,
// =================================================================================================
// Drop implementation

struct ImplDrop<'a> {
generics: &'a Generics,
pinned_drop: Option<Span>,
}

impl ImplDrop {
/// Parses attribute arguments.
fn new(generics: Generics, pinned_drop: Option<ItemFn>) -> Result<Self> {
Ok(Self { generics, pinned_drop })
impl<'a> ImplDrop<'a> {
fn new(generics: &'a Generics, pinned_drop: Option<Span>) -> Self {
Self { generics, pinned_drop }
}

fn build(self, ident: &Ident) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();

if let Some(fn_) = self.pinned_drop {
let fn_name = fn_.ident.clone();
if let Some(pinned_drop) = self.pinned_drop {
let crate_path = crate_path();
let call = quote_spanned! { pinned_drop =>
::#crate_path::__private::UnsafePinnedDrop::pinned_drop(pinned_self)
};

quote! {
impl #impl_generics ::core::ops::Drop for #ident #ty_generics #where_clause {
fn drop(&mut self) {
// Declare the #[pinned_drop] function *inside* our drop function
// This guarantees that it's impossible for any other user code
// to call it
#fn_
// Safety - we're in 'drop', so we know that 'self' will
// never move again
let pinned_self = unsafe { ::core::pin::Pin::new_unchecked(self) };
// 'pinned_drop' is a free function - if it were part of a trait impl,
// it would be possible for user code to call it by directly invoking
// the trait.
#fn_name(pinned_self);
// We call `pinned_drop` only once. Since `UnsafePinnedDrop::pinned_drop`
// is an unsafe function and a private API, it is never called again in safe
// code *unless the user uses a maliciously crafted macro*.
unsafe {
#call;
}
}
}
}
Expand All @@ -254,25 +215,28 @@ impl ImplDrop {

struct ImplUnpin {
generics: Generics,
auto: bool,
unsafe_unpin: bool,
}

impl ImplUnpin {
/// Parses attribute arguments.
fn new(args: TokenStream, generics: &Generics) -> Result<Self> {
fn new(generics: &Generics, unsafe_unpin: Option<Span>) -> Self {
let mut generics = generics.clone();
generics.make_where_clause();

match &*args.to_string() {
"" => Ok(Self { generics, auto: true }),
"unsafe_Unpin" => Ok(Self { generics, auto: false }),
_ => Err(error!(args, "an invalid argument was passed")),
if let Some(unsafe_unpin) = unsafe_unpin {
let crate_path = crate_path();
generics.make_where_clause().predicates.push(
syn::parse2(quote_spanned! { unsafe_unpin =>
::#crate_path::__private::Wrapper<Self>: ::#crate_path::UnsafeUnpin
})
.unwrap(),
);
}

Self { generics, unsafe_unpin: unsafe_unpin.is_some() }
}

fn push(&mut self, ty: &Type) {
// We only add bounds for automatically generated impls
if self.auto {
if !self.unsafe_unpin {
self.generics
.make_where_clause()
.predicates
Expand All @@ -283,45 +247,8 @@ impl ImplUnpin {
/// Creates `Unpin` implementation.
fn build(self, ident: &Ident) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let mut where_clause = where_clause.unwrap().clone(); // Created in 'new'

if self.auto {
quote! {
impl #impl_generics ::core::marker::Unpin for #ident #ty_generics #where_clause {}
}
} else {
let pin_project_crate = pin_project_crate_path();
where_clause.predicates.push(syn::parse_quote!(::#pin_project_crate::Wrapper<#ident #ty_generics>: ::#pin_project_crate::UnsafeUnpin));

quote! {
impl #impl_generics ::core::marker::Unpin for #ident #ty_generics #where_clause {}
}
quote! {
impl #impl_generics ::core::marker::Unpin for #ident #ty_generics #where_clause {}
}
}
}

/// If the 'renamed' feature is enabled, we detect
/// the actual name of the 'pin-project' crate in the consumer's Cargo.toml
#[cfg(feature = "renamed")]
fn pin_project_crate_path() -> Ident {
use crate::PIN_PROJECT_CRATE;
// This is fairly subtle.
// Normally, you would use `env!("CARGO_PKG_NAME")` to get the name of the package,
// since it's set at compile time.
// However, we're in a proc macro, which runs while *another* crate is being compiled.
// By retreiving the runtime value of `CARGO_PKG_NAME`, we can figure out the name
// of the crate that's calling us.
let cur_crate = std::env::var("CARGO_PKG_NAME")
.expect("Could not find CARGO_PKG_NAME environemnt variable");
Ident::new(
if cur_crate == "pin-project" { "pin_project" } else { PIN_PROJECT_CRATE.as_str() },
Span::call_site(),
)
}

/// If the 'renamed' feature is not enabled, we just
/// assume that the 'pin-project' dependency has not been renamed
#[cfg(not(feature = "renamed"))]
fn pin_project_crate_path() -> Ident {
Ident::new("pin_project", Span::call_site())
}
Loading

0 comments on commit e98b9e8

Please sign in to comment.