diff options
author | Chih-Hung Hsieh <chh@google.com> | 2020-10-29 20:40:59 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-10-29 20:40:59 +0000 |
commit | c617f9a630d64222780a99b089a3539ef6b014bb (patch) | |
tree | 046d0eb65b1631264519144f629b13f5273268e0 | |
parent | 89d4308f5f0a39a6b9d635f959d3174cf6f3135e (diff) | |
parent | 1241efe6e7a49281272b375cf9dd17fbaba1a840 (diff) | |
download | pin-project-internal-c617f9a630d64222780a99b089a3539ef6b014bb.tar.gz |
Upgrade rust/crates/pin-project-internal to 1.0.1 am: 9018f669e4 am: 6781a0015d am: ec331065ae am: 1241efe6e7
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/pin-project-internal/+/1475360
Change-Id: I04ca53648cb9c46ae663e3e043212e01bbdc129d
-rw-r--r-- | Android.bp | 12 | ||||
-rw-r--r-- | Cargo.toml | 10 | ||||
-rw-r--r-- | Cargo.toml.orig | 10 | ||||
-rw-r--r-- | LICENSE-APACHE | 25 | ||||
-rw-r--r-- | METADATA | 10 | ||||
-rw-r--r-- | TEST_MAPPING | 1 | ||||
-rw-r--r-- | build.rs | 34 | ||||
-rw-r--r-- | src/lib.rs | 378 | ||||
-rw-r--r-- | src/pin_project/args.rs | 257 | ||||
-rw-r--r-- | src/pin_project/derive.rs | 1911 | ||||
-rw-r--r-- | src/pin_project/mod.rs | 1 | ||||
-rw-r--r-- | src/pinned_drop.rs | 217 | ||||
-rw-r--r-- | src/project.rs | 350 | ||||
-rw-r--r-- | src/utils.rs | 56 |
14 files changed, 1380 insertions, 1892 deletions
@@ -5,10 +5,6 @@ rust_proc_macro { crate_name: "pin_project_internal", srcs: ["src/lib.rs"], edition: "2018", - flags: [ - "--cfg deprecated_proc_macro", - "--cfg underscore_consts", - ], rustlibs: [ "libproc_macro2", "libquote", @@ -23,10 +19,6 @@ rust_test_host { test_suites: ["general-tests"], auto_gen_config: true, edition: "2018", - flags: [ - "--cfg deprecated_proc_macro", - "--cfg underscore_consts", - ], rustlibs: [ "libproc_macro2", "libquote", @@ -35,7 +27,7 @@ rust_test_host { } // dependent_library ["feature_list"] -// proc-macro2-1.0.18 "default,proc-macro" +// proc-macro2-1.0.24 "default,proc-macro" // quote-1.0.7 "default,proc-macro" -// syn-1.0.33 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut" +// syn-1.0.48 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut" // unicode-xid-0.2.1 "default" @@ -13,9 +13,9 @@ [package] edition = "2018" name = "pin-project-internal" -version = "0.4.23" +version = "1.0.1" authors = ["Taiki Endo <te316e89@gmail.com>"] -description = "An internal crate to support pin_project - do not use directly\n" +description = "Implementation detail of the `pin-project` crate.\n" homepage = "https://github.com/taiki-e/pin-project" documentation = "https://docs.rs/pin-project-internal" keywords = ["pin", "macros", "attribute"] @@ -28,11 +28,11 @@ targets = ["x86_64-unknown-linux-gnu"] [lib] proc-macro = true [dependencies.proc-macro2] -version = "1.0" +version = "1" [dependencies.quote] -version = "1.0" +version = "1" [dependencies.syn] -version = "1.0.13" +version = "1.0.44" features = ["full", "visit-mut"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index ac5e137..4a4d1d2 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "pin-project-internal" -version = "0.4.23" +version = "1.0.1" authors = ["Taiki Endo <te316e89@gmail.com>"] edition = "2018" license = "Apache-2.0 OR MIT" @@ -10,7 +10,7 @@ documentation = "https://docs.rs/pin-project-internal" keywords = ["pin", "macros", "attribute"] categories = ["no-std", "rust-patterns"] description = """ -An internal crate to support pin_project - do not use directly +Implementation detail of the `pin-project` crate. """ [package.metadata.docs.rs] @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro2 = "1.0" -quote = "1.0" -syn = { version = "1.0.13", features = ["full", "visit-mut"] } +proc-macro2 = "1" +quote = "1" +syn = { version = "1.0.44", features = ["full", "visit-mut"] } diff --git a/LICENSE-APACHE b/LICENSE-APACHE index d645695..f433b1a 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -175,28 +175,3 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. @@ -1,5 +1,5 @@ name: "pin-project-internal" -description: "An internal crate to support pin_project - do not use directly" +description: "Implementation detail of the `pin-project` crate." third_party { url { type: HOMEPAGE @@ -7,13 +7,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/pin-project-internal/pin-project-internal-0.4.23.crate" + value: "https://static.crates.io/crates/pin-project-internal/pin-project-internal-1.0.1.crate" } - version: "0.4.23" + version: "1.0.1" license_type: NOTICE last_upgrade_date { year: 2020 - month: 7 - day: 27 + month: 10 + day: 26 } } diff --git a/TEST_MAPPING b/TEST_MAPPING index fd2153b..787bcd5 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,3 +1,4 @@ +// Generated by cargo2android.py for tests in Android.bp { "presubmit": [ { diff --git a/build.rs b/build.rs deleted file mode 100644 index b4d314c..0000000 --- a/build.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::{env, process::Command, str}; - -// The rustc-cfg strings below are *not* public API. Please let us know by -// opening a GitHub issue if your build environment requires some way to enable -// these cfgs other than by executing our build script. -fn main() { - let minor = match rustc_minor_version() { - Some(minor) => minor, - None => return, - }; - - // Underscore const names requires Rust 1.37: - // https://github.com/rust-lang/rust/pull/61347 - if minor >= 37 { - println!("cargo:rustc-cfg=underscore_consts"); - } - - // #[deprecated] on proc-macro requires Rust 1.40: - // https://github.com/rust-lang/rust/pull/65666 - if minor >= 40 { - println!("cargo:rustc-cfg=deprecated_proc_macro"); - } -} - -fn rustc_minor_version() -> Option<u32> { - let rustc = env::var_os("RUSTC")?; - let output = Command::new(rustc).arg("--version").output().ok()?; - let version = str::from_utf8(&output.stdout).ok()?; - let mut pieces = version.split('.'); - if pieces.next() != Some("rustc 1") { - return None; - } - pieces.next()?.parse().ok() -} @@ -1,15 +1,19 @@ -//! An internal crate to support pin_project - **do not use directly** +//! Implementation detail of the `pin-project` crate. - **do not use directly** -#![doc(html_root_url = "https://docs.rs/pin-project-internal/0.4.23")] +#![doc(html_root_url = "https://docs.rs/pin-project-internal/1.0.1")] #![doc(test( no_crate_inject, attr(deny(warnings, rust_2018_idioms, single_use_lifetimes), allow(dead_code)) ))] #![warn(unsafe_code)] -#![warn(rust_2018_idioms, single_use_lifetimes, unreachable_pub)] +#![warn(future_incompatible, rust_2018_idioms, single_use_lifetimes, unreachable_pub)] #![warn(clippy::all, clippy::default_trait_access)] -// mem::take and #[non_exhaustive] requires Rust 1.40 -#![allow(clippy::mem_replace_with_default, clippy::manual_non_exhaustive)] +// mem::take and #[non_exhaustive] requires Rust 1.40, matches! requires Rust 1.42 +#![allow( + clippy::mem_replace_with_default, + clippy::manual_non_exhaustive, + clippy::match_like_matches_macro +)] #![allow(clippy::needless_doctest_main)] // older compilers require explicit `extern crate`. @@ -21,12 +25,9 @@ mod utils; mod pin_project; mod pinned_drop; -mod project; use proc_macro::TokenStream; -use crate::utils::ProjKind; - /// An attribute that creates projection types covering all the fields of /// struct or enum. /// @@ -49,23 +50,25 @@ use crate::utils::ProjKind; /// ``` /// /// 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; +/// } +/// } /// } /// } /// ``` @@ -73,10 +76,15 @@ use crate::utils::ProjKind; /// Note that the projection types returned by `project` and `project_ref` have /// an additional lifetime at the beginning of generics. /// -/// The visibility of the projected type and projection method is based on the +/// ```text +/// let this: EnumProj<'_, T> = self.project(); +/// ^^ +/// ``` +/// +/// The visibility of the projected types and projection methods 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 -/// `pub(crate)`. +/// visibility of the projected types and the projection methods is downgraded +/// to `pub(crate)`. /// /// # Safety /// @@ -109,6 +117,7 @@ use crate::utils::ProjKind; /// ```rust /// struct MyStruct {} /// trait MyStructMustNotImplDrop {} +/// # #[allow(unknown_lints, drop_bounds)] /// impl<T: Drop> MyStructMustNotImplDrop for T {} /// impl MyStructMustNotImplDrop for MyStruct {} /// ``` @@ -122,7 +131,7 @@ use crate::utils::ProjKind; /// your struct - that is, [`Pin`]`<&mut MyStruct>` where `MyStruct` is the /// type of your struct. /// -/// You can call `project()` on this type as usual, along with any other +/// You can call `.project()` on this type as usual, along with any other /// methods you have defined. Because your code is never provided with /// a `&mut MyStruct`, it is impossible to move out of pin-projectable /// fields in safe code in your destructor. @@ -148,8 +157,8 @@ use crate::utils::ProjKind; /// code. /// /// Pin projections are also incompatible with [`#[repr(packed)]`][repr-packed] -/// structs. Attempting to use this attribute on a -/// [`#[repr(packed)]`][repr-packed] struct results in a compile-time error. +/// types. Attempting to use this attribute on a `#[repr(packed)]` type results +/// in a compile-time error. /// /// # Examples /// @@ -220,7 +229,33 @@ use crate::utils::ProjKind; /// } /// ``` /// -/// If you want to call the `project()` method multiple times or later use the +/// 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 the `project` method is generated, +/// and the `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 `.project()` multiple times or later use the /// original [`Pin`] type, it needs to use [`.as_mut()`][`Pin::as_mut`] to avoid /// consuming the [`Pin`]. /// @@ -339,14 +374,14 @@ use crate::utils::ProjKind; /// use std::{fmt::Debug, pin::Pin}; /// /// #[pin_project(PinnedDrop)] -/// struct Struct<T: Debug, U: Debug> { +/// struct PrintOnDrop<T: Debug, U: Debug> { /// #[pin] /// pinned_field: T, /// unpin_field: U, /// } /// /// #[pinned_drop] -/// impl<T: Debug, U: Debug> PinnedDrop for Struct<T, U> { +/// impl<T: Debug, U: Debug> PinnedDrop for PrintOnDrop<T, U> { /// fn drop(self: Pin<&mut Self>) { /// println!("Dropping pinned field: {:?}", self.pinned_field); /// println!("Dropping unpin field: {:?}", self.unpin_field); @@ -354,17 +389,17 @@ use crate::utils::ProjKind; /// } /// /// fn main() { -/// let _x = Struct { pinned_field: true, unpin_field: 40 }; +/// let _x = PrintOnDrop { pinned_field: true, unpin_field: 40 }; /// } /// ``` /// -/// See also [`#[pinned_drop]`][`pinned_drop`] attribute. +/// See also [`#[pinned_drop]`][macro@pinned_drop] attribute. /// -/// # `project_replace()` +/// # `project_replace` method /// -/// In addition to the `project()` and `project_ref()` methods which are always +/// In addition to the `project` and `project_ref` methods which are always /// provided when you use the `#[pin_project]` attribute, there is a third -/// method, `project_replace()` which can be useful in some situations. It is +/// method, `project_replace` which can be useful in some situations. It is /// equivalent to [`Pin::set`], except that the unpinned fields are moved and /// returned, instead of being dropped in-place. /// @@ -407,8 +442,8 @@ use crate::utils::ProjKind; /// ``` /// /// By passing the value to the `project_replace` argument, you can name the -/// returned type of `project_replace()`. This is necessary whenever -/// destructuring the return type of `project_replace()`, and work in exactly +/// returned type of the `project_replace` method. This is necessary whenever +/// destructuring the return type of the `project_replace` method, and work in exactly /// the same way as the `project` and `project_ref` arguments. /// /// ```rust @@ -437,26 +472,25 @@ use crate::utils::ProjKind; /// [`Pin::as_mut`]: core::pin::Pin::as_mut /// [`Pin::set`]: core::pin::Pin::set /// [`Pin`]: core::pin::Pin -/// [`UnsafeUnpin`]: https://docs.rs/pin-project/0.4/pin_project/trait.UnsafeUnpin.html -/// [`pinned_drop`]: ./attr.pinned_drop.html -/// [drop-guarantee]: https://doc.rust-lang.org/nightly/std/pin/index.html#drop-guarantee -/// [pin-projection]: https://doc.rust-lang.org/nightly/std/pin/index.html#projections-and-structural-pinning -/// [pinned-drop]: ./attr.pin_project.html#pinned_drop +/// [`UnsafeUnpin`]: https://docs.rs/pin-project/1/pin_project/trait.UnsafeUnpin.html +/// [drop-guarantee]: core::pin#drop-guarantee +/// [pin-projection]: core::pin#projections-and-structural-pinning +/// [pinned-drop]: macro@pin_project#pinned_drop /// [repr-packed]: https://doc.rust-lang.org/nomicon/other-reprs.html#reprpacked /// [undefined-behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html -/// [unsafe-unpin]: ./attr.pin_project.html#unsafeunpin +/// [unsafe-unpin]: macro@pin_project#unsafeunpin #[proc_macro_attribute] pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream { pin_project::attribute(&args.into(), input.into()).into() } -/// An attribute for annotating an impl block that implements [`Drop`]. +/// An attribute used for custom implementations of [`Drop`]. /// -/// This attribute is only needed when you wish to provide a [`Drop`] -/// impl for your type. +/// This attribute is used in conjunction with the `PinnedDrop` argument to +/// the [`#[pin_project]`][macro@pin_project] attribute. /// -/// This impl block acts just like a normal [`Drop`] impl, -/// except for the following two: +/// The impl block annotated with this attribute acts just like a normal +/// [`Drop`] impl, except for the following two: /// /// * `drop` method takes [`Pin`]`<&mut Self>` /// * Name of the trait is `PinnedDrop`. @@ -475,27 +509,27 @@ pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream { /// In particular, it will never be called more than once, just like /// [`Drop::drop`]. /// -/// # Example +/// # Examples /// /// ```rust /// use pin_project::{pin_project, pinned_drop}; /// use std::pin::Pin; /// /// #[pin_project(PinnedDrop)] -/// struct Foo { +/// struct PrintOnDrop { /// #[pin] /// field: u8, /// } /// /// #[pinned_drop] -/// impl PinnedDrop for Foo { +/// impl PinnedDrop for PrintOnDrop { /// fn drop(self: Pin<&mut Self>) { /// println!("Dropping: {}", self.field); /// } /// } /// /// fn main() { -/// let _x = Foo { field: 50 }; +/// let _x = PrintOnDrop { field: 50 }; /// } /// ``` /// @@ -504,13 +538,13 @@ pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream { /// # Why `#[pinned_drop]` attribute is needed? /// /// Implementing `PinnedDrop::drop` is safe, but calling it is not safe. -// This is because destructors can be called multiple times in safe code and -/// [double dropping is unsound](https://github.com/rust-lang/rust/pull/62360). +/// This is because destructors can be called multiple times in safe code and +/// [double dropping is unsound][rust-lang/rust#62360]. /// /// Ideally, it would be desirable to be able to forbid manual calls in /// the same way as [`Drop::drop`], but the library cannot do it. So, by using -/// macros and replacing them with private traits like the following, we prevent -/// users from calling `PinnedDrop::drop` in safe code. +/// macros and replacing them with private traits like the following, +/// this crate prevent users from calling `PinnedDrop::drop` in safe code. /// /// ```rust /// # use std::pin::Pin; @@ -523,244 +557,16 @@ pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream { /// Also by using the [`drop`] function just like dropping a type that directly /// implements [`Drop`], can drop safely a type that implements `PinnedDrop`. /// +/// [rust-lang/rust#62360]: https://github.com/rust-lang/rust/pull/62360 /// [`Pin`]: core::pin::Pin -/// [pinned-drop]: ./attr.pin_project.html#pinned_drop +/// [pinned-drop]: macro@pin_project#pinned_drop #[proc_macro_attribute] pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input); pinned_drop::attribute(&args.into(), input).into() } -/// (deprecated) An attribute to provide way to refer to the projected type returned by -/// `project` method. -/// -/// **This attribute is deprecated. Consider naming projected type by passing -/// `project` argument to `#[pin_project]` attribute instead, see [release note] -/// for details** -/// -/// The following syntaxes are supported. -/// -/// # `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 -/// # #![allow(deprecated)] -/// use pin_project::{pin_project, project}; -/// use std::pin::Pin; -/// -/// #[pin_project] -/// struct Foo<T, U> { -/// #[pin] -/// future: T, -/// field: U, -/// } -/// -/// impl<T, U> Foo<T, U> { -/// #[project] // Nightly does not need a dummy attribute to the function. -/// fn baz(self: Pin<&mut Self>) { -/// #[project] -/// let Foo { future, field } = self.project(); -/// -/// let _: Pin<&mut T> = future; -/// let _: &mut U = field; -/// } -/// } -/// ``` -/// -/// # `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 -/// # #![allow(deprecated)] -/// use pin_project::{pin_project, project}; -/// use std::pin::Pin; -/// -/// #[pin_project] -/// enum Enum<A, B, C> { -/// Tuple(#[pin] A, B), -/// Struct { field: C }, -/// Unit, -/// } -/// -/// impl<A, B, C> Enum<A, B, C> { -/// #[project] // Nightly does not need a dummy attribute to the function. -/// fn baz(self: Pin<&mut Self>) { -/// #[project] -/// match self.project() { -/// Enum::Tuple(x, y) => { -/// let _: Pin<&mut A> = x; -/// let _: &mut B = y; -/// } -/// Enum::Struct { field } => { -/// let _: &mut C = field; -/// } -/// Enum::Unit => {} -/// } -/// } -/// } -/// ``` -/// -/// # `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. -/// -/// To call a method implemented in `#[project] impl` block, you need to first -/// get the projected-type with `let this = self.project();`. -/// -/// ## Examples -/// -/// ```rust -/// # #![allow(deprecated)] -/// 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(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; -/// } -/// } -/// ``` -/// -/// # `use` statements -/// -/// ## Examples -/// -/// ```rust -/// # #![allow(deprecated)] -/// # mod dox { -/// use pin_project::pin_project; -/// -/// #[pin_project] -/// struct Foo<A> { -/// #[pin] -/// field: A, -/// } -/// -/// mod bar { -/// use super::Foo; -/// use pin_project::project; -/// use std::pin::Pin; -/// -/// #[project] -/// use super::Foo; -/// -/// #[project] -/// fn baz<A>(foo: Pin<&mut Foo<A>>) { -/// #[project] -/// let Foo { field } = foo.project(); -/// let _: Pin<&mut A> = field; -/// } -/// } -/// # } -/// ``` -/// -/// [release note]: https://github.com/taiki-e/pin-project/releases/tag/v0.4.21 -#[cfg_attr( - deprecated_proc_macro, - deprecated( - since = "0.4.21", - note = "consider naming projected type by passing `project` \ - argument to #[pin_project] attribute instead, see release note \ - <https://github.com/taiki-e/pin-project/releases/tag/v0.4.21> \ - for details" - ) -)] -#[proc_macro_attribute] -pub fn project(args: TokenStream, input: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(input); - project::attribute(&args.into(), input, ProjKind::Mutable).into() -} - -/// (deprecated) An attribute to provide way to refer to the projected type returned by -/// `project_ref` method. -/// -/// **This attribute is deprecated. Consider naming projected type by passing -/// `project_ref` argument to `#[pin_project]` attribute instead, see [release note] -/// for details** -/// -/// This is the same as [`#[project]`][`project`] attribute except it refers to -/// the projected type returned by the `project_ref` method. -/// -/// See [`#[project]`][`project`] attribute for more details. -/// -/// [release note]: https://github.com/taiki-e/pin-project/releases/tag/v0.4.21 -/// [`project`]: ./attr.project.html -#[cfg_attr( - deprecated_proc_macro, - deprecated( - since = "0.4.21", - note = "consider naming projected type by passing `project_ref` \ - argument to #[pin_project] attribute instead, see release note \ - <https://github.com/taiki-e/pin-project/releases/tag/v0.4.21> \ - for details" - ) -)] -#[proc_macro_attribute] -pub fn project_ref(args: TokenStream, input: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(input); - project::attribute(&args.into(), input, ProjKind::Immutable).into() -} - -/// (deprecated) An attribute to provide way to refer to the projected type returned by -/// `project_replace` method. -/// -/// **This attribute is deprecated. Consider naming projected type by passing -/// `project_replace` argument to `#[pin_project]` attribute instead, see [release note] -/// for details** -/// -/// This is the same as [`#[project]`][`project`] attribute except it refers to -/// the projected type returned by the `project_replace` method. -/// -/// See [`#[project]`][`project`] attribute for more details. -/// -/// [release note]: https://github.com/taiki-e/pin-project/releases/tag/v0.4.21 -/// [`project`]: ./attr.project.html -#[cfg_attr( - deprecated_proc_macro, - deprecated( - since = "0.4.21", - note = "consider naming projected type by passing `project_replace` \ - argument to #[pin_project] attribute instead, see release note \ - <https://github.com/taiki-e/pin-project/releases/tag/v0.4.21> \ - for details" - ) -)] -#[proc_macro_attribute] -pub fn project_replace(args: TokenStream, input: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(input); - project::attribute(&args.into(), input, ProjKind::Owned).into() -} - -// An internal helper macro. Not public API. +// Not public API. #[doc(hidden)] #[proc_macro_derive(__PinProjectInternalDerive, attributes(pin))] pub fn __pin_project_internal_derive(input: TokenStream) -> TokenStream { diff --git a/src/pin_project/args.rs b/src/pin_project/args.rs new file mode 100644 index 0000000..38e43d4 --- /dev/null +++ b/src/pin_project/args.rs @@ -0,0 +1,257 @@ +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned, + *, +}; + +use super::PIN; +use crate::utils::{ParseBufferExt, SliceExt}; + +pub(super) fn parse_args(attrs: &[Attribute]) -> Result<Args> { + // `(__private(<args>))` -> `<args>` + struct Input(Option<TokenStream>); + + impl Parse for Input { + fn parse(input: ParseStream<'_>) -> Result<Self> { + Ok(Self((|| { + let content = input.parenthesized().ok()?; + let private = content.parse::<Ident>().ok()?; + if private == "__private" { + content.parenthesized().ok()?.parse::<TokenStream>().ok() + } else { + None + } + })())) + } + } + + if let Some(attr) = attrs.find("pin_project") { + return Err(error!(attr, "duplicate #[pin_project] attribute")); + } + + let mut attrs = attrs.iter().filter(|attr| attr.path.is_ident(PIN)); + + let prev = if let Some(attr) = attrs.next() { + (attr, syn::parse2::<Input>(attr.tokens.clone()).unwrap().0) + } else { + // This only fails if another macro removes `#[pin]`. + return Err(error!(TokenStream::new(), "#[pin_project] attribute has been removed")); + }; + + if let Some(attr) = attrs.next() { + let (prev_attr, prev_res) = &prev; + // As the `#[pin]` attribute generated by `#[pin_project]` + // has the same span as `#[pin_project]`, it is possible + // that a useless error message will be generated. + let res = syn::parse2::<Input>(attr.tokens.clone()).unwrap().0; + let span = match (prev_res, res) { + (Some(_), _) => attr, + (_, Some(_)) => prev_attr, + (None, None) => prev_attr, + }; + Err(error!(span, "duplicate #[pin] attribute")) + } else { + // This `unwrap` only fails if another macro removes `#[pin]` and inserts own `#[pin]`. + syn::parse2(prev.1.unwrap()) + } +} + +pub(super) struct Args { + /// `PinnedDrop` argument. + pub(super) pinned_drop: Option<Span>, + /// `UnsafeUnpin` or `!Unpin` argument. + pub(super) unpin_impl: UnpinImpl, + /// `project = <ident>` argument. + pub(super) project: Option<Ident>, + /// `project_ref = <ident>` argument. + pub(super) project_ref: Option<Ident>, + /// `project_replace [= <ident>]` argument. + pub(super) project_replace: ProjReplace, +} + +impl Parse for Args { + fn parse(input: ParseStream<'_>) -> Result<Self> { + mod kw { + syn::custom_keyword!(Unpin); + } + + /// Parses `= <value>` in `<name> = <value>` and returns value and span of name-value pair. + fn parse_value( + input: ParseStream<'_>, + name: &Ident, + has_prev: bool, + ) -> Result<(Ident, TokenStream)> { + if input.is_empty() { + return Err(error!(name, "expected `{0} = <identifier>`, found `{0}`", name)); + } + let eq_token: Token![=] = input.parse()?; + if input.is_empty() { + let span = quote!(#name #eq_token); + return Err(error!(span, "expected `{0} = <identifier>`, found `{0} =`", name)); + } + let value: Ident = input.parse()?; + let span = quote!(#name #value); + if has_prev { + Err(error!(span, "duplicate `{}` argument", name)) + } else { + Ok((value, span)) + } + } + + let mut pinned_drop = None; + let mut unsafe_unpin = None; + let mut not_unpin = None; + let mut project = None; + let mut project_ref = None; + let mut project_replace_value = None; + let mut project_replace_span = None; + + while !input.is_empty() { + if input.peek(Token![!]) { + let bang: Token![!] = input.parse()?; + if input.is_empty() { + return Err(error!(bang, "expected `!Unpin`, found `!`")); + } + let unpin: kw::Unpin = input.parse()?; + let span = quote!(#bang #unpin); + if not_unpin.replace(span.span()).is_some() { + return Err(error!(span, "duplicate `!Unpin` argument")); + } + } else { + let token = input.parse::<Ident>()?; + match &*token.to_string() { + "PinnedDrop" => { + if pinned_drop.replace(token.span()).is_some() { + return Err(error!(token, "duplicate `PinnedDrop` argument")); + } + } + "UnsafeUnpin" => { + if unsafe_unpin.replace(token.span()).is_some() { + return Err(error!(token, "duplicate `UnsafeUnpin` argument")); + } + } + "project" => { + project = Some(parse_value(input, &token, project.is_some())?.0); + } + "project_ref" => { + project_ref = Some(parse_value(input, &token, project_ref.is_some())?.0); + } + "project_replace" => { + if input.peek(Token![=]) { + let (value, span) = + parse_value(input, &token, project_replace_span.is_some())?; + project_replace_value = Some(value); + project_replace_span = Some(span.span()); + } else if project_replace_span.is_some() { + return Err(error!(token, "duplicate `project_replace` argument")); + } else { + project_replace_span = Some(token.span()); + } + } + "Replace" => { + return Err(error!( + token, + "`Replace` argument was removed, use `project_replace` argument instead" + )); + } + _ => return Err(error!(token, "unexpected argument: {}", token)), + } + } + + if input.is_empty() { + break; + } + let _: Token![,] = input.parse()?; + } + + if project.is_some() || project_ref.is_some() { + if project == project_ref { + return Err(error!( + project_ref, + "name `{}` is already specified by `project` argument", + project_ref.as_ref().unwrap() + )); + } + if let Some(ident) = &project_replace_value { + if project == project_replace_value { + return Err(error!( + ident, + "name `{}` is already specified by `project` argument", ident + )); + } else if project_ref == project_replace_value { + return Err(error!( + ident, + "name `{}` is already specified by `project_ref` argument", ident + )); + } + } + } + + if let Some(span) = pinned_drop { + if project_replace_span.is_some() { + return Err(Error::new( + span, + "arguments `PinnedDrop` and `project_replace` are mutually exclusive", + )); + } + } + let project_replace = match (project_replace_span, project_replace_value) { + (None, _) => ProjReplace::None, + (Some(span), Some(ident)) => ProjReplace::Named { ident, span }, + (Some(span), None) => ProjReplace::Unnamed { span }, + }; + let unpin_impl = match (unsafe_unpin, not_unpin) { + (None, None) => UnpinImpl::Default, + (Some(span), None) => UnpinImpl::Unsafe(span), + (None, Some(span)) => UnpinImpl::Negative(span), + (Some(span), Some(_)) => { + return Err(Error::new( + span, + "arguments `UnsafeUnpin` and `!Unpin` are mutually exclusive", + )); + } + }; + + Ok(Self { pinned_drop, unpin_impl, project, project_ref, project_replace }) + } +} + +/// `UnsafeUnpin` or `!Unpin` argument. +#[derive(Clone, Copy)] +pub(super) enum UnpinImpl { + Default, + /// `UnsafeUnpin`. + Unsafe(Span), + /// `!Unpin`. + Negative(Span), +} + +/// `project_replace [= <ident>]` argument. +pub(super) enum ProjReplace { + None, + /// `project_replace`. + Unnamed { + span: Span, + }, + /// `project_replace = <ident>`. + Named { + span: Span, + ident: Ident, + }, +} + +impl ProjReplace { + /// Return the span of this argument. + pub(super) fn span(&self) -> Option<Span> { + match self { + ProjReplace::None => None, + ProjReplace::Named { span, .. } | ProjReplace::Unnamed { span, .. } => Some(*span), + } + } + + pub(super) fn ident(&self) -> Option<&Ident> { + if let ProjReplace::Named { ident, .. } = self { Some(ident) } else { None } + } +} diff --git a/src/pin_project/derive.rs b/src/pin_project/derive.rs index 56a3d47..1a598b7 100644 --- a/src/pin_project/derive.rs +++ b/src/pin_project/derive.rs @@ -1,21 +1,22 @@ use proc_macro2::{Delimiter, Group, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; -use syn::{ - parse::{Parse, ParseStream}, - spanned::Spanned, - visit_mut::VisitMut, - *, -}; +use syn::{visit_mut::VisitMut, *}; -use super::PIN; +use super::{ + args::{parse_args, Args, ProjReplace, UnpinImpl}, + PIN, +}; use crate::utils::{ - determine_lifetime_name, determine_visibility, insert_lifetime_and_bound, ParseBufferExt, - ProjKind, ReplaceReceiver, SliceExt, Variants, + 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); @@ -23,20 +24,14 @@ pub(super) fn parse_derive(input: TokenStream) -> Result<TokenStream> { visitor.visit_generics_mut(&mut input.generics); visitor.visit_data_mut(&mut input.data); - let mut cx = Context::new(&input.attrs, &input.vis, &input.ident, &mut input.generics)?; - let packed_check; - - let (mut items, scoped_items) = match &input.data { + match &input.data { Data::Struct(data) => { - // Do this first for a better error message. - packed_check = Some(cx.ensure_not_packed(&data.fields)?); - cx.parse_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) => { - // We don't need to check for `#[repr(packed)]`, - // since it does not apply to enums. - packed_check = None; - cx.parse_enum(data)? + cx = Context::new(&input.attrs, &input.vis, ident, &mut input.generics, Enum)?; + parse_enum(&mut cx, data, &mut generate)?; } Data::Union(_) => { return Err(error!( @@ -44,371 +39,101 @@ pub(super) fn parse_derive(input: TokenStream) -> Result<TokenStream> { "#[pin_project] attribute may only be used on structs or enums" )); } - }; - - let unpin_impl = cx.make_unpin_impl(); - let drop_impl = cx.make_drop_impl(); - let dummy_const = if cfg!(underscore_consts) { - format_ident!("_") - } else { - format_ident!("__SCOPE_{}", ident) - }; - items.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: - // * https://github.com/rust-lang/rust/issues/63281 - // * https://github.com/taiki-e/pin-project/pull/53#issuecomment-525906867 - // * https://github.com/taiki-e/pin-project/pull/70 - #[doc(hidden)] - #[allow(non_upper_case_globals)] - #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 - #[allow(clippy::used_underscore_binding)] - const #dummy_const: () = { - #scoped_items - #unpin_impl - #drop_impl - #packed_check - }; - }); - Ok(items) -} - -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 { Err(error!(ident, msg)) } else { Err(error!(fields, msg)) } - } else { - Ok(()) } -} -fn validate_enum(brace_token: token::Brace, variants: &Variants) -> Result<()> { - if variants.is_empty() { - return Err(Error::new( - brace_token.span, - "#[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 { - Err(error!(e, "#[pin_project] attribute may not be used on enums with discriminants")) - } else if let Some(attr) = v.attrs.find(PIN) { - Err(error!(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 { - Err(error!(variants, "#[pin_project] attribute may not be used on enums with zero fields")) - } + Ok(generate.into_tokens(&cx)) } -struct Args { - /// `PinnedDrop` argument. - pinned_drop: Option<Span>, - /// `UnsafeUnpin` or `!Unpin` argument. - unpin_impl: UnpinImpl, - /// `project = <ident>` argument. - project: Option<Ident>, - /// `project_ref = <ident>` argument. - project_ref: Option<Ident>, - /// `project_replace [= <ident>]` or `Replace` argument. - project_replace: ProjReplace, -} - -enum ProjReplace { - None, - /// `project_replace` or `Replace`. - Unnamed { - span: Span, - }, - /// `project_replace = <ident>`. - Named { - span: Span, - ident: Ident, - }, +#[derive(Default)] +struct GenerateTokens { + exposed: TokenStream, + scoped: TokenStream, } -impl ProjReplace { - fn span(&self) -> Option<Span> { - match self { - ProjReplace::None => None, - ProjReplace::Named { span, .. } | ProjReplace::Unnamed { span, .. } => Some(*span), +impl GenerateTokens { + fn extend(&mut self, expose: bool, tokens: TokenStream) { + if expose { + self.exposed.extend(tokens); + } else { + self.scoped.extend(tokens); } } - fn ident(&self) -> Option<&Ident> { - if let ProjReplace::Named { ident, .. } = self { Some(ident) } else { None } - } -} + fn into_tokens(self, cx: &Context<'_>) -> TokenStream { + let mut tokens = self.exposed; + let scoped = self.scoped; -const DUPLICATE_PIN: &str = "duplicate #[pin] attribute"; - -impl Args { - fn get(attrs: &[Attribute]) -> Result<Self> { - // `(__private(<args>))` -> `<args>` - struct Input(Option<TokenStream>); - - impl Parse for Input { - fn parse(input: ParseStream<'_>) -> Result<Self> { - Ok(Self((|| { - let content = input.parenthesized().ok()?; - let private = content.parse::<Ident>().ok()?; - if private == "__private" { - content.parenthesized().ok()?.parse::<TokenStream>().ok() - } else { - None - } - })())) - } - } + let unpin_impl = make_unpin_impl(cx); + let drop_impl = make_drop_impl(cx); + let allowed_lints = global_allowed_lints(); - if let Some(attr) = attrs.find("pin_project") { - return Err(error!(attr, "duplicate #[pin_project] attribute")); - } - - let mut attrs = attrs.iter().filter(|attr| attr.path.is_ident(PIN)); - - let prev = if let Some(attr) = attrs.next() { - (attr, syn::parse2::<Input>(attr.tokens.clone()).unwrap().0) - } else { - // This only fails if another macro removes `#[pin]`. - return Err(error!(TokenStream::new(), "#[pin_project] attribute has been removed")); - }; - - if let Some(attr) = attrs.next() { - let (prev_attr, prev_res) = &prev; - // As the `#[pin]` attribute generated by `#[pin_project]` - // has the same span as `#[pin_project]`, it is possible - // that a useless error message will be generated. - let res = syn::parse2::<Input>(attr.tokens.clone()).unwrap().0; - let span = match (&prev_res, res) { - (Some(_), _) => attr, - (_, Some(_)) => prev_attr, - (None, None) => prev_attr, + 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: + // * https://github.com/rust-lang/rust/issues/63281 + // * https://github.com/taiki-e/pin-project/pull/53#issuecomment-525906867 + // * https://github.com/taiki-e/pin-project/pull/70 + #allowed_lints + #[allow(clippy::used_underscore_binding)] + const _: () = { + #scoped + #unpin_impl + #drop_impl }; - Err(error!(span, DUPLICATE_PIN)) - } else { - // This `unwrap` only fails if another macro removes `#[pin]` and inserts own `#[pin]`. - syn::parse2(prev.1.unwrap()) - } + }); + tokens } } -impl Parse for Args { - fn parse(input: ParseStream<'_>) -> Result<Self> { - mod kw { - syn::custom_keyword!(Unpin); - } - - // Parses `= <value>` in `<name> = <value>` and returns value and span of name-value pair. - fn parse_value( - input: ParseStream<'_>, - name: &Ident, - has_prev: bool, - ) -> Result<(Ident, TokenStream)> { - if input.is_empty() { - return Err(error!(name, "expected `{0} = <identifier>`, found `{0}`", name)); - } - let eq_token: Token![=] = input.parse()?; - if input.is_empty() { - let span = quote!(#name #eq_token); - return Err(error!(span, "expected `{0} = <identifier>`, found `{0} =`", name)); - } - let value: Ident = input.parse()?; - let span = quote!(#name #value); - if has_prev { - Err(error!(span, "duplicate `{}` argument", name)) - } else { - Ok((value, span)) - } - } - - let mut pinned_drop = None; - let mut unsafe_unpin = None; - let mut not_unpin = None; - let mut project = None; - let mut project_ref = None; - - let mut replace = None; - let mut project_replace_value = None; - let mut project_replace_span = None; - - while !input.is_empty() { - if input.peek(Token![!]) { - let bang: Token![!] = input.parse()?; - if input.is_empty() { - return Err(error!(bang, "expected `!Unpin`, found `!`")); - } - let unpin: kw::Unpin = input.parse()?; - let span = quote!(#bang #unpin); - if not_unpin.replace(span.span()).is_some() { - return Err(error!(span, "duplicate `!Unpin` argument")); - } - } else { - let token = input.parse::<Ident>()?; - match &*token.to_string() { - "PinnedDrop" => { - if pinned_drop.replace(token.span()).is_some() { - return Err(error!(token, "duplicate `PinnedDrop` argument")); - } - } - "UnsafeUnpin" => { - if unsafe_unpin.replace(token.span()).is_some() { - return Err(error!(token, "duplicate `UnsafeUnpin` argument")); - } - } - "project" => { - project = Some(parse_value(input, &token, project.is_some())?.0); - } - "project_ref" => { - project_ref = Some(parse_value(input, &token, project_ref.is_some())?.0); - } - "project_replace" => { - if input.peek(Token![=]) { - let (value, span) = - parse_value(input, &token, project_replace_span.is_some())?; - project_replace_value = Some(value); - project_replace_span = Some(span.span()); - } else if project_replace_span.is_some() { - return Err(error!(token, "duplicate `project_replace` argument")); - } else { - project_replace_span = Some(token.span()); - } - } - "Replace" => { - if replace.replace(token.span()).is_some() { - return Err(error!(token, "duplicate `Replace` argument")); - } - } - _ => return Err(error!(token, "unexpected argument: {}", token)), - } - } - - if input.is_empty() { - break; - } - let _: Token![,] = input.parse()?; - } - - if project.is_some() || project_ref.is_some() { - if project == project_ref { - return Err(error!( - project_ref, - "name `{}` is already specified by `project` argument", - project_ref.as_ref().unwrap() - )); - } - if let Some(ident) = &project_replace_value { - if project == project_replace_value { - return Err(error!( - ident, - "name `{}` is already specified by `project` argument", ident - )); - } else if project_ref == project_replace_value { - return Err(error!( - ident, - "name `{}` is already specified by `project_ref` argument", ident - )); - } - } - } - - if let Some(span) = pinned_drop { - if project_replace_span.is_some() { - return Err(Error::new( - span, - "arguments `PinnedDrop` and `project_replace` are mutually exclusive", - )); - } else if replace.is_some() { - return Err(Error::new( - span, - "arguments `PinnedDrop` and `Replace` are mutually exclusive", - )); - } - } - let project_replace = match (project_replace_span, project_replace_value, replace) { - (None, _, None) => ProjReplace::None, - // If both `project_replace` and `Replace` are specified, - // We always prefer `project_replace`'s span, - (Some(span), Some(ident), _) => ProjReplace::Named { ident, span }, - (Some(span), ..) | (None, _, Some(span)) => ProjReplace::Unnamed { span }, - }; - let unpin_impl = match (unsafe_unpin, not_unpin) { - (None, None) => UnpinImpl::Default, - (Some(span), None) => UnpinImpl::Unsafe(span), - (None, Some(span)) => UnpinImpl::Negative(span), - (Some(span), Some(_)) => { - return Err(Error::new( - span, - "arguments `UnsafeUnpin` and `!Unpin` are mutually exclusive", - )); - } - }; - - Ok(Self { pinned_drop, unpin_impl, project, project_ref, project_replace }) +/// 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(explicit_outlives_requirements)] // https://github.com/rust-lang/rust/issues/60993 + #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 + #[allow(unreachable_pub)] // This lint warns `pub` field in private struct. + #[allow(clippy::pattern_type_mismatch)] + #[allow(clippy::redundant_pub_crate)] // This lint warns `pub(crate)` field in private 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, +/// Returns attributes used on projected types. +fn proj_allowed_lints(kind: TypeKind) -> (TokenStream, TokenStream, TokenStream) { + let large_enum_variant = if 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 = quote! { + #[allow(dead_code)] // This lint warns unused fields/variants. + #[allow(clippy::mut_mut)] // This lint warns `&mut &mut <ty>`. + #[allow(clippy::type_repetition_in_bounds)] // https://github.com/rust-lang/rust-clippy/issues/4326} + #global_allowed_lints + }; + let proj_ref = quote! { + #[allow(dead_code)] // This lint warns unused fields/variants. + #[allow(clippy::type_repetition_in_bounds)] // https://github.com/rust-lang/rust-clippy/issues/4326 + #global_allowed_lints + }; + let proj_own = quote! { + #[allow(dead_code)] // This lint warns unused fields/variants. + #large_enum_variant + #global_allowed_lints + }; + (proj_mut, proj_ref, proj_own) } struct Context<'a> { @@ -418,6 +143,8 @@ struct Context<'a> { proj: ProjectedType, /// Types of the pinned fields. pinned_fields: Vec<Type>, + /// Kind of the original type: struct or enum + kind: TypeKind, /// `PinnedDrop` argument. pinned_drop: Option<Span>, @@ -427,28 +154,28 @@ struct Context<'a> { project: bool, /// `project_ref` argument. project_ref: bool, - /// `project_replace [= <ident>]` or `Replace` argument. + /// `project_replace [= <ident>]` argument. project_replace: ProjReplace, } -#[derive(Clone, Copy)] -enum UnpinImpl { - Default, - /// `UnsafeUnpin`. - Unsafe(Span), - /// `!Unpin`. - Negative(Span), -} - 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 } = - Args::get(attrs)?; + 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) + { + return Err(error!(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); @@ -466,10 +193,13 @@ impl<'a> Context<'a> { let mut where_clause = generics.make_where_clause().clone(); where_clause.predicates.push(pred); - let own_ident = - project_replace.ident().cloned().unwrap_or_else(|| ProjKind::Owned.proj_ident(ident)); + 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(), @@ -477,8 +207,8 @@ impl<'a> Context<'a> { project_replace, proj: ProjectedType { vis: determine_visibility(vis), - mut_ident: project.unwrap_or_else(|| ProjKind::Mutable.proj_ident(ident)), - ref_ident: project_ref.unwrap_or_else(|| ProjKind::Immutable.proj_ident(ident)), + 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, @@ -488,739 +218,868 @@ impl<'a> Context<'a> { pinned_fields: Vec::new(), }) } +} - /// Returns attributes used on projected types. - fn proj_attrs(&self) -> (TokenStream, TokenStream, TokenStream) { - // If the user gave it a name, it should appear in the document. - let doc_attr = quote!(#[doc(hidden)]); - let doc_proj = if self.project { None } else { Some(&doc_attr) }; - let doc_proj_ref = if self.project_ref { None } else { Some(&doc_attr) }; - let doc_proj_own = - if self.project_replace.ident().is_some() { None } else { Some(&doc_attr) }; - - let proj_mut = quote! { - #doc_proj - #[allow(dead_code)] // This lint warns unused fields/variants. - #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 - #[allow(clippy::mut_mut)] // This lint warns `&mut &mut <ty>`. - #[allow(clippy::type_repetition_in_bounds)] // https://github.com/rust-lang/rust-clippy/issues/4326} - }; - let proj_ref = quote! { - #doc_proj_ref - #[allow(dead_code)] // This lint warns unused fields/variants. - #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 - #[allow(clippy::type_repetition_in_bounds)] // https://github.com/rust-lang/rust-clippy/issues/4326 - }; - let proj_own = quote! { - #doc_proj_own - #[allow(dead_code)] // This lint warns unused fields/variants. - #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 - #[allow(unreachable_pub)] // This lint warns `pub` field in private struct. - }; - (proj_mut, proj_ref, proj_own) - } +#[derive(Copy, Clone, Eq, PartialEq)] +enum TypeKind { + Enum, + Struct, +} - fn parse_struct( - &mut self, - DataStruct { fields, .. }: &DataStruct, - ) -> Result<(TokenStream, TokenStream)> { - validate_struct(self.orig.ident, fields)?; +use TypeKind::{Enum, Struct}; - let ProjectedFields { - proj_pat, - proj_body, - proj_fields, - proj_ref_fields, - proj_own_fields, - proj_own_body, - } = match fields { - Fields::Named(_) => self.visit_fields(None, fields, Delimiter::Brace)?, - Fields::Unnamed(_) => self.visit_fields(None, fields, Delimiter::Parenthesis)?, - Fields::Unit => unreachable!(), - }; +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, +} - let proj_ident = &self.proj.mut_ident; - let proj_ref_ident = &self.proj.ref_ident; - let proj_own_ident = &self.proj.own_ident; - let vis = &self.proj.vis; - let mut orig_generics = self.orig.generics.clone(); - let orig_where_clause = orig_generics.where_clause.take(); - let proj_generics = &self.proj.generics; - let proj_where_clause = &self.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!(), - }; +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, +} - let (proj_attrs, proj_ref_attrs, proj_own_attrs) = self.proj_attrs(); - let mut proj_items = quote! { - #proj_attrs - #vis struct #proj_ident #proj_generics #where_clause_fields - #proj_ref_attrs - #vis struct #proj_ref_ident #proj_generics #where_clause_ref_fields - }; - if self.project_replace.span().is_some() { - proj_items.extend(quote! { - #proj_own_attrs - #vis struct #proj_own_ident #orig_generics #where_clause_own_fields - }); +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 { Err(error!(ident, msg)) } else { Err(error!(fields, msg)) } + } else { + Ok(()) + } +} + +fn validate_enum(brace_token: token::Brace, variants: &Variants) -> Result<()> { + if variants.is_empty() { + return Err(Error::new( + brace_token.span, + "#[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 { + Err(error!(e, "#[pin_project] attribute may not be used on enums with discriminants")) + } else if let Some(attr) = v.attrs.find(PIN) { + Err(error!(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 { + Err(error!(variants, "#[pin_project] attribute may not be used on enums with zero 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_ptr: *mut Self = self.get_unchecked_mut(); - let Self #proj_pat = &mut *__self_ptr; - #proj_own_body - }; - let proj_impl = self.make_proj_impl(&proj_mut_body, &proj_ref_body, &proj_own_body); +fn parse_struct( + cx: &mut Context<'_>, + fields: &Fields, + generate: &mut GenerateTokens, +) -> Result<()> { + // Do this first for a better error message. + let packed_check = ensure_not_packed(&cx.orig, 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!(), + }; - Ok((proj_items, proj_impl)) + 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.kind); + 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_ptr: *mut Self = self.get_unchecked_mut(); + 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( + cx: &mut Context<'_>, + DataEnum { brace_token, variants, .. }: &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", + )); } - fn parse_enum( - &mut self, - DataEnum { brace_token, variants, .. }: &DataEnum, - ) -> Result<(TokenStream, TokenStream)> { - validate_enum(*brace_token, variants)?; - - let ProjectedVariants { - proj_variants, - proj_ref_variants, - proj_own_variants, - proj_arms, - proj_ref_arms, - proj_own_arms, - } = self.visit_variants(variants)?; - - let proj_ident = &self.proj.mut_ident; - let proj_ref_ident = &self.proj.ref_ident; - let proj_own_ident = &self.proj.own_ident; - let vis = &self.proj.vis; - let mut orig_generics = self.orig.generics.clone(); - let orig_where_clause = orig_generics.where_clause.take(); - let proj_generics = &self.proj.generics; - let proj_where_clause = &self.proj.where_clause; - - let (proj_attrs, proj_ref_attrs, proj_own_attrs) = self.proj_attrs(); - let mut proj_items = quote! { + // We don't need to check for `#[repr(packed)]`, + // since it does not apply to enums. + + 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.kind); + 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 self.project_replace.span().is_some() { - proj_items.extend(quote! { - #proj_own_attrs - #vis enum #proj_own_ident #orig_generics #orig_where_clause { - #proj_own_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! { + let __self_ptr: *mut Self = self.get_unchecked_mut(); + match &mut *__self_ptr { + #proj_own_arms + } + }; + generate.extend(false, make_proj_impl(cx, &proj_mut_body, &proj_ref_body, &proj_own_body)); - let proj_mut_body = quote! { - match self.get_unchecked_mut() { - #proj_arms - } + Ok(()) +} + +fn visit_variants(cx: &mut Context<'_>, variants: &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, &[]), + ..ProjectedFields::default() + }, }; - let proj_ref_body = quote! { - match self.get_ref() { - #proj_ref_arms + + let orig_ident = cx.orig.ident; + 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! { + #orig_ident::#ident #proj_pat => { + #proj_ident::#ident #proj_body } - }; - let proj_own_body = quote! { - let __self_ptr: *mut Self = self.get_unchecked_mut(); - match &mut *__self_ptr { - #proj_own_arms + }); + proj_ref_arms.extend(quote! { + #orig_ident::#ident #proj_pat => { + #proj_ref_ident::#ident #proj_body } - }; - let proj_impl = self.make_proj_impl(&proj_mut_body, &proj_ref_body, &proj_own_body); - - Ok((proj_items, proj_impl)) + }); + proj_own_arms.extend(quote! { + #orig_ident::#ident #proj_pat => { + #proj_own_body + } + }); } - fn visit_variants(&mut self, variants: &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(_) => self.visit_fields(Some(ident), fields, Delimiter::Brace)?, - Fields::Unnamed(_) => { - self.visit_fields(Some(ident), fields, Delimiter::Parenthesis)? - } - Fields::Unit => ProjectedFields { - proj_own_body: self.proj_own_body(Some(ident), None, &[]), - ..ProjectedFields::default() - }, - }; + Ok(ProjectedVariants { + proj_variants, + proj_ref_variants, + proj_own_variants, + proj_arms, + proj_ref_arms, + proj_own_arms, + }) +} - let orig_ident = self.orig.ident; - let proj_ident = &self.proj.mut_ident; - let proj_ref_ident = &self.proj.ref_ident; - proj_variants.extend(quote! { - #ident #proj_fields, +fn visit_fields( + cx: &mut Context<'_>, + variant_ident: Option<&Ident>, + fields: &Fields, + delim: Delimiter, +) -> Result<ProjectedFields> { + 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, .. }) in fields.iter().enumerate() { + let binding = ident.clone().unwrap_or_else(|| format_ident!("_{}", i)); + proj_pat.extend(quote!(#binding,)); + if attrs.position_exact(PIN)?.is_some() { + let lifetime = &cx.proj.lifetime; + proj_fields.extend(quote! { + #vis #ident #colon_token ::pin_project::__private::Pin<&#lifetime mut (#ty)>, }); - proj_ref_variants.extend(quote! { - #ident #proj_ref_fields, + proj_ref_fields.extend(quote! { + #vis #ident #colon_token ::pin_project::__private::Pin<&#lifetime (#ty)>, }); - proj_own_variants.extend(quote! { - #ident #proj_own_fields, + proj_own_fields.extend(quote! { + #vis #ident #colon_token ::pin_project::__private::PhantomData<#ty>, }); - proj_arms.extend(quote! { - #orig_ident::#ident #proj_pat => { - #proj_ident::#ident #proj_body - } + proj_body.extend(quote! { + #ident #colon_token ::pin_project::__private::Pin::new_unchecked(#binding), }); - proj_ref_arms.extend(quote! { - #orig_ident::#ident #proj_pat => { - #proj_ref_ident::#ident #proj_body - } + proj_move.extend(quote! { + #ident #colon_token ::pin_project::__private::PhantomData, }); - proj_own_arms.extend(quote! { - #orig_ident::#ident #proj_pat => { - #proj_own_body - } + + cx.pinned_fields.push(ty.clone()); + pinned_bindings.push(binding); + } else { + let lifetime = &cx.proj.lifetime; + 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), }); } - - Ok(ProjectedVariants { - proj_variants, - proj_ref_variants, - proj_own_variants, - proj_arms, - proj_ref_arms, - proj_own_arms, - }) } - fn visit_fields( - &mut self, - variant_ident: Option<&Ident>, - fields: &Fields, - delim: Delimiter, - ) -> Result<ProjectedFields> { - 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, .. }) in fields.iter().enumerate() { - let binding = ident.clone().unwrap_or_else(|| format_ident!("_{}", i)); - proj_pat.extend(quote!(#binding,)); - if attrs.position_exact(PIN)?.is_some() { - let lifetime = &self.proj.lifetime; - 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, - }); - - self.pinned_fields.push(ty.clone()); - pinned_bindings.push(binding); - } else { - let lifetime = &self.proj.lifetime; - 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), - }); - } - } + fn surround(delim: Delimiter, tokens: TokenStream) -> TokenStream { + Group::new(delim, tokens).into_token_stream() + } - fn surround(delim: Delimiter, tokens: TokenStream) -> TokenStream { - Group::new(delim, tokens).into_token_stream() - } + 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, + }) +} - 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); +/// 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), + }; - let proj_move = Group::new(delim, proj_move); - let proj_own_body = self.proj_own_body(variant_ident, Some(proj_move), &pinned_bindings); + // The fields of the struct and the active enum variant are dropped + // in declaration order. + // Refs: https://doc.rust-lang.org/reference/destructors.html + let pinned_fields = pinned_fields.iter().rev(); - Ok(ProjectedFields { - proj_pat, - proj_body, - proj_own_body, - proj_fields, - proj_ref_fields, - proj_own_fields, - }) - } + quote! { + // First, extract all the unpinned fields. + let __result = #proj_own #proj_move; - /// Generates the processing that `project_replace` does for the struct or each variant. - fn proj_own_body( - &self, - variant_ident: Option<&'a Ident>, - proj_move: Option<Group>, - pinned_fields: &[Ident], - ) -> TokenStream { - let ident = &self.proj.own_ident; - let proj_own = match variant_ident { - Some(variant_ident) => quote!(#ident::#variant_ident), - None => quote!(#ident), + // 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 { + target: __self_ptr, + value: ::pin_project::__private::ManuallyDrop::new(__replacement), }; - quote! { - // First, extract all the unpinned fields - let __result = #proj_own #proj_move; - - // 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 { - target: __self_ptr, - value: ::pin_project::__private::ManuallyDrop::new(__replacement), - }; - - // Now create guards to drop all the pinned fields - // - // Due to a compiler bug (https://github.com/rust-lang/rust/issues/47949) - // 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(#pinned_fields); - )* - } - - // Finally, return the result - __result + // Now create guards to drop all the pinned fields. + // + // Due to a compiler bug (https://github.com/rust-lang/rust/issues/47949) + // 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(#pinned_fields); + )* } - } - /// Creates `Unpin` implementation for original type. - fn make_unpin_impl(&self) -> TokenStream { - match self.unpin_impl { - UnpinImpl::Unsafe(span) => { - let mut proj_generics = self.proj.generics.clone(); - let orig_ident = self.orig.ident; - let lifetime = &self.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 = self.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 = self.proj.generics.clone(); - let orig_ident = self.orig.ident; - let lifetime = &self.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 (impl_generics, ty_generics, orig_where_clause) = - self.orig.generics.split_for_impl(); - - // 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 - { - } + // Finally, return the result. + __result + } +} - // 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. - #unsafety impl #impl_generics ::pin_project::UnsafeUnpin - for #orig_ident #ty_generics - #orig_where_clause - { - } - } - } - UnpinImpl::Default => { - let mut full_where_clause = self.orig.generics.where_clause.clone().unwrap(); - - // Generate a field in our new struct for every - // pinned field in the original type. - let fields = self.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 = self.orig.generics.lifetimes().enumerate().map( - |(i, LifetimeDef { lifetime, .. })| { - let field_ident = format_ident!("__lifetime{}", i); - quote!(#field_ident: &#lifetime ()) - }, - ); - - let orig_ident = self.orig.ident; - let struct_ident = format_ident!("__{}", orig_ident); - let vis = self.orig.vis; - let lifetime = &self.proj.lifetime; - let type_params = self.orig.generics.type_params().map(|t| &t.ident); - let proj_generics = &self.proj.generics; - let (proj_impl_generics, proj_ty_generics, _) = proj_generics.split_for_impl(); - let (impl_generics, ty_generics, where_clause) = - self.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`. - #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,)* - } +/// 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 + }); - impl #proj_impl_generics ::pin_project::__private::Unpin - for #orig_ident #ty_generics - #full_where_clause - { - } + let (impl_generics, _, where_clause) = proj_generics.split_for_impl(); + let ty_generics = cx.orig.generics.split_for_impl().1; - // 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. - unsafe impl #impl_generics ::pin_project::UnsafeUnpin - for #orig_ident #ty_generics - #where_clause - { - } + 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 + }); - /// Creates `Drop` implementation for original type. - fn make_drop_impl(&self) -> TokenStream { - let ident = self.orig.ident; - let (impl_generics, ty_generics, where_clause) = self.orig.generics.split_for_impl(); + let (proj_impl_generics, _, proj_where_clause) = proj_generics.split_for_impl(); + let ty_generics = cx.orig.generics.split_for_impl().1; - if let Some(span) = self.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 + 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 { - fn drop(&mut self) { - // Safety - we're in 'drop', so we know that 'self' will - // never move again. - let pinned_self = #unsafety { - ::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*. - #unsafety { - ::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 + } + 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: // - // We create a new identifier for each struct, so that the traits - // for different types do not conflict with each other. + // ```rust + // struct AlwaysUnpin<T: ?Sized>(PhantomData<T>) {} + // impl<T: ?Sized> Unpin for AlwaysUnpin<T> {} // - // 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); + // ... + // _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, LifetimeDef { 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! { - // 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)] - impl<T: ::pin_project::__private::Drop> #trait_ident for T {} - impl #impl_generics #trait_ident for #ident #ty_generics #where_clause {} - - // 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 attirbutes/impl may exist. + // This needs to have the same visibility as the original type, + // due to the limitations of the 'public in private' error. // - // 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. - impl #impl_generics ::pin_project::__private::PinnedDrop for #ident #ty_generics - #where_clause + // 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 { - unsafe fn drop(self: ::pin_project::__private::Pin<&mut Self>) {} } } } } +} - /// Creates an implementation of the projection method. - fn make_proj_impl( - &self, - proj_body: &TokenStream, - proj_ref_body: &TokenStream, - proj_own_body: &TokenStream, - ) -> TokenStream { - let vis = &self.proj.vis; - let lifetime = &self.proj.lifetime; - let orig_ident = self.orig.ident; - let proj_ident = &self.proj.mut_ident; - let proj_ref_ident = &self.proj.ref_ident; - let proj_own_ident = &self.proj.own_ident; - - let orig_ty_generics = self.orig.generics.split_for_impl().1; - 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| { - // For interoperability with `forbid(unsafe_code)`, `unsafe` token should be - // call-site span. - let unsafety = <Token![unsafe]>::default(); - quote_spanned! { span => - #vis fn project_replace( - self: ::pin_project::__private::Pin<&mut Self>, - __replacement: Self, - ) -> #proj_own_ident #orig_ty_generics { +/// 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 { - #proj_own_body + // 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! { - 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 + // 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 attirbutes/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>) {} } } } +} - fn ensure_not_packed(&self, fields: &Fields) -> Result<TokenStream> { - for meta in self.orig.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { - if let Meta::List(list) = meta { - if list.path.is_ident("repr") { - for repr in list.nested.iter() { - match repr { - NestedMeta::Meta(Meta::Path(path)) - | NestedMeta::Meta(Meta::List(MetaList { path, .. })) - if path.is_ident("packed") => - { - return Err(error!( - repr, - "#[pin_project] attribute may not be used on #[repr(packed)] types" - )); - } - _ => {} - } - } +/// 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(); + + 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! { + #[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 => + #vis fn project_replace( + self: ::pin_project::__private::Pin<&mut Self>, + __replacement: Self, + ) -> #proj_own_ident #orig_ty_generics + }; + quote! { + #sig { + unsafe { + #proj_own_body } } } + }); - // As proc-macro-derive can't rewrite the structure definition, - // it's probably no longer necessary, but it keeps it for now. + 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; + } + } - // Workaround for https://github.com/taiki-e/pin-project/issues/32 - // 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 - // #[deny(safe_packed_borrows)] - // fn assert_not_repr_packed(val: &MyStruct) { - // let _field1 = &val.field1; - // let _field2 = &val.field2; - // ... - // let _fieldn = &val.fieldn; - // } - // ``` - // - // Taking a reference to a packed field is unsafe, and applying - // `#[deny(safe_packed_borrows)]` makes sure that doing this without - // an `unsafe` block (which we deliberately do not generate) - // 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. - // - // See also https://github.com/taiki-e/pin-project/pull/34. - let mut field_refs = vec![]; - match fields { - Fields::Named(FieldsNamed { named, .. }) => { - for Field { ident, .. } in named { - field_refs.push(quote!(&val.#ident;)); - } - } - Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { - for (index, _) in unnamed.iter().enumerate() { - let index = Index::from(index); - field_refs.push(quote!(&val.#index;)); + 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 `safe_packed_borrows` lint. +fn ensure_not_packed(orig: &OriginalType<'_>, fields: &Fields) -> Result<TokenStream> { + for meta in orig.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { + if let Meta::List(list) = meta { + if list.path.is_ident("repr") { + for repr in list.nested.iter() { + match repr { + NestedMeta::Meta(Meta::Path(path)) + | NestedMeta::Meta(Meta::List(MetaList { path, .. })) + if path.is_ident("packed") => + { + return Err(error!( + repr, + "#[pin_project] attribute may not be used on #[repr(packed)] types" + )); + } + _ => {} + } } } - Fields::Unit => {} } + } - let (impl_generics, ty_generics, where_clause) = self.orig.generics.split_for_impl(); - let ident = self.orig.ident; - Ok(quote! { - #[deny(safe_packed_borrows)] - fn __assert_not_repr_packed #impl_generics (val: &#ident #ty_generics) #where_clause { - #(#field_refs)* + // As proc-macro-derive can't rewrite the structure definition, + // it's probably no longer necessary, but it keeps it for now. + + // Workaround for https://github.com/taiki-e/pin-project/issues/32 + // 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(safe_packed_borrows)] + // fn assert_not_repr_packed(val: &MyStruct) { + // let _field1 = &val.field1; + // let _field2 = &val.field2; + // ... + // let _fieldn = &val.fieldn; + // } + // ``` + // + // Taking a reference to a packed field is unsafe, and applying + // `#[forbid(safe_packed_borrows)]` makes sure that doing this without + // an `unsafe` block (which we deliberately do not generate) + // 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. + // + // See also https://github.com/taiki-e/pin-project/pull/34. + 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(safe_packed_borrows)] + fn __assert_not_repr_packed #impl_generics (this: &#ident #ty_generics) #where_clause { + #(let _ = #field_refs;)* + } + }) } diff --git a/src/pin_project/mod.rs b/src/pin_project/mod.rs index 3c8e29e..380b586 100644 --- a/src/pin_project/mod.rs +++ b/src/pin_project/mod.rs @@ -1,3 +1,4 @@ +mod args; mod attribute; mod derive; diff --git a/src/pinned_drop.rs b/src/pinned_drop.rs index 42619e3..86b0f47 100644 --- a/src/pinned_drop.rs +++ b/src/pinned_drop.rs @@ -5,12 +5,19 @@ use syn::{spanned::Spanned, visit_mut::VisitMut, *}; use crate::utils::{parse_as_empty, prepend_underscore_to_self, ReplaceReceiver, SliceExt}; pub(crate) fn attribute(args: &TokenStream, mut input: ItemImpl) -> TokenStream { - if let Err(e) = parse_as_empty(args).and_then(|()| parse(&mut input)) { + let res = (|| -> Result<()> { + parse_as_empty(args)?; + validate_impl(&input)?; + expand_impl(&mut input); + Ok(()) + })(); + + if let Err(e) = res { let mut tokens = e.to_compile_error(); if let Type::Path(self_ty) = &*input.self_ty { let (impl_generics, _, where_clause) = input.generics.split_for_impl(); - // A dummy impl of `PinnedDrop`. + // Generate a dummy impl of `PinnedDrop`. // In many cases, `#[pinned_drop] impl` is declared after `#[pin_project]`. // Therefore, if `pinned_drop` compile fails, you will also get an error // about `PinnedDrop` not being implemented. @@ -20,7 +27,8 @@ pub(crate) fn attribute(args: &TokenStream, mut input: ItemImpl) -> TokenStream // accidentally compile successfully. // // However, if `input.self_ty` is not Type::Path, there is a high possibility that - // the type does not exist, so do not generate a dummy impl. + // the type does not exist (since #[pin_project] can only be used on struct/enum + // definitions), so do not generate a dummy impl. tokens.extend(quote! { impl #impl_generics ::pin_project::__private::PinnedDrop for #self_ty #where_clause @@ -35,36 +43,92 @@ pub(crate) fn attribute(args: &TokenStream, mut input: ItemImpl) -> TokenStream } } -fn parse_method(method: &ImplItemMethod) -> Result<()> { +/// Validates the signature of given `PinnedDrop` impl. +fn validate_impl(item: &ItemImpl) -> Result<()> { + const INVALID_ITEM: &str = + "#[pinned_drop] may only be used on implementation for the `PinnedDrop` trait"; + + if let Some(attr) = item.attrs.find("pinned_drop") { + return Err(error!(attr, "duplicate #[pinned_drop] attribute")); + } + + if let Some((_, path, _)) = &item.trait_ { + if !path.is_ident("PinnedDrop") { + return Err(error!(path, INVALID_ITEM)); + } + } else { + return Err(error!(item.self_ty, INVALID_ITEM)); + } + + if item.unsafety.is_some() { + return Err(error!(item.unsafety, "implementing the trait `PinnedDrop` is not unsafe")); + } + if item.items.is_empty() { + return Err(error!(item, "not all trait items implemented, missing: `drop`")); + } + + match &*item.self_ty { + Type::Path(_) => {} + ty => { + return Err(error!( + ty, + "implementing the trait `PinnedDrop` on this type is unsupported" + )); + } + } + + item.items.iter().enumerate().try_for_each(|(i, item)| match item { + ImplItem::Const(item) => { + Err(error!(item, "const `{}` is not a member of trait `PinnedDrop`", item.ident)) + } + ImplItem::Type(item) => { + Err(error!(item, "type `{}` is not a member of trait `PinnedDrop`", item.ident)) + } + ImplItem::Method(method) => { + validate_sig(&method.sig)?; + if i == 0 { + Ok(()) + } else { + Err(error!(method, "duplicate definitions with name `drop`")) + } + } + _ => unreachable!("unexpected ImplItem"), + }) +} + +/// Validates the signature of given `PinnedDrop::drop` method. +/// +/// The correct signature is: `(mut) self: (<path>::)Pin<&mut Self>` +fn validate_sig(sig: &Signature) -> Result<()> { fn get_ty_path(ty: &Type) -> Option<&Path> { if let Type::Path(TypePath { qself: None, path }) = ty { Some(path) } else { None } } const INVALID_ARGUMENT: &str = "method `drop` must take an argument `self: Pin<&mut Self>`"; - if method.sig.ident != "drop" { + if sig.ident != "drop" { return Err(error!( - method.sig.ident, - "method `{}` is not a member of trait `PinnedDrop", method.sig.ident, + sig.ident, + "method `{}` is not a member of trait `PinnedDrop", sig.ident, )); } - if let ReturnType::Type(_, ty) = &method.sig.output { + if let ReturnType::Type(_, ty) = &sig.output { match &**ty { Type::Tuple(ty) if ty.elems.is_empty() => {} _ => return Err(error!(ty, "method `drop` must return the unit type")), } } - match method.sig.inputs.len() { + match sig.inputs.len() { 1 => {} - 0 => return Err(Error::new(method.sig.paren_token.span, INVALID_ARGUMENT)), - _ => return Err(error!(method.sig.inputs, INVALID_ARGUMENT)), + 0 => return Err(Error::new(sig.paren_token.span, INVALID_ARGUMENT)), + _ => return Err(error!(sig.inputs, INVALID_ARGUMENT)), } - if let Some(FnArg::Typed(pat)) = method.sig.receiver() { + if let Some(FnArg::Typed(arg)) = sig.receiver() { // (mut) self: <path> - if let Some(path) = get_ty_path(&pat.ty) { + if let Some(path) = get_ty_path(&arg.ty) { let ty = path.segments.last().unwrap(); if let PathArguments::AngleBracketed(args) = &ty.arguments { // (mut) self: (<path>::)<ty><&mut <elem>..> @@ -79,9 +143,9 @@ fn parse_method(method: &ImplItemMethod) -> Result<()> { && ty.ident == "Pin" && get_ty_path(elem).map_or(false, |path| path.is_ident("Self")) { - if method.sig.unsafety.is_some() { + if sig.unsafety.is_some() { return Err(error!( - method.sig.unsafety, + sig.unsafety, "implementing the method `drop` is not unsafe" )); } @@ -92,73 +156,13 @@ fn parse_method(method: &ImplItemMethod) -> Result<()> { } } - Err(error!(method.sig.inputs[0], INVALID_ARGUMENT)) -} - -fn parse(item: &mut ItemImpl) -> Result<()> { - const INVALID_ITEM: &str = - "#[pinned_drop] may only be used on implementation for the `PinnedDrop` trait"; - - if let Some(attr) = item.attrs.find("pinned_drop") { - return Err(error!(attr, "duplicate #[pinned_drop] attribute")); - } - - if let Some((_, path, _)) = &mut item.trait_ { - if path.is_ident("PinnedDrop") { - *path = parse_quote_spanned! { path.span() => - ::pin_project::__private::PinnedDrop - }; - } else { - return Err(error!(path, INVALID_ITEM)); - } - } else { - return Err(error!(item.self_ty, INVALID_ITEM)); - } - - if item.unsafety.is_some() { - return Err(error!(item.unsafety, "implementing the trait `PinnedDrop` is not unsafe")); - } - if item.items.is_empty() { - return Err(error!(item, "not all trait items implemented, missing: `drop`")); - } - - match &*item.self_ty { - Type::Path(_) => {} - ty => { - return Err(error!( - ty, - "implementing the trait `PinnedDrop` on this type is unsupported" - )); - } - } - - item.items - .iter() - .enumerate() - .try_for_each(|(i, item)| match item { - ImplItem::Const(item) => { - Err(error!(item, "const `{}` is not a member of trait `PinnedDrop`", item.ident)) - } - ImplItem::Type(item) => { - Err(error!(item, "type `{}` is not a member of trait `PinnedDrop`", item.ident)) - } - ImplItem::Method(method) => { - parse_method(method)?; - if i == 0 { - Ok(()) - } else { - Err(error!(method, "duplicate definitions with name `drop`")) - } - } - _ => unreachable!("unexpected ImplItem"), - }) - .map(|()| expand_item(item)) + Err(error!(sig.inputs[0], INVALID_ARGUMENT)) } // from: // // fn drop(self: Pin<&mut Self>) { -// // something +// // ... // } // // into: @@ -166,42 +170,51 @@ fn parse(item: &mut ItemImpl) -> Result<()> { // unsafe fn drop(self: Pin<&mut Self>) { // fn __drop_inner<T>(__self: Pin<&mut Foo<'_, T>>) { // fn __drop_inner() {} -// // something +// // ... // } // __drop_inner(self); // } // -fn expand_item(item: &mut ItemImpl) { +fn expand_impl(item: &mut ItemImpl) { + fn get_arg_pat(arg: &mut FnArg) -> Option<&mut PatIdent> { + if let FnArg::Typed(arg) = arg { + if let Pat::Ident(ident) = &mut *arg.pat { + return Some(ident); + } + } + None + } + + let path = &mut item.trait_.as_mut().unwrap().1; + *path = parse_quote_spanned! { path.span() => + ::pin_project::__private::PinnedDrop + }; + let method = if let ImplItem::Method(method) = &mut item.items[0] { method } else { unreachable!() }; - let mut drop_inner = method.clone(); // `fn drop(mut self: Pin<&mut Self>)` -> `fn __drop_inner<T>(mut __self: Pin<&mut Receiver>)` - let ident = Ident::new("__drop_inner", drop_inner.sig.ident.span()); - // Add a dummy `__drop_inner` function to prevent users call outer `__drop_inner`. - drop_inner.block.stmts.insert(0, parse_quote!(fn #ident() {})); - drop_inner.sig.ident = ident; - drop_inner.sig.generics = item.generics.clone(); - if let FnArg::Typed(arg) = &mut drop_inner.sig.inputs[0] { - if let Pat::Ident(ident) = &mut *arg.pat { - prepend_underscore_to_self(&mut ident.ident); - } - } - let self_ty = if let Type::Path(ty) = &*item.self_ty { ty } else { unreachable!() }; - let mut visitor = ReplaceReceiver(self_ty); - visitor.visit_signature_mut(&mut drop_inner.sig); - visitor.visit_block_mut(&mut drop_inner.block); + let drop_inner = { + let mut drop_inner = method.clone(); + let ident = Ident::new("__drop_inner", drop_inner.sig.ident.span()); + // Add a dummy `__drop_inner` function to prevent users call outer `__drop_inner`. + drop_inner.block.stmts.insert(0, parse_quote!(fn #ident() {})); + drop_inner.sig.ident = ident; + drop_inner.sig.generics = item.generics.clone(); + let self_pat = get_arg_pat(&mut drop_inner.sig.inputs[0]).unwrap(); + prepend_underscore_to_self(&mut self_pat.ident); + let self_ty = if let Type::Path(ty) = &*item.self_ty { ty } else { unreachable!() }; + let mut visitor = ReplaceReceiver(self_ty); + visitor.visit_signature_mut(&mut drop_inner.sig); + visitor.visit_block_mut(&mut drop_inner.block); + drop_inner + }; // `fn drop(mut self: Pin<&mut Self>)` -> `unsafe fn drop(self: Pin<&mut Self>)` method.sig.unsafety = Some(<Token![unsafe]>::default()); - let mut self_token = None; - if let FnArg::Typed(arg) = &mut method.sig.inputs[0] { - if let Pat::Ident(ident) = &mut *arg.pat { - ident.mutability = None; - self_token = Some(&ident.ident); - } - } - assert!(self_token.is_some()); + let self_pat = get_arg_pat(&mut method.sig.inputs[0]).unwrap(); + self_pat.mutability = None; + let self_token = &self_pat.ident; method.block.stmts = parse_quote! { #[allow(clippy::needless_pass_by_value)] // This lint does not warn the receiver. diff --git a/src/project.rs b/src/project.rs deleted file mode 100644 index a00e4c2..0000000 --- a/src/project.rs +++ /dev/null @@ -1,350 +0,0 @@ -use proc_macro2::{Span, TokenStream}; -use quote::ToTokens; -use syn::{ - visit_mut::{self, VisitMut}, - *, -}; - -use crate::utils::{ - determine_lifetime_name, insert_lifetime, parse_as_empty, ProjKind, SliceExt, VecExt, -}; - -pub(crate) fn attribute(args: &TokenStream, input: Stmt, kind: ProjKind) -> TokenStream { - parse_as_empty(args).and_then(|()| parse(input, kind)).unwrap_or_else(|e| e.to_compile_error()) -} - -fn replace_expr(expr: &mut Expr, kind: ProjKind) { - match expr { - Expr::Match(expr) => { - Context::new(kind).replace_expr_match(expr); - } - Expr::If(expr_if) => { - let mut expr_if = expr_if; - while let Expr::Let(ref mut expr) = &mut *expr_if.cond { - Context::new(kind).replace_expr_let(expr); - if let Some((_, ref mut expr)) = expr_if.else_branch { - if let Expr::If(new_expr_if) = &mut **expr { - expr_if = new_expr_if; - continue; - } - } - break; - } - } - _ => {} - } -} - -fn parse(mut stmt: Stmt, kind: ProjKind) -> Result<TokenStream> { - match &mut stmt { - Stmt::Expr(expr) | Stmt::Semi(expr, _) => replace_expr(expr, kind), - Stmt::Local(local) => Context::new(kind).replace_local(local)?, - Stmt::Item(Item::Fn(item)) => replace_item_fn(item, kind)?, - Stmt::Item(Item::Impl(item)) => replace_item_impl(item, kind)?, - Stmt::Item(Item::Use(item)) => replace_item_use(item, kind)?, - _ => {} - } - - Ok(stmt.into_token_stream()) -} - -struct Context { - register: Option<(Ident, usize)>, - replaced: bool, - kind: ProjKind, -} - -impl Context { - fn new(kind: ProjKind) -> Self { - Self { register: None, replaced: false, kind } - } - - fn update(&mut self, ident: &Ident, len: usize) { - self.register.get_or_insert_with(|| (ident.clone(), len)); - } - - fn compare_paths(&self, ident: &Ident, len: usize) -> bool { - match &self.register { - Some((i, l)) => *l == len && i == ident, - None => false, - } - } - - fn replace_local(&mut self, local: &mut Local) -> Result<()> { - if let Some(attr) = local.attrs.find(self.kind.method_name()) { - return Err(error!(attr, "duplicate #[{}] attribute", self.kind.method_name())); - } - - if let Some(Expr::Match(expr)) = local.init.as_mut().map(|(_, expr)| &mut **expr) { - self.replace_expr_match(expr); - } - - if self.replaced { - if is_replaceable(&local.pat, false) { - return Err(error!( - local.pat, - "Both initializer expression and pattern are replaceable, \ - you need to split the initializer expression into separate let bindings \ - to avoid ambiguity" - )); - } - } else { - self.replace_pat(&mut local.pat, false); - } - - Ok(()) - } - - fn replace_expr_let(&mut self, expr: &mut ExprLet) { - self.replace_pat(&mut expr.pat, true) - } - - fn replace_expr_match(&mut self, expr: &mut ExprMatch) { - expr.arms.iter_mut().for_each(|arm| self.replace_pat(&mut arm.pat, true)) - } - - fn replace_pat(&mut self, pat: &mut Pat, allow_pat_path: bool) { - match pat { - Pat::Ident(PatIdent { subpat: Some((_, pat)), .. }) - | Pat::Reference(PatReference { pat, .. }) - | Pat::Box(PatBox { pat, .. }) - | Pat::Type(PatType { pat, .. }) => self.replace_pat(pat, allow_pat_path), - - Pat::Or(PatOr { cases, .. }) => { - cases.iter_mut().for_each(|pat| self.replace_pat(pat, allow_pat_path)) - } - - Pat::Struct(PatStruct { path, .. }) | Pat::TupleStruct(PatTupleStruct { path, .. }) => { - self.replace_path(path) - } - Pat::Path(PatPath { qself: None, path, .. }) if allow_pat_path => { - self.replace_path(path) - } - _ => {} - } - } - - fn replace_path(&mut self, path: &mut Path) { - let len = match path.segments.len() { - // 1: struct - // 2: enum - len @ 1 | len @ 2 => len, - // other path - _ => return, - }; - - if self.register.is_none() || self.compare_paths(&path.segments[0].ident, len) { - self.update(&path.segments[0].ident, len); - self.replaced = true; - replace_ident(&mut path.segments[0].ident, self.kind); - } - } -} - -fn is_replaceable(pat: &Pat, allow_pat_path: bool) -> bool { - match pat { - Pat::Ident(PatIdent { subpat: Some((_, pat)), .. }) - | Pat::Reference(PatReference { pat, .. }) - | Pat::Box(PatBox { pat, .. }) - | Pat::Type(PatType { pat, .. }) => is_replaceable(pat, allow_pat_path), - - Pat::Or(PatOr { cases, .. }) => cases.iter().any(|pat| is_replaceable(pat, allow_pat_path)), - - Pat::Struct(_) | Pat::TupleStruct(_) => true, - Pat::Path(PatPath { qself: None, .. }) => allow_pat_path, - _ => false, - } -} - -fn replace_ident(ident: &mut Ident, kind: ProjKind) { - *ident = kind.proj_ident(ident); -} - -fn replace_item_impl(item: &mut ItemImpl, kind: ProjKind) -> Result<()> { - if let Some(attr) = item.attrs.find(kind.method_name()) { - return Err(error!(attr, "duplicate #[{}] attribute", kind.method_name())); - } - - let PathSegment { ident, arguments } = match &mut *item.self_ty { - Type::Path(TypePath { qself: None, path }) => path.segments.last_mut().unwrap(), - _ => return Ok(()), - }; - - replace_ident(ident, kind); - - let mut lifetime_name = String::from("'pin"); - determine_lifetime_name(&mut lifetime_name, &mut item.generics); - item.items - .iter_mut() - .filter_map(|i| if let ImplItem::Method(i) = i { Some(i) } else { None }) - .for_each(|item| determine_lifetime_name(&mut lifetime_name, &mut item.sig.generics)); - let lifetime = Lifetime::new(&lifetime_name, Span::call_site()); - - insert_lifetime(&mut item.generics, lifetime.clone()); - - match arguments { - PathArguments::None => { - *arguments = PathArguments::AngleBracketed(parse_quote!(<#lifetime>)); - } - PathArguments::AngleBracketed(args) => { - args.args.insert(0, parse_quote!(#lifetime)); - } - PathArguments::Parenthesized(_) => unreachable!(), - } - Ok(()) -} - -fn replace_item_fn(item: &mut ItemFn, kind: ProjKind) -> Result<()> { - struct FnVisitor(Result<()>); - - impl FnVisitor { - fn visit_stmt(&mut self, node: &mut Stmt) -> Result<()> { - match node { - Stmt::Expr(expr) | Stmt::Semi(expr, _) => self.visit_expr(expr), - Stmt::Local(local) => { - visit_mut::visit_local_mut(self, local); - - let mut prev = None; - for &kind in &ProjKind::ALL { - if let Some(attr) = local.attrs.find_remove(kind.method_name())? { - if let Some(prev) = prev.replace(kind) { - return Err(error!( - attr, - "attributes `{}` and `{}` are mutually exclusive", - prev.method_name(), - kind.method_name(), - )); - } - Context::new(kind).replace_local(local)?; - } - } - - Ok(()) - } - // Do not recurse into nested items. - Stmt::Item(_) => Ok(()), - } - } - - fn visit_expr(&mut self, node: &mut Expr) -> Result<()> { - visit_mut::visit_expr_mut(self, node); - match node { - Expr::Match(expr) => { - let mut prev = None; - for &kind in &ProjKind::ALL { - if let Some(attr) = expr.attrs.find_remove(kind.method_name())? { - if let Some(prev) = prev.replace(kind) { - return Err(error!( - attr, - "attributes `{}` and `{}` are mutually exclusive", - prev.method_name(), - kind.method_name(), - )); - } - } - } - if let Some(kind) = prev { - replace_expr(node, kind); - } - } - Expr::If(expr_if) => { - if let Expr::Let(_) = &*expr_if.cond { - let mut prev = None; - for &kind in &ProjKind::ALL { - if let Some(attr) = expr_if.attrs.find_remove(kind.method_name())? { - if let Some(prev) = prev.replace(kind) { - return Err(error!( - attr, - "attributes `{}` and `{}` are mutually exclusive", - prev.method_name(), - kind.method_name(), - )); - } - } - } - if let Some(kind) = prev { - replace_expr(node, kind); - } - } - } - _ => {} - } - Ok(()) - } - } - - impl VisitMut for FnVisitor { - fn visit_stmt_mut(&mut self, node: &mut Stmt) { - if self.0.is_err() { - return; - } - if let Err(e) = self.visit_stmt(node) { - self.0 = Err(e) - } - } - - fn visit_expr_mut(&mut self, node: &mut Expr) { - if self.0.is_err() { - return; - } - if let Err(e) = self.visit_expr(node) { - self.0 = Err(e) - } - } - - fn visit_item_mut(&mut self, _: &mut Item) { - // Do not recurse into nested items. - } - } - - if let Some(attr) = item.attrs.find(kind.method_name()) { - return Err(error!(attr, "duplicate #[{}] attribute", kind.method_name())); - } - - let mut visitor = FnVisitor(Ok(())); - visitor.visit_block_mut(&mut item.block); - visitor.0 -} - -fn replace_item_use(item: &mut ItemUse, kind: ProjKind) -> Result<()> { - struct UseTreeVisitor { - res: Result<()>, - kind: ProjKind, - } - - impl VisitMut for UseTreeVisitor { - fn visit_use_tree_mut(&mut self, node: &mut UseTree) { - if self.res.is_err() { - return; - } - - match node { - // Desugar `use tree::<name>` into `tree::__<name>Projection`. - UseTree::Name(name) => replace_ident(&mut name.ident, self.kind), - UseTree::Glob(glob) => { - self.res = Err(error!( - glob, - "#[{}] attribute may not be used on glob imports", - self.kind.method_name() - )); - } - UseTree::Rename(rename) => { - self.res = Err(error!( - rename, - "#[{}] attribute may not be used on renamed imports", - self.kind.method_name() - )); - } - UseTree::Path(_) | UseTree::Group(_) => visit_mut::visit_use_tree_mut(self, node), - } - } - } - - if let Some(attr) = item.attrs.find(kind.method_name()) { - return Err(error!(attr, "duplicate #[{}] attribute", kind.method_name())); - } - - let mut visitor = UseTreeVisitor { res: Ok(()), kind }; - visitor.visit_item_use_mut(item); - visitor.res -} diff --git a/src/utils.rs b/src/utils.rs index 716d764..2fd8a61 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,5 @@ use proc_macro2::{Group, Spacing, Span, TokenStream, TokenTree}; -use quote::{format_ident, quote, quote_spanned, ToTokens}; +use quote::{quote, quote_spanned, ToTokens}; use std::{iter::FromIterator, mem}; use syn::{ parse::{Parse, ParseBuffer, ParseStream}, @@ -25,36 +25,6 @@ macro_rules! parse_quote_spanned { }; } -#[derive(Clone, Copy, Eq, PartialEq)] -pub(crate) enum ProjKind { - Mutable, - Immutable, - Owned, -} - -impl ProjKind { - pub(crate) const ALL: [Self; 3] = [ProjKind::Mutable, ProjKind::Immutable, ProjKind::Owned]; - - /// Returns the name of the projection method. - pub(crate) fn method_name(self) -> &'static str { - match self { - ProjKind::Mutable => "project", - ProjKind::Immutable => "project_ref", - ProjKind::Owned => "project_replace", - } - } - - /// Creates the ident of the projected type from the ident of the original - /// type. - pub(crate) fn proj_ident(self, ident: &Ident) -> Ident { - match self { - ProjKind::Mutable => format_ident!("__{}Projection", ident), - ProjKind::Immutable => format_ident!("__{}ProjectionRef", ident), - ProjKind::Owned => format_ident!("__{}ProjectionOwned", ident), - } - } -} - /// Determines the lifetime names. Ensure it doesn't overlap with any existing /// lifetime names. pub(crate) fn determine_lifetime_name(lifetime_name: &mut String, generics: &mut Generics) { @@ -106,7 +76,10 @@ pub(crate) fn insert_lifetime(generics: &mut Generics, lifetime: Lifetime) { generics.params.insert(0, LifetimeDef::new(lifetime).into()); } -/// Determines the visibility of the projected type and projection method. +/// Determines the visibility of the projected types and projection methods. +/// +/// If given visibility is `pub`, returned visibility is `pub(crate)`. +/// Otherwise, returned visibility is the same as given visibility. pub(crate) fn determine_visibility(vis: &Visibility) -> Visibility { if let Visibility::Public(token) = vis { parse_quote_spanned!(token.pub_token.span => pub(crate)) @@ -115,7 +88,8 @@ pub(crate) fn determine_visibility(vis: &Visibility) -> Visibility { } } -/// Check if `tokens` is an empty `TokenStream`. +/// Checks if `tokens` is an empty `TokenStream`. +/// /// This is almost equivalent to `syn::parse2::<Nothing>()`, but produces /// a better error message and does not require ownership of `tokens`. pub(crate) fn parse_as_empty(tokens: &TokenStream) -> Result<()> { @@ -149,11 +123,11 @@ pub(crate) trait SliceExt { fn find(&self, ident: &str) -> Option<&Attribute>; } -pub(crate) trait VecExt { - fn find_remove(&mut self, ident: &str) -> Result<Option<Attribute>>; -} - impl SliceExt for [Attribute] { + /// # Errors + /// + /// * There are multiple specified attributes. + /// * The `Attribute::tokens` field of the specified attribute is not empty. fn position_exact(&self, ident: &str) -> Result<Option<usize>> { self.iter() .try_fold((0, None), |(i, mut prev), attr| { @@ -169,13 +143,7 @@ impl SliceExt for [Attribute] { } fn find(&self, ident: &str) -> Option<&Attribute> { - self.iter().position(|attr| attr.path.is_ident(ident)).and_then(|i| self.get(i)) - } -} - -impl VecExt for Vec<Attribute> { - fn find_remove(&mut self, ident: &str) -> Result<Option<Attribute>> { - self.position_exact(ident).map(|pos| pos.map(|i| self.remove(i))) + self.iter().position(|attr| attr.path.is_ident(ident)).map(|i| &self[i]) } } |