Skip to content

Commit

Permalink
Merge #46 #48
Browse files Browse the repository at this point in the history
46: Add impl block support to #[project] attribute r=taiki-e a=taiki-e

This adds a feature to convert an annotated impl to a projected type's impl.

Example:

```rust
#[project]
impl<T, U> Foo<T, U> {
    fn baz(self) {
        let Self { future, field } = self;
    }
}

// convert to:
impl<'_pin, T, U> __FooProjection<'_pin, T, U> {
    fn baz(self) {
        let Self { future, field } = self;
    }
}
```

Closes #43

cc @Nemo157



48: Assert lack of Drop impl without implementing Drop r=taiki-e a=Aaron1011

Previously, we would provide an empty Drop impl for types that did not
provide a pinned_drop method, in order to assert that the user was not
providing their own Drop impl.

However, the compiler places certain restrictions on types that
implement Drop. For example, you cannot move out of the fields of such
types, as that would cause the Drop impl to observe the fields in an
uninitialized state.

This commit changes to a purely trait-based approach, as used in
the assert-impl crate by @upsuper . This prevents the creation of
unnecessary Drop impls, while still ensuring that the user cannot
provide their own Drop impl without going through pinned_drop

Co-authored-by: Taiki Endo <te316e89@gmail.com>
Co-authored-by: Aaron Hill <aa1ronham@gmail.com>
  • Loading branch information
3 people authored Aug 23, 2019
3 parents 44426ae + f8d0a0c + deea6e8 commit b5bc62b
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 54 deletions.
74 changes: 66 additions & 8 deletions pin-project-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,19 @@ use syn::parse::Nothing;
///
/// 2. The destructor of the struct must not move structural fields out of its argument.
///
/// To enforce this, this attribute will automatically generate a `Drop` impl.
/// To enforce this, this attribute will generate code like this:
///
/// ```rust
/// struct MyStruct {}
/// trait MyStructMustNotImplDrop {}
/// impl<T: Drop> MyStructMustNotImplDrop for T {}
/// impl MyStructMustNotImplDrop for MyStruct {}
/// ```
///
/// If you attempt to provide an Drop impl, the blanket impl will
/// then apply to your type, causing a compile-time error due to
/// the conflict with the second impl.
///
/// If you wish to provide a custom `Drop` impl, you can annotate a function
/// with `#[pinned_drop]`. This function takes a pinned version of your struct -
/// that is, `Pin<&mut MyStruct>` where `MyStruct` is the type of your struct.
Expand Down Expand Up @@ -289,7 +301,7 @@ pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream {
/// `Pin<&mut Self>`. In particular, it will never be called more than once,
/// just like [`Drop::drop`].
///
/// Example:
/// ## Example
///
/// ```rust
/// use pin_project::{pin_project, pinned_drop};
Expand Down Expand Up @@ -325,14 +337,55 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
/// *This attribute is available if pin-project is built with the
/// `"project_attr"` feature.*
///
/// The attribute at the expression position is not stable, so you need to use
/// a dummy `#[project]` attribute for the function.
/// The following three syntaxes are supported.
///
/// ## Examples
/// ## `impl` blocks
///
/// All methods (and associated functions) in `#[project] impl` block become
/// methods of the projected type. If you want to implement methods on the
/// original type, you need to create another (non-`#[project]`) `impl` block.
///
/// The following two syntaxes are supported.
/// To call a method implemented in `#[project] impl` block, you need to first
/// get the projected-type with `let this = self.project();`.
///
/// ### `let` bindings
/// ### Examples
///
/// ```rust
/// use pin_project::{pin_project, project};
/// use std::pin::Pin;
///
/// #[pin_project]
/// struct Foo<T, U> {
/// #[pin]
/// future: T,
/// field: U,
/// }
///
/// // impl for the original type
/// impl<T, U> Foo<T, U> {
/// fn bar(mut self: Pin<&mut Self>) {
/// self.project().baz()
/// }
/// }
///
/// // impl for the projected type
/// #[project]
/// impl<T, U> Foo<T, U> {
/// fn baz(self) {
/// let Self { future, field } = self;
///
/// let _: Pin<&mut T> = future;
/// let _: &mut U = field;
/// }
/// }
/// ```
///
/// ## `let` bindings
///
/// *The attribute at the expression position is not stable, so you need to use
/// a dummy `#[project]` attribute for the function.*
///
/// ### Examples
///
/// ```rust
/// use pin_project::{pin_project, project};
Expand All @@ -357,7 +410,12 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// ### `match` expressions
/// ## `match` expressions
///
/// *The attribute at the expression position is not stable, so you need to use
/// a dummy `#[project]` attribute for the function.*
///
/// ### Examples
///
/// ```rust
/// use pin_project::{project, pin_project};
Expand Down
57 changes: 28 additions & 29 deletions pin-project-internal/src/pin_project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use syn::{
*,
};

use crate::utils::{crate_path, proj_ident, proj_trait_ident};
use crate::utils::{self, crate_path, proj_ident, proj_trait_ident};

mod enums;
mod structs;
Expand Down Expand Up @@ -208,34 +208,14 @@ fn ensure_not_packed(item: &ItemStruct) -> Result<TokenStream> {
/// Determine the lifetime names. Ensure it doesn't overlap with any existing lifetime names.
fn proj_lifetime(generics: &Punctuated<GenericParam, Comma>) -> Lifetime {
let mut lifetime_name = String::from("'_pin");
let existing_lifetimes: Vec<String> = generics
.iter()
.filter_map(|param| {
if let GenericParam::Lifetime(LifetimeDef { lifetime, .. }) = param {
Some(lifetime.to_string())
} else {
None
}
})
.collect();
while existing_lifetimes.iter().any(|name| *name == lifetime_name) {
lifetime_name.push('_');
}
utils::proj_lifetime_name(&mut lifetime_name, generics);
Lifetime::new(&lifetime_name, Span::call_site())
}

/// Makes the generics of projected type from the reference of the original generics.
fn proj_generics(generics: &Generics, lifetime: &Lifetime) -> Generics {
let mut generics = generics.clone();
generics.params.insert(
0,
GenericParam::Lifetime(LifetimeDef {
attrs: Vec::new(),
lifetime: lifetime.clone(),
colon_token: None,
bounds: Punctuated::new(),
}),
);
utils::proj_generics(&mut generics, lifetime.clone());
generics
}

Expand Down Expand Up @@ -277,13 +257,32 @@ impl<'a> ImplDrop<'a> {
}
}
} else {
// If the user does not provide a pinned_drop 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 conflcit 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! {
impl #impl_generics ::core::ops::Drop for #ident #ty_generics #where_clause {
fn drop(&mut self) {
// Do nothing. The precense of this Drop
// impl ensures that the user can't provide one of their own
}
}
// 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 comflict with the explicit MustNotImplDrop impl below.
// This will result in a compilation error, which is exactly what we want
trait #trait_ident {}
impl<T: ::core::ops::Drop> #trait_ident for T {}
impl #impl_generics #trait_ident for #ident #ty_generics #where_clause {}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pin-project-internal/src/pinned_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn parse_arg(arg: &FnArg) -> Result<&Type> {
}
}

Err(error!(&arg, "#[pinned_drop] function must take a argument `Pin<&mut Type>`"))
Err(error!(arg, "#[pinned_drop] function must take a argument `Pin<&mut Type>`"))
}

fn parse(input: TokenStream) -> Result<TokenStream> {
Expand Down
42 changes: 39 additions & 3 deletions pin-project-internal/src/project.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use proc_macro2::TokenStream;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{
parse::Nothing,
Expand All @@ -8,7 +8,7 @@ use syn::{
*,
};

use crate::utils::{proj_ident, VecExt};
use crate::utils::{proj_generics, proj_ident, proj_lifetime_name, VecExt};

/// The attribute name.
const NAME: &str = "project";
Expand All @@ -25,6 +25,7 @@ fn parse(input: TokenStream) -> Result<TokenStream> {
}
Stmt::Local(local) => local.replace(&mut Register::default()),
Stmt::Item(Item::Fn(ItemFn { block, .. })) => Dummy.visit_block_mut(block),
Stmt::Item(Item::Impl(item)) => item.replace(&mut Register::default()),
_ => {}
}

Expand All @@ -36,6 +37,37 @@ trait Replace {
fn replace(&mut self, register: &mut Register);
}

impl Replace for ItemImpl {
fn replace(&mut self, _: &mut Register) {
let PathSegment { ident, arguments } = match &mut *self.self_ty {
Type::Path(TypePath { qself: None, path }) => path.segments.last_mut().unwrap(),
_ => return,
};

replace_ident(ident);

let mut lifetime_name = String::from("'_pin");
proj_lifetime_name(&mut lifetime_name, &self.generics.params);
self.items
.iter_mut()
.filter_map(|i| if let ImplItem::Method(i) = i { Some(i) } else { None })
.for_each(|item| proj_lifetime_name(&mut lifetime_name, &item.sig.generics.params));
let lifetime = Lifetime::new(&lifetime_name, Span::call_site());

proj_generics(&mut self.generics, syn::parse_quote!(#lifetime));

match arguments {
PathArguments::None => {
*arguments = PathArguments::AngleBracketed(syn::parse_quote!(<#lifetime>));
}
PathArguments::AngleBracketed(args) => {
args.args.insert(0, syn::parse_quote!(#lifetime));
}
PathArguments::Parenthesized(_) => unreachable!(),
}
}
}

impl Replace for Local {
fn replace(&mut self, register: &mut Register) {
self.pat.replace(register);
Expand Down Expand Up @@ -83,11 +115,15 @@ impl Replace for Path {

if register.0.is_none() || register.eq(&self.segments[0].ident, len) {
register.update(&self.segments[0].ident, len);
self.segments[0].ident = proj_ident(&self.segments[0].ident)
replace_ident(&mut self.segments[0].ident);
}
}
}

fn replace_ident(ident: &mut Ident) {
*ident = proj_ident(ident);
}

#[derive(Default)]
struct Register(Option<(Ident, usize)>);

Expand Down
49 changes: 46 additions & 3 deletions pin-project-internal/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use proc_macro2::Ident;
use quote::format_ident;
use syn::Attribute;
use syn::{
punctuated::Punctuated,
token::{self, Comma},
Attribute, GenericParam, Generics, Ident, Lifetime, LifetimeDef,
};

/// Makes the ident of projected type from the reference of the original ident.
pub(crate) fn proj_ident(ident: &Ident) -> Ident {
Expand All @@ -11,6 +14,46 @@ pub(crate) fn proj_trait_ident(ident: &Ident) -> Ident {
format_ident!("__{}ProjectionTrait", ident)
}

/// Determine the lifetime names. Ensure it doesn't overlap with any existing lifetime names.
pub(crate) fn proj_lifetime_name(
lifetime_name: &mut String,
generics: &Punctuated<GenericParam, Comma>,
) {
let existing_lifetimes: Vec<String> = generics
.iter()
.filter_map(|param| {
if let GenericParam::Lifetime(LifetimeDef { lifetime, .. }) = param {
Some(lifetime.to_string())
} else {
None
}
})
.collect();
while existing_lifetimes.iter().any(|name| name.starts_with(&**lifetime_name)) {
lifetime_name.push('_');
}
}

/// Makes the generics of projected type from the reference of the original generics.
pub(crate) fn proj_generics(generics: &mut Generics, lifetime: Lifetime) {
if let lt_token @ None = &mut generics.lt_token {
*lt_token = Some(token::Lt::default())
}
if let gt_token @ None = &mut generics.gt_token {
*gt_token = Some(token::Gt::default())
}

generics.params.insert(
0,
GenericParam::Lifetime(LifetimeDef {
attrs: Vec::new(),
lifetime,
colon_token: None,
bounds: Punctuated::new(),
}),
);
}

pub(crate) trait VecExt {
fn find_remove(&mut self, ident: &str) -> Option<Attribute>;
}
Expand Down Expand Up @@ -48,7 +91,7 @@ pub(crate) fn crate_path() -> Ident {

macro_rules! error {
($span:expr, $msg:expr) => {
syn::Error::new_spanned($span, $msg)
syn::Error::new_spanned(&$span, $msg)
};
($span:expr, $($tt:tt)*) => {
error!($span, format!($($tt)*))
Expand Down
23 changes: 23 additions & 0 deletions tests/pin_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,29 @@ fn where_clause_and_associated_type_fields() {
}
}

#[test]
fn test_move_out() {
struct NotCopy;

#[pin_project]
struct Foo {
val: NotCopy,
}

let foo = Foo { val: NotCopy };
let _val: NotCopy = foo.val;

#[pin_project]
enum Bar {
Variant(NotCopy),
}

let bar = Bar::Variant(NotCopy);
let _val: NotCopy = match bar {
Bar::Variant(val) => val,
};
}

#[test]
fn trait_bounds_on_type_generics() {
// struct
Expand Down
Loading

0 comments on commit b5bc62b

Please sign in to comment.