From cf5b0cb4281e057d0284c5a25d323af02a727b30 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sat, 7 Sep 2019 12:43:55 +0900 Subject: [PATCH 1/6] Move automatic generation of Unpin implementation to proc-macro-derive To generate the correct `Unpin` implementation, we need to collect the types of the pinned fields. However, since proc-macro-attribute is applied before cfg, we cannot be collecting field types at this timing. So instead of generating the `Unpin` implementation here, we need to delegate automatic generation of the `Unpin` implementation to proc-macro-derive. --- azure-pipelines.yml | 9 +- ci/azure-test.yml | 12 +- compiletest.sh | 5 + pin-project-internal/src/lib.rs | 16 +- pin-project-internal/src/pin_project/enums.rs | 85 ++-- pin-project-internal/src/pin_project/mod.rs | 462 +++++++++++------- .../src/pin_project/structs.rs | 60 ++- pin-project-internal/src/pinned_drop.rs | 5 +- pin-project-internal/src/project.rs | 5 +- pin-project-internal/src/utils.rs | 10 +- src/lib.rs | 3 + tests/cfg.rs | 150 ++++++ tests/pinned_drop.rs | 1 - tests/project.rs | 1 - tests/project_nightly.rs | 1 - tests/ui/cfg/tuple-struct.rs | 20 + tests/ui/cfg/tuple-struct.stderr | 8 + tests/ui/cfg/tuple-variant.rs | 22 + tests/ui/cfg/tuple-variant.stderr | 8 + tests/ui/pin_project/phantom-pinned.rs | 13 + tests/ui/pin_project/phantom-pinned.stderr | 16 + tests/ui/pin_project/proper_unpin.rs | 2 +- tests/ui/pin_project/trivial_bounds.rs | 15 + tests/ui/pin_project/unsupported.stderr | 8 +- tests/unsafe_unpin.rs | 1 - 25 files changed, 678 insertions(+), 260 deletions(-) create mode 100644 compiletest.sh create mode 100644 tests/cfg.rs create mode 100644 tests/ui/cfg/tuple-struct.rs create mode 100644 tests/ui/cfg/tuple-struct.stderr create mode 100644 tests/ui/cfg/tuple-variant.rs create mode 100644 tests/ui/cfg/tuple-variant.stderr create mode 100644 tests/ui/pin_project/phantom-pinned.rs create mode 100644 tests/ui/pin_project/phantom-pinned.stderr create mode 100644 tests/ui/pin_project/trivial_bounds.rs diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b723605b..e92ebc4c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -39,6 +39,7 @@ jobs: parameters: toolchain: nightly name: nightly + cross: true - job: compiletest pool: @@ -49,7 +50,9 @@ jobs: toolchain: nightly - script: | cargo clean - RUSTFLAGS='--cfg compiletest --cfg pin_project_show_unpin_struct' cargo test -p pin-project --all-features --test compiletest + cargo test -p pin-project --all-features --test compiletest + env: + RUSTFLAGS: -Dwarnings --cfg compiletest --cfg pin_project_show_unpin_struct displayName: compiletest - job: clippy @@ -122,5 +125,7 @@ jobs: parameters: toolchain: nightly - script: | - RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps --all --all-features + cargo doc --no-deps --all --all-features + env: + RUSTDOCFLAGS: -Dwarnings displayName: cargo doc diff --git a/ci/azure-test.yml b/ci/azure-test.yml index 6f632d67..f1b0cc61 100644 --- a/ci/azure-test.yml +++ b/ci/azure-test.yml @@ -1,11 +1,19 @@ parameters: - vmImage: ubuntu-16.04 cmd: test jobs: - job: ${{ parameters.name }} + strategy: + matrix: + Linux: + vmImage: ubuntu-16.04 + ${{ if parameters.cross }}: + MacOS: + vmImage: macOS-10.13 + Windows: + vmImage: vs2017-win2016 pool: - vmImage: ${{ parameters.vmImage }} + vmImage: $(vmImage) steps: - template: azure-install-rust.yml diff --git a/compiletest.sh b/compiletest.sh new file mode 100644 index 00000000..c1226e1c --- /dev/null +++ b/compiletest.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# A script to run compile tests with the same condition that pin-project executes with CI. + +rm -rf target/debug/deps/libpin_project* && RUSTFLAGS='--cfg compiletest --cfg pin_project_show_unpin_struct' cargo test -p pin-project --all-features --test compiletest diff --git a/pin-project-internal/src/lib.rs b/pin-project-internal/src/lib.rs index 940176b0..004d9c81 100644 --- a/pin-project-internal/src/lib.rs +++ b/pin-project-internal/src/lib.rs @@ -333,7 +333,8 @@ use syn::parse::Nothing; /// [`pinned_drop`]: ./attr.pinned_drop.html #[proc_macro_attribute] pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream { - pin_project::attribute(args.into(), input.into()).into() + let input = syn::parse_macro_input!(input); + pin_project::attribute(args.into(), input).into() } // TODO: Move this doc into pin-project crate when https://github.com/rust-lang/rust/pull/62855 merged. @@ -372,7 +373,8 @@ pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream { let _: Nothing = syn::parse_macro_input!(args); - pinned_drop::attribute(input.into()).into() + let input = syn::parse_macro_input!(input); + pinned_drop::attribute(&input).into() } // TODO: Move this doc into pin-project crate when https://github.com/rust-lang/rust/pull/62855 merged. @@ -493,7 +495,15 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn project(args: TokenStream, input: TokenStream) -> TokenStream { let _: Nothing = syn::parse_macro_input!(args); - project::attribute(input.into()).into() + let input = syn::parse_macro_input!(input); + project::attribute(input).into() +} + +#[doc(hidden)] +#[proc_macro_derive(__PinProjectAutoImplUnpin, attributes(pin))] +pub fn derive_unpin(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input); + pin_project::derive(input).into() } #[cfg(feature = "renamed")] diff --git a/pin-project-internal/src/pin_project/enums.rs b/pin-project-internal/src/pin_project/enums.rs index aa05f9dc..6a529dcf 100644 --- a/pin-project-internal/src/pin_project/enums.rs +++ b/pin-project-internal/src/pin_project/enums.rs @@ -1,16 +1,16 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; -use syn::{parse::Nothing, Field, Fields, FieldsNamed, FieldsUnnamed, ItemEnum, Result, Variant}; +use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, ItemEnum, Result, Variant}; -use crate::utils::VecExt; +use crate::utils::collect_cfg; -use super::{Context, PIN}; +use super::Context; pub(super) fn parse(cx: &mut Context, mut item: ItemEnum) -> Result { if item.variants.is_empty() { - return Err(error!( - item, - "#[pin_project] attribute may not be used on enums without variants" + return Err(syn::Error::new( + item.brace_token.span, + "#[pin_project] attribute may not be used on enums without variants", )); } let has_field = item.variants.iter().try_fold(false, |has_field, v| { @@ -62,17 +62,22 @@ pub(super) fn parse(cx: &mut Context, mut item: ItemEnum) -> Result fn variants(cx: &mut Context, item: &mut ItemEnum) -> Result<(Vec, Vec)> { let mut proj_variants = Vec::with_capacity(item.variants.len()); let mut proj_arms = Vec::with_capacity(item.variants.len()); - for Variant { fields, ident, .. } in &mut item.variants { + for Variant { attrs, fields, ident, .. } in &mut item.variants { let (proj_pat, proj_body, proj_fields) = match fields { Fields::Unnamed(fields) => unnamed(cx, fields)?, Fields::Named(fields) => named(cx, fields)?, Fields::Unit => (TokenStream::new(), TokenStream::new(), TokenStream::new()), }; + let cfg = collect_cfg(attrs); let Context { orig_ident, proj_ident, .. } = &cx; - let proj_arm = quote!(#orig_ident::#ident #proj_pat => #proj_ident::#ident #proj_body ); - let proj_variant = quote!(#ident #proj_fields); - proj_arms.push(proj_arm); - proj_variants.push(proj_variant); + proj_variants.push(quote! { + #(#cfg)* #ident #proj_fields + }); + proj_arms.push(quote! { + #(#cfg)* #orig_ident::#ident #proj_pat => { + #proj_ident::#ident #proj_body + } + }); } Ok((proj_variants, proj_arms)) @@ -86,18 +91,27 @@ fn named( let mut proj_body = Vec::with_capacity(fields.len()); let mut proj_fields = Vec::with_capacity(fields.len()); for Field { attrs, ident, ty, .. } in fields { - if let Some(attr) = attrs.find_remove(PIN) { - let _: Nothing = syn::parse2(attr.tokens)?; - cx.push_unpin_bounds(ty.clone()); + let cfg = collect_cfg(attrs); + if cx.find_pin_attr(attrs)? { let lifetime = &cx.lifetime; - proj_fields.push(quote!(#ident: ::core::pin::Pin<&#lifetime mut #ty>)); - proj_body.push(quote!(#ident: ::core::pin::Pin::new_unchecked(#ident))); + proj_fields.push(quote! { + #(#cfg)* #ident: ::core::pin::Pin<&#lifetime mut #ty> + }); + proj_body.push(quote! { + #(#cfg)* #ident: ::core::pin::Pin::new_unchecked(#ident) + }); } else { let lifetime = &cx.lifetime; - proj_fields.push(quote!(#ident: &#lifetime mut #ty)); - proj_body.push(quote!(#ident)); + proj_fields.push(quote! { + #(#cfg)* #ident: &#lifetime mut #ty + }); + proj_body.push(quote! { + #(#cfg)* #ident: #ident + }); } - proj_pat.push(ident); + proj_pat.push(quote! { + #(#cfg)* #ident + }); } let proj_pat = quote!({ #(#proj_pat),* }); @@ -114,19 +128,34 @@ fn unnamed( let mut proj_body = Vec::with_capacity(fields.len()); let mut proj_fields = Vec::with_capacity(fields.len()); for (i, Field { attrs, ty, .. }) in fields.iter_mut().enumerate() { - let x = format_ident!("_x{}", i); - if let Some(attr) = attrs.find_remove(PIN) { - let _: Nothing = syn::parse2(attr.tokens)?; - cx.push_unpin_bounds(ty.clone()); + let id = format_ident!("_x{}", i); + let cfg = collect_cfg(attrs); + if !cfg.is_empty() { + return Err(error!( + cfg.first(), + "`cfg` attributes on the field of tuple variants are not supported" + )); + } + if cx.find_pin_attr(attrs)? { let lifetime = &cx.lifetime; - proj_fields.push(quote!(::core::pin::Pin<&#lifetime mut #ty>)); - proj_body.push(quote!(::core::pin::Pin::new_unchecked(#x))); + proj_fields.push(quote! { + ::core::pin::Pin<&#lifetime mut #ty> + }); + proj_body.push(quote! { + ::core::pin::Pin::new_unchecked(#id) + }); } else { let lifetime = &cx.lifetime; - proj_fields.push(quote!(&#lifetime mut #ty)); - proj_body.push(quote!(#x)); + proj_fields.push(quote! { + &#lifetime mut #ty + }); + proj_body.push(quote! { + #id + }); } - proj_pat.push(x); + proj_pat.push(quote! { + #id + }); } let proj_pat = quote!((#(#proj_pat),*)); diff --git a/pin-project-internal/src/pin_project/mod.rs b/pin-project-internal/src/pin_project/mod.rs index d1c3a1a7..b5aff2a7 100644 --- a/pin-project-internal/src/pin_project/mod.rs +++ b/pin-project-internal/src/pin_project/mod.rs @@ -1,14 +1,15 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::{ + parse::Nothing, parse::{Parse, ParseStream}, token::Comma, *, }; use crate::utils::{ - self, crate_path, proj_ident, proj_lifetime_name, proj_trait_ident, DEFAULT_LIFETIME_NAME, - TRAIT_LIFETIME_NAME, + self, collect_cfg, crate_path, proj_ident, proj_lifetime_name, proj_trait_ident, VecExt, + DEFAULT_LIFETIME_NAME, TRAIT_LIFETIME_NAME, }; mod enums; @@ -17,8 +18,201 @@ mod structs; /// The annotation for pinned type. const PIN: &str = "pin"; -pub(super) fn attribute(args: TokenStream, input: TokenStream) -> TokenStream { - parse(args, input).unwrap_or_else(|e| e.to_compile_error()) +pub(crate) fn attribute(args: TokenStream, input: Item) -> TokenStream { + parse_attribute(args, input).unwrap_or_else(|e| e.to_compile_error()) +} + +pub(crate) fn derive(input: DeriveInput) -> TokenStream { + let DeriveInput { vis, ident, generics, mut data, .. } = input; + let mut cx = DeriveContext::new(ident, vis, generics); + match &mut data { + Data::Struct(data) => cx.fields(&mut data.fields), + Data::Enum(data) => cx.variants(data), + Data::Union(_) => unreachable!(), + } + + cx.make_unpin_impl() +} + +struct DeriveContext { + /// Name of the original type. + orig_ident: Ident, + + /// Visibility of the original type. + vis: Visibility, + + /// Generics of the original type. + generics: Generics, + + /// Types of the pinned fields. + pinned_fields: Vec, +} + +impl DeriveContext { + fn new(orig_ident: Ident, vis: Visibility, generics: Generics) -> Self { + Self { orig_ident, vis, generics, pinned_fields: Vec::new() } + } + + fn variants(&mut self, data: &mut DataEnum) { + for Variant { fields, .. } in &mut data.variants { + self.fields(fields) + } + } + + fn fields(&mut self, fields: &mut Fields) { + let fields = match fields { + Fields::Unnamed(fields) => &mut fields.unnamed, + Fields::Named(fields) => &mut fields.named, + Fields::Unit => return, + }; + + for Field { attrs, ty, .. } in fields { + if let Some(attr) = attrs.position(PIN).and_then(|i| attrs.get(i)) { + let _: Nothing = syn::parse2(attr.tokens.clone()).unwrap(); + self.pinned_fields.push(ty.clone()); + } + } + } + + /// Creates conditional `Unpin` implementation for original type. + fn make_unpin_impl(&mut self) -> TokenStream { + let where_clause = self.generics.make_where_clause().clone(); + let orig_ident = &self.orig_ident; + let (impl_generics, ty_generics, _) = self.generics.split_for_impl(); + let type_params: Vec<_> = self.generics.type_params().map(|t| t.ident.clone()).collect(); + + let make_span = || { + #[cfg(proc_macro_def_site)] + { + proc_macro::Span::def_site().into() + } + #[cfg(not(proc_macro_def_site))] + { + Span::call_site() + } + }; + + let struct_ident = if cfg!(proc_macro_def_site) { + format_ident!("UnpinStruct{}", orig_ident, span = make_span()) + } else { + format_ident!("__UnpinStruct{}", orig_ident) + }; + let always_unpin_ident = format_ident!("AlwaysUnpin{}", orig_ident, span = make_span()); + + // Generate a field in our new struct for every + // pinned field in the original type. + let fields: Vec<_> = self + .pinned_fields + .iter() + .enumerate() + .map(|(i, ty)| { + let field_ident = format_ident!("__field{}", i); + quote! { + #field_ident: #ty + } + }) + .collect(); + + // 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(PhantomData) {} + // impl Unpin for AlwaysUnpin {} + // + // ... + // _field: AlwaysUnpin<(A, B, C)> + // ``` + // + // This ensures that any unused type paramters + // don't end up with Unpin bounds. + let lifetime_fields: Vec<_> = self + .generics + .lifetimes() + .enumerate() + .map(|(i, l)| { + let field_ident = format_ident!("__lifetime{}", i); + quote! { + #field_ident: &#l () + } + }) + .collect(); + + let scope_ident = format_ident!("__unpin_scope_{}", orig_ident); + + let vis = &self.vis; + let full_generics = &self.generics; + let mut full_where_clause = where_clause.clone(); + + let unpin_clause: WherePredicate = syn::parse_quote! { + #struct_ident #ty_generics: ::core::marker::Unpin + }; + + full_where_clause.predicates.push(unpin_clause); + + let attrs = if cfg!(proc_macro_def_site) { quote!() } else { quote!(#[doc(hidden)]) }; + + let inner_data = quote! { + struct #always_unpin_ident { + val: ::core::marker::PhantomData + } + + impl ::core::marker::Unpin for #always_unpin_ident {} + + // This needs to have the same visibility as the original type, + // due to the limitations of the 'public in private' error. + // + // Out 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 take + // steps to ensure that the user can never actually reference + // this 'public' type. These steps are described below. + // + // See also https://github.com/taiki-e/pin-project/pull/53. + #[allow(dead_code)] + #attrs + #vis struct #struct_ident #full_generics #where_clause { + __pin_project_use_generics: #always_unpin_ident <(#(#type_params),*)>, + + #(#fields,)* + #(#lifetime_fields,)* + } + + impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #full_where_clause {} + }; + + if cfg!(proc_macro_def_site) { + // On nightly, we use def-site hygiene to make it impossible + // for user code to refer to any of the types we define. + // This allows us to omit wrapping the generated types + // in an fn() scope, allowing rustdoc to properly document + // them. + inner_data + } else { + // When we're not on nightly, we need to create an enclosing fn() scope + // for all of our generated items. This makes it impossible for + // user code to refer to any of our generated types, but has + // the advantage of preventing Rustdoc from displaying + // docs for any of our types. In particular, users cannot see + // the automatically generated Unpin impl for the '__UnpinStruct$Name' types. + quote! { + #[allow(non_snake_case)] + fn #scope_ident() { + #inner_data + } + } + } + } } #[allow(dead_code)] // https://github.com/rust-lang/rust/issues/56750 @@ -63,6 +257,8 @@ impl Parse for Args { } struct Context { + crate_path: Ident, + /// Name of the original type. orig_ident: Ident, @@ -72,9 +268,6 @@ struct Context { /// Name of the trait generated to provide a 'project' method. proj_trait: Ident, - /// Visibility of the original type. - vis: Visibility, - /// Generics of the original type. generics: Generics, @@ -84,12 +277,7 @@ struct Context { /// Lifetime on the generated projection trait. trait_lifetime: Lifetime, - /// `where`-clause for conditional `Unpin` implementation. - impl_unpin: WhereClause, - - pinned_fields: Vec, - - unsafe_unpin: bool, + unsafe_unpin: Option, pinned_drop: Option, } @@ -97,13 +285,21 @@ struct Context { impl Context { fn new( args: TokenStream, - orig_ident: &Ident, - vis: &Visibility, - generics: &Generics, + attrs: &mut Vec, + orig_ident: Ident, + generics: Generics, ) -> Result { let Args { pinned_drop, unsafe_unpin } = syn::parse2(args)?; - let proj_ident = proj_ident(orig_ident); - let proj_trait = proj_trait_ident(orig_ident); + + let crate_path = crate_path(); + if unsafe_unpin.is_none() { + attrs.push( + syn::parse_quote!(#[derive(#crate_path::__private::__PinProjectAutoImplUnpin)]), + ); + } + + let proj_ident = proj_ident(&orig_ident); + let proj_trait = proj_trait_ident(&orig_ident); let mut lifetime_name = String::from(DEFAULT_LIFETIME_NAME); proj_lifetime_name(&mut lifetime_name, &generics.params); @@ -113,29 +309,15 @@ impl Context { proj_lifetime_name(&mut trait_lifetime_name, &generics.params); let trait_lifetime = Lifetime::new(&trait_lifetime_name, Span::call_site()); - let mut generics = generics.clone(); - let mut impl_unpin = generics.make_where_clause().clone(); - if let Some(unsafe_unpin) = unsafe_unpin { - let crate_path = crate_path(); - impl_unpin.predicates.push( - syn::parse2(quote_spanned! { unsafe_unpin => - ::#crate_path::__private::Wrapper: ::#crate_path::UnsafeUnpin - }) - .unwrap(), - ); - } - Ok(Self { - orig_ident: orig_ident.clone(), + crate_path, + orig_ident, proj_ident, proj_trait, - vis: vis.clone(), generics, lifetime, trait_lifetime, - impl_unpin, - pinned_fields: vec![], - unsafe_unpin: unsafe_unpin.is_some(), + unsafe_unpin, pinned_drop, }) } @@ -154,153 +336,49 @@ impl Context { generics } - fn push_unpin_bounds(&mut self, ty: Type) { - self.pinned_fields.push(ty); + fn find_pin_attr(&self, attrs: &mut Vec) -> Result { + if let Some(pos) = attrs.position(PIN) { + let tokens = if self.unsafe_unpin.is_some() { + attrs.remove(pos).tokens + } else { + attrs[pos].tokens.clone() + }; + let _: Nothing = syn::parse2(tokens)?; + Ok(true) + } else { + Ok(false) + } } - /// Creates conditional `Unpin` implementation for original type. - fn make_unpin_impl(&self) -> TokenStream { + /// Creates `Unpin` implementation for original type if `UnsafeUnpin` argument used. + fn make_unpin_impl(&mut self) -> TokenStream { + let unsafe_unpin = if let Some(unsafe_unpin) = self.unsafe_unpin { + unsafe_unpin + } else { + // To generate the correct `Unpin` implementation, + // we need to collect the types of the pinned fields. + // However, since proc-macro-attribute is applied before cfg, + // we cannot be collecting field types at this timing. + // So instead of generating the `Unpin` implementation here, + // we need to delegate automatic generation of the `Unpin` + // implementation to proc-macro-derive. + return TokenStream::new(); + }; + + let mut where_clause = self.generics.make_where_clause().clone(); + let crate_path = &self.crate_path; let orig_ident = &self.orig_ident; let (impl_generics, ty_generics, _) = self.generics.split_for_impl(); - let type_params: Vec<_> = self.generics.type_params().map(|t| t.ident.clone()).collect(); - let where_clause = &self.impl_unpin; - - if self.unsafe_unpin { - quote! { - impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #where_clause {} - } - } else { - let make_span = || { - #[cfg(proc_macro_def_site)] - { - proc_macro::Span::def_site().into() - } - #[cfg(not(proc_macro_def_site))] - { - Span::call_site() - } - }; - let struct_ident = if cfg!(proc_macro_def_site) { - format_ident!("UnpinStruct{}", orig_ident, span = make_span()) - } else { - format_ident!("__UnpinStruct{}", orig_ident) - }; - let always_unpin_ident = format_ident!("AlwaysUnpin{}", orig_ident, span = make_span()); - - // Generate a field in our new struct for every - // pinned field in the original type. - let fields: Vec<_> = self - .pinned_fields - .iter() - .enumerate() - .map(|(i, ty)| { - let field_ident = format_ident!("__field{}", i); - quote! { - #field_ident: #ty - } - }) - .collect(); - - // 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(PhantomData) {} - // impl Unpin for AlwaysUnpin {} - // - // ... - // _field: AlwaysUnpin<(A, B, C)> - // ``` - // - // This ensures that any unused type paramters - // don't end up with Unpin bounds. - let lifetime_fields: Vec<_> = self - .generics - .lifetimes() - .enumerate() - .map(|(i, l)| { - let field_ident = format_ident!("__lifetime{}", i); - quote! { - #field_ident: &#l () - } - }) - .collect(); - - let scope_ident = format_ident!("__unpin_scope_{}", orig_ident); - - let vis = &self.vis; - let full_generics = &self.generics; - let mut full_where_clause = where_clause.clone(); - - let unpin_clause: WherePredicate = syn::parse_quote! { - #struct_ident #ty_generics: ::core::marker::Unpin - }; + where_clause.predicates.push( + syn::parse2(quote_spanned! { unsafe_unpin => + ::#crate_path::__private::Wrapper: ::#crate_path::UnsafeUnpin + }) + .unwrap(), + ); - full_where_clause.predicates.push(unpin_clause); - - let attrs = if cfg!(proc_macro_def_site) { quote!() } else { quote!(#[doc(hidden)]) }; - - let inner_data = quote! { - struct #always_unpin_ident { - val: ::core::marker::PhantomData - } - - impl ::core::marker::Unpin for #always_unpin_ident {} - - // This needs to have the same visibility as the original type, - // due to the limitations of the 'public in private' error. - // - // Out 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 take - // steps to ensure that the user can never actually reference - // this 'public' type. These steps are described below. - // - // See also https://github.com/taiki-e/pin-project/pull/53. - #[allow(dead_code)] - #attrs - #vis struct #struct_ident #full_generics #where_clause { - __pin_project_use_generics: #always_unpin_ident <(#(#type_params),*)>, - - #(#fields,)* - #(#lifetime_fields,)* - } - - impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #full_where_clause {} - }; - - if cfg!(proc_macro_def_site) { - // On nightly, we use def-site hygiene to make it impossible - // for user code to refer to any of the types we define. - // This allows us to omit wrapping the generated types - // in an fn() scope, allowing rustdoc to properly document - // them. - inner_data - } else { - // When we're not on nightly, we need to create an enclosing fn() scope - // for all of our generated items. This makes it impossible for - // user code to refer to any of our generated types, but has - // the advantage of preventing Rustdoc from displaying - // docs for any of our types. In particular, users cannot see - // the automatically generated Unpin impl for the '__UnpinStruct$Name' types. - quote! { - #[allow(non_snake_case)] - fn #scope_ident() { - #inner_data - } - } - } + quote! { + impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #where_clause {} } } @@ -310,7 +388,7 @@ impl Context { let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); if let Some(pinned_drop) = self.pinned_drop { - let crate_path = crate_path(); + let crate_path = &self.crate_path; let call = quote_spanned! { pinned_drop => ::#crate_path::__private::UnsafePinnedDrop::pinned_drop(pinned_self) }; @@ -419,10 +497,11 @@ impl Context { } } -fn parse(args: TokenStream, input: TokenStream) -> Result { - match syn::parse2(input)? { - Item::Struct(item) => { - let mut cx = Context::new(args, &item.ident, &item.vis, &item.generics)?; +fn parse_attribute(args: TokenStream, input: Item) -> Result { + match input { + Item::Struct(mut item) => { + let mut cx = + Context::new(args, &mut item.attrs, item.ident.clone(), item.generics.clone())?; let packed_check = ensure_not_packed(&item)?; let mut res = structs::parse(&mut cx, item)?; @@ -432,8 +511,9 @@ fn parse(args: TokenStream, input: TokenStream) -> Result { res.extend(packed_check); Ok(res) } - Item::Enum(item) => { - let mut cx = Context::new(args, &item.ident, &item.vis, &item.generics)?; + Item::Enum(mut item) => { + let mut cx = + Context::new(args, &mut item.attrs, item.ident.clone(), item.generics.clone())?; // We don't need to check for '#[repr(packed)]', // since it does not apply to enums. @@ -504,15 +584,19 @@ fn ensure_not_packed(item: &ItemStruct) -> Result { let mut field_refs = vec![]; match &item.fields { Fields::Named(FieldsNamed { named, .. }) => { - for field in named { - let ident = field.ident.as_ref().unwrap(); - field_refs.push(quote!(&val.#ident;)); + for Field { attrs, ident, .. } in named { + let cfg = collect_cfg(attrs); + field_refs.push(quote! { + #(#cfg)* { &val.#ident; } + }); } } Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { - for (i, _) in unnamed.iter().enumerate() { - let index = Index::from(i); - field_refs.push(quote!(&val.#index;)); + for (index, _) in unnamed.iter().enumerate() { + let index = Index::from(index); + field_refs.push(quote! { + &val.#index; + }); } } Fields::Unit => {} diff --git a/pin-project-internal/src/pin_project/structs.rs b/pin-project-internal/src/pin_project/structs.rs index 5202addd..75ce3497 100644 --- a/pin-project-internal/src/pin_project/structs.rs +++ b/pin-project-internal/src/pin_project/structs.rs @@ -1,10 +1,10 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use syn::{parse::Nothing, Field, Fields, FieldsNamed, FieldsUnnamed, Index, ItemStruct, Result}; +use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, Index, ItemStruct, Result}; -use crate::utils::VecExt; +use crate::utils::collect_cfg; -use super::{Context, PIN}; +use super::Context; pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result { let (proj_fields, proj_init) = match &mut item.fields { @@ -19,7 +19,7 @@ pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result { return Err(error!( - item, + item.ident, "#[pin_project] attribute may not be used on structs with units" )); } @@ -61,16 +61,23 @@ fn named( let mut proj_fields = Vec::with_capacity(fields.len()); let mut proj_init = Vec::with_capacity(fields.len()); for Field { attrs, ident, ty, .. } in fields { - if let Some(attr) = attrs.find_remove(PIN) { - let _: Nothing = syn::parse2(attr.tokens)?; - cx.push_unpin_bounds(ty.clone()); + let cfg = collect_cfg(attrs); + if cx.find_pin_attr(attrs)? { let lifetime = &cx.lifetime; - proj_fields.push(quote!(#ident: ::core::pin::Pin<&#lifetime mut #ty>)); - proj_init.push(quote!(#ident: ::core::pin::Pin::new_unchecked(&mut this.#ident))); + proj_fields.push(quote! { + #(#cfg)* #ident: ::core::pin::Pin<&#lifetime mut #ty> + }); + proj_init.push(quote! { + #(#cfg)* #ident: ::core::pin::Pin::new_unchecked(&mut this.#ident) + }); } else { let lifetime = &cx.lifetime; - proj_fields.push(quote!(#ident: &#lifetime mut #ty)); - proj_init.push(quote!(#ident: &mut this.#ident)); + proj_fields.push(quote! { + #(#cfg)* #ident: &#lifetime mut #ty + }); + proj_init.push(quote! { + #(#cfg)* #ident: &mut this.#ident + }); } } @@ -85,18 +92,31 @@ fn unnamed( ) -> Result<(TokenStream, TokenStream)> { let mut proj_fields = Vec::with_capacity(fields.len()); let mut proj_init = Vec::with_capacity(fields.len()); - for (i, Field { attrs, ty, .. }) in fields.iter_mut().enumerate() { - let i = Index::from(i); - if let Some(attr) = attrs.find_remove(PIN) { - let _: Nothing = syn::parse2(attr.tokens)?; - cx.push_unpin_bounds(ty.clone()); + for (index, Field { attrs, ty, .. }) in fields.iter_mut().enumerate() { + let index = Index::from(index); + let cfg = collect_cfg(attrs); + if !cfg.is_empty() { + return Err(error!( + cfg.first(), + "`cfg` attributes on the field of tuple structs are not supported" + )); + } + if cx.find_pin_attr(attrs)? { let lifetime = &cx.lifetime; - proj_fields.push(quote!(::core::pin::Pin<&#lifetime mut #ty>)); - proj_init.push(quote!(::core::pin::Pin::new_unchecked(&mut this.#i))); + proj_fields.push(quote! { + ::core::pin::Pin<&#lifetime mut #ty> + }); + proj_init.push(quote! { + ::core::pin::Pin::new_unchecked(&mut this.#index) + }); } else { let lifetime = &cx.lifetime; - proj_fields.push(quote!(&#lifetime mut #ty)); - proj_init.push(quote!(&mut this.#i)); + proj_fields.push(quote! { + &#lifetime mut #ty + }); + proj_init.push(quote! { + &mut this.#index + }); } } diff --git a/pin-project-internal/src/pinned_drop.rs b/pin-project-internal/src/pinned_drop.rs index 0397a204..83affe07 100644 --- a/pin-project-internal/src/pinned_drop.rs +++ b/pin-project-internal/src/pinned_drop.rs @@ -7,7 +7,7 @@ use syn::{ use crate::utils::crate_path; -pub(super) fn attribute(input: TokenStream) -> TokenStream { +pub(crate) fn attribute(input: &ItemFn) -> TokenStream { parse(input).unwrap_or_else(|e| e.to_compile_error()) } @@ -34,8 +34,7 @@ fn parse_arg(arg: &FnArg) -> Result<&Type> { Err(error!(arg, "#[pinned_drop] function must take a argument `Pin<&mut Type>`")) } -fn parse(input: TokenStream) -> Result { - let item: ItemFn = syn::parse2(input)?; +fn parse(item: &ItemFn) -> Result { if let ReturnType::Type(_, ty) = &item.sig.output { match &**ty { Type::Tuple(TypeTuple { elems, .. }) if elems.is_empty() => {} diff --git a/pin-project-internal/src/project.rs b/pin-project-internal/src/project.rs index d26c014f..181a2ee1 100644 --- a/pin-project-internal/src/project.rs +++ b/pin-project-internal/src/project.rs @@ -11,12 +11,11 @@ use crate::utils::{proj_generics, proj_ident, proj_lifetime_name, VecExt, DEFAUL /// The attribute name. const NAME: &str = "project"; -pub(super) fn attribute(input: TokenStream) -> TokenStream { +pub(crate) fn attribute(input: Stmt) -> TokenStream { parse(input).unwrap_or_else(|e| e.to_compile_error()) } -fn parse(input: TokenStream) -> Result { - let mut stmt = syn::parse2(input)?; +fn parse(mut stmt: Stmt) -> Result { match &mut stmt { Stmt::Expr(Expr::Match(expr)) | Stmt::Semi(Expr::Match(expr), _) => { Context::default().replace_expr_match(expr) diff --git a/pin-project-internal/src/utils.rs b/pin-project-internal/src/utils.rs index e6da4137..c29775fa 100644 --- a/pin-project-internal/src/utils.rs +++ b/pin-project-internal/src/utils.rs @@ -58,13 +58,21 @@ pub(crate) fn proj_generics(generics: &mut Generics, lifetime: Lifetime) { ); } +pub(crate) fn collect_cfg(attrs: &[Attribute]) -> Vec { + attrs.iter().filter(|attr| attr.path.is_ident("cfg")).cloned().collect() +} + pub(crate) trait VecExt { + fn position(&self, ident: &str) -> Option; fn find_remove(&mut self, ident: &str) -> Option; } impl VecExt for Vec { + fn position(&self, ident: &str) -> Option { + self.iter().position(|attr| attr.path.is_ident(ident)) + } fn find_remove(&mut self, ident: &str) -> Option { - self.iter().position(|attr| attr.path.is_ident(ident)).map(|i| self.remove(i)) + self.position(ident).map(|i| self.remove(i)) } } diff --git a/src/lib.rs b/src/lib.rs index 4a22705a..39381c0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,6 +124,9 @@ pub mod __private { use super::UnsafeUnpin; use core::pin::Pin; + #[doc(hidden)] + pub use pin_project_internal::__PinProjectAutoImplUnpin; + // This is an internal helper trait used by `pin-project-internal`. // This allows us to force an error if the user tries to provide // a regular `Drop` impl when they specify the `PinnedDrop` argument. diff --git a/tests/cfg.rs b/tests/cfg.rs new file mode 100644 index 00000000..95abe3c3 --- /dev/null +++ b/tests/cfg.rs @@ -0,0 +1,150 @@ +#![warn(unsafe_code)] +#![warn(rust_2018_idioms, single_use_lifetimes)] +#![allow(dead_code)] + +// Refs: https://doc.rust-lang.org/reference/attributes.html + +use core::marker::PhantomPinned; +use pin_project::pin_project; + +#[cfg(all(unix, target_os = "macos"))] +pub struct MacOS; +#[cfg(all(unix, not(target_os = "macos")))] +pub struct Linux; +#[cfg(windows)] +pub struct Windows; + +// Using `PhantomPinned: Unpin` without #![feature(trivial_bounds)] will result in an error. +// Use this type to check that `cfg(any())` is working properly. +pub struct Any(PhantomPinned); + +#[test] +fn struct_() { + #[pin_project] + pub struct SameName { + #[cfg(all(unix, target_os = "macos"))] + #[pin] + inner: MacOS, + #[cfg(all(unix, not(target_os = "macos")))] + #[pin] + inner: Linux, + #[cfg(windows)] + #[pin] + inner: Windows, + #[cfg(any())] + #[pin] + any: Any, + } + + #[cfg(all(unix, target_os = "macos"))] + let _x = SameName { inner: MacOS }; + #[cfg(all(unix, not(target_os = "macos")))] + let _x = SameName { inner: Linux }; + #[cfg(windows)] + let _x = SameName { inner: Windows }; + + #[pin_project] + pub struct DifferentName { + #[cfg(all(unix, target_os = "macos"))] + #[pin] + m: MacOS, + #[cfg(all(unix, not(target_os = "macos")))] + #[pin] + l: Linux, + #[cfg(windows)] + #[pin] + w: Windows, + #[cfg(any())] + #[pin] + a: Any, + } + + #[cfg(all(unix, target_os = "macos"))] + let _x = DifferentName { m: MacOS }; + #[cfg(all(unix, not(target_os = "macos")))] + let _x = DifferentName { l: Linux }; + #[cfg(windows)] + let _x = DifferentName { w: Windows }; +} + +#[test] +fn enum_() { + #[pin_project] + pub enum Variant { + #[cfg(all(unix, target_os = "macos"))] + Inner(#[pin] MacOS), + #[cfg(all(unix, not(target_os = "macos")))] + Inner(#[pin] Linux), + #[cfg(windows)] + Inner(#[pin] Windows), + + #[cfg(all(unix, target_os = "macos"))] + MacOS(#[pin] MacOS), + #[cfg(all(unix, not(target_os = "macos")))] + Linux(#[pin] Linux), + #[cfg(windows)] + Windows(#[pin] Windows), + #[cfg(any())] + Any(#[pin] Any), + } + + #[cfg(all(unix, target_os = "macos"))] + let _x = Variant::Inner(MacOS); + #[cfg(all(unix, not(target_os = "macos")))] + let _x = Variant::Inner(Linux); + #[cfg(windows)] + let _x = Variant::Inner(Windows); + + #[cfg(all(unix, target_os = "macos"))] + let _x = Variant::MacOS(MacOS); + #[cfg(all(unix, not(target_os = "macos")))] + let _x = Variant::Linux(Linux); + #[cfg(windows)] + let _x = Variant::Windows(Windows); + + #[pin_project] + pub enum Field { + SameName { + #[cfg(all(unix, target_os = "macos"))] + #[pin] + inner: MacOS, + #[cfg(all(unix, not(target_os = "macos")))] + #[pin] + inner: Linux, + #[cfg(windows)] + #[pin] + inner: Windows, + #[cfg(any())] + #[pin] + any: Any, + }, + DifferentName { + #[cfg(all(unix, target_os = "macos"))] + #[pin] + m: MacOS, + #[cfg(all(unix, not(target_os = "macos")))] + #[pin] + l: Linux, + #[cfg(windows)] + #[pin] + w: Windows, + #[cfg(any())] + #[pin] + any: Any, + }, + } + + #[cfg(all(unix, target_os = "macos"))] + let _x = Field::SameName { inner: MacOS }; + #[cfg(all(unix, not(target_os = "macos")))] + let _x = Field::SameName { inner: Linux }; + #[cfg(windows)] + let _x = Field::SameName { inner: Windows }; + + #[cfg(all(unix, target_os = "macos"))] + let _x = Field::DifferentName { m: MacOS }; + #[cfg(all(unix, not(target_os = "macos")))] + let _x = Field::DifferentName { l: Linux }; + #[cfg(windows)] + let _x = Field::DifferentName { w: Windows }; +} diff --git a/tests/pinned_drop.rs b/tests/pinned_drop.rs index 8c4436bf..4ff8b6b4 100644 --- a/tests/pinned_drop.rs +++ b/tests/pinned_drop.rs @@ -1,4 +1,3 @@ -#![no_std] #![warn(unsafe_code)] #![warn(rust_2018_idioms, single_use_lifetimes)] #![allow(dead_code)] diff --git a/tests/project.rs b/tests/project.rs index 874ab742..fd2b6d06 100644 --- a/tests/project.rs +++ b/tests/project.rs @@ -1,5 +1,4 @@ #![cfg(feature = "project_attr")] -#![no_std] #![warn(unsafe_code)] #![warn(rust_2018_idioms, single_use_lifetimes)] #![allow(dead_code)] diff --git a/tests/project_nightly.rs b/tests/project_nightly.rs index 58ee2552..53a13345 100644 --- a/tests/project_nightly.rs +++ b/tests/project_nightly.rs @@ -1,5 +1,4 @@ #![cfg(feature = "project_attr")] -#![no_std] #![warn(unsafe_code)] #![warn(rust_2018_idioms, single_use_lifetimes)] #![allow(dead_code)] diff --git a/tests/ui/cfg/tuple-struct.rs b/tests/ui/cfg/tuple-struct.rs new file mode 100644 index 00000000..7807120c --- /dev/null +++ b/tests/ui/cfg/tuple-struct.rs @@ -0,0 +1,20 @@ +// compile-fail + +use pin_project::pin_project; + +#[cfg(unix)] +pub struct Unix; +#[cfg(windows)] +pub struct Windows; + +#[pin_project] +pub struct TupleStruct( + #[cfg(unix)] //~ ERROR `cfg` attributes on the field of tuple structs are not supported + #[pin] + Unix, + #[cfg(windows)] + #[pin] + Windows, +); + +fn main() {} diff --git a/tests/ui/cfg/tuple-struct.stderr b/tests/ui/cfg/tuple-struct.stderr new file mode 100644 index 00000000..f3d6adf7 --- /dev/null +++ b/tests/ui/cfg/tuple-struct.stderr @@ -0,0 +1,8 @@ +error: `cfg` attributes on the field of tuple structs are not supported + --> $DIR/tuple-struct.rs:12:5 + | +12 | #[cfg(unix)] //~ ERROR `cfg` attributes on the field of tuple structs are not supported + | ^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/tests/ui/cfg/tuple-variant.rs b/tests/ui/cfg/tuple-variant.rs new file mode 100644 index 00000000..1eb7882a --- /dev/null +++ b/tests/ui/cfg/tuple-variant.rs @@ -0,0 +1,22 @@ +// compile-fail + +use pin_project::pin_project; + +#[cfg(unix)] +pub struct Unix; +#[cfg(windows)] +pub struct Windows; + +#[pin_project] +pub enum Field { + Tuple( + #[cfg(unix)] //~ ERROR `cfg` attributes on the field of tuple variants are not supported + #[pin] + Unix, + #[cfg(windows)] + #[pin] + Windows, + ), +} + +fn main() {} diff --git a/tests/ui/cfg/tuple-variant.stderr b/tests/ui/cfg/tuple-variant.stderr new file mode 100644 index 00000000..ca7a64ac --- /dev/null +++ b/tests/ui/cfg/tuple-variant.stderr @@ -0,0 +1,8 @@ +error: `cfg` attributes on the field of tuple variants are not supported + --> $DIR/tuple-variant.rs:13:9 + | +13 | #[cfg(unix)] //~ ERROR `cfg` attributes on the field of tuple variants are not supported + | ^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/tests/ui/pin_project/phantom-pinned.rs b/tests/ui/pin_project/phantom-pinned.rs new file mode 100644 index 00000000..52382757 --- /dev/null +++ b/tests/ui/pin_project/phantom-pinned.rs @@ -0,0 +1,13 @@ +// compile-fail + +// Refs: https://github.com/rust-lang/rust/issues/48214 + +use core::marker::PhantomPinned; +use pin_project::pin_project; + +struct Inner(PhantomPinned); + +#[pin_project] +struct Foo(#[pin] Inner); + +fn main() {} diff --git a/tests/ui/pin_project/phantom-pinned.stderr b/tests/ui/pin_project/phantom-pinned.stderr new file mode 100644 index 00000000..2499a2d6 --- /dev/null +++ b/tests/ui/pin_project/phantom-pinned.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `std::marker::PhantomPinned: std::marker::Unpin` is not satisfied in `UnpinStructFoo` + --> $DIR/phantom-pinned.rs:10:1 + | +10 | #[pin_project] + | ^^^^^^^^^^^^^^ within `UnpinStructFoo`, the trait `std::marker::Unpin` is not implemented for `std::marker::PhantomPinned` + | + = help: the following implementations were found: + + = note: required because it appears within the type `Inner` + = note: required because it appears within the type `UnpinStructFoo` + = help: see issue #48214 + = help: add `#![feature(trivial_bounds)]` to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/pin_project/proper_unpin.rs b/tests/ui/pin_project/proper_unpin.rs index 8a1ace14..a5369e9e 100644 --- a/tests/ui/pin_project/proper_unpin.rs +++ b/tests/ui/pin_project/proper_unpin.rs @@ -1,6 +1,6 @@ // compile-fail -use pin_project::{pin_project, pinned_drop}; +use pin_project::pin_project; use std::pin::Pin; struct Inner { diff --git a/tests/ui/pin_project/trivial_bounds.rs b/tests/ui/pin_project/trivial_bounds.rs new file mode 100644 index 00000000..c3abdb5a --- /dev/null +++ b/tests/ui/pin_project/trivial_bounds.rs @@ -0,0 +1,15 @@ +// run-pass + +// Refs: https://github.com/rust-lang/rust/issues/48214 + +#![feature(trivial_bounds)] + +use core::marker::PhantomPinned; +use pin_project::pin_project; + +struct Inner(PhantomPinned); + +#[pin_project] +struct Foo(#[pin] Inner); + +fn main() {} diff --git a/tests/ui/pin_project/unsupported.stderr b/tests/ui/pin_project/unsupported.stderr index 02442b4e..dc4715df 100644 --- a/tests/ui/pin_project/unsupported.stderr +++ b/tests/ui/pin_project/unsupported.stderr @@ -11,16 +11,16 @@ error: #[pin_project] attribute may not be used on structs with zero fields | ^^ error: #[pin_project] attribute may not be used on structs with units - --> $DIR/unsupported.rs:12:1 + --> $DIR/unsupported.rs:12:8 | 12 | struct Struct3; //~ ERROR may not be used on structs with units - | ^^^^^^^^^^^^^^^ + | ^^^^^^^ error: #[pin_project] attribute may not be used on enums without variants - --> $DIR/unsupported.rs:15:1 + --> $DIR/unsupported.rs:15:12 | 15 | enum Enum1 {} //~ ERROR may not be used on enums without variants - | ^^^^^^^^^^^^^ + | ^^ error: #[pin_project] attribute may not be used on enums with discriminants --> $DIR/unsupported.rs:19:9 diff --git a/tests/unsafe_unpin.rs b/tests/unsafe_unpin.rs index fb9404ad..f48618d4 100644 --- a/tests/unsafe_unpin.rs +++ b/tests/unsafe_unpin.rs @@ -1,4 +1,3 @@ -#![no_std] #![warn(unsafe_code)] #![warn(rust_2018_idioms, single_use_lifetimes)] #![allow(dead_code)] From fe55c4333e7bd5715e8ba6091b0d405a02c6e663 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sat, 7 Sep 2019 20:32:44 +0900 Subject: [PATCH 2/6] Add validation to proc-macro-derive We need to check this on proc-macro-derive because cfg may reduce the fields. On the other hand, if we check this only on proc-macro-derive, it may generate unhelpful error messages. So, we need to check this on both proc-macro-attribute and proc-macro-derive. --- .../src/pin_project/derive.rs | 211 ++++++++++++++++++ pin-project-internal/src/pin_project/enums.rs | 26 ++- pin-project-internal/src/pin_project/mod.rs | 198 +--------------- .../src/pin_project/structs.rs | 31 +-- tests/ui/cfg/unsupported.rs | 13 ++ tests/ui/cfg/unsupported.stderr | 13 ++ 6 files changed, 277 insertions(+), 215 deletions(-) create mode 100644 pin-project-internal/src/pin_project/derive.rs create mode 100644 tests/ui/cfg/unsupported.rs create mode 100644 tests/ui/cfg/unsupported.stderr diff --git a/pin-project-internal/src/pin_project/derive.rs b/pin-project-internal/src/pin_project/derive.rs new file mode 100644 index 00000000..8c4c0441 --- /dev/null +++ b/pin-project-internal/src/pin_project/derive.rs @@ -0,0 +1,211 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse::Nothing, *}; + +use crate::utils::VecExt; + +use super::PIN; + +pub(super) fn parse_derive(input: DeriveInput) -> Result { + let DeriveInput { vis, ident, generics, mut data, .. } = input; + let mut cx = DeriveContext::new(ident, vis, generics); + match &mut data { + Data::Struct(data) => { + // We need to check this on proc-macro-derive because cfg may reduce + // the fields. On the other hand, if we check this only on + // proc-macro-derive, it may generate unhelpful error messages. + // So, we need to check this on both proc-macro-attribute + // and proc-macro-derive. + super::structs::validate(&cx.ident, &data.fields)?; + cx.fields(&mut data.fields) + } + Data::Enum(data) => { + super::enums::validate(data.brace_token, &data.variants)?; + cx.variants(data) + } + Data::Union(_) => unreachable!(), + } + + Ok(cx.make_unpin_impl()) +} + +struct DeriveContext { + /// Name of the original type. + ident: Ident, + + /// Visibility of the original type. + vis: Visibility, + + /// Generics of the original type. + generics: Generics, + + /// Types of the pinned fields. + pinned_fields: Vec, +} + +impl DeriveContext { + fn new(ident: Ident, vis: Visibility, generics: Generics) -> Self { + Self { ident, vis, generics, pinned_fields: Vec::new() } + } + + fn variants(&mut self, data: &mut DataEnum) { + for Variant { fields, .. } in &mut data.variants { + self.fields(fields) + } + } + + fn fields(&mut self, fields: &mut Fields) { + let fields = match fields { + Fields::Unnamed(fields) => &mut fields.unnamed, + Fields::Named(fields) => &mut fields.named, + Fields::Unit => return, + }; + + for Field { attrs, ty, .. } in fields { + if let Some(attr) = attrs.position(PIN).and_then(|i| attrs.get(i)) { + let _: Nothing = syn::parse2(attr.tokens.clone()).unwrap(); + self.pinned_fields.push(ty.clone()); + } + } + } + + /// Creates conditional `Unpin` implementation for original type. + fn make_unpin_impl(&mut self) -> TokenStream { + let where_clause = self.generics.make_where_clause().clone(); + let orig_ident = &self.ident; + let (impl_generics, ty_generics, _) = self.generics.split_for_impl(); + let type_params: Vec<_> = self.generics.type_params().map(|t| t.ident.clone()).collect(); + + let make_span = || { + #[cfg(proc_macro_def_site)] + { + proc_macro::Span::def_site().into() + } + #[cfg(not(proc_macro_def_site))] + { + proc_macro2::Span::call_site() + } + }; + + let struct_ident = if cfg!(proc_macro_def_site) { + format_ident!("UnpinStruct{}", orig_ident, span = make_span()) + } else { + format_ident!("__UnpinStruct{}", orig_ident) + }; + let always_unpin_ident = format_ident!("AlwaysUnpin{}", orig_ident, span = make_span()); + + // Generate a field in our new struct for every + // pinned field in the original type. + let fields: Vec<_> = self + .pinned_fields + .iter() + .enumerate() + .map(|(i, ty)| { + let field_ident = format_ident!("__field{}", i); + quote! { + #field_ident: #ty + } + }) + .collect(); + + // 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(PhantomData) {} + // impl Unpin for AlwaysUnpin {} + // + // ... + // _field: AlwaysUnpin<(A, B, C)> + // ``` + // + // This ensures that any unused type paramters + // don't end up with Unpin bounds. + let lifetime_fields: Vec<_> = self + .generics + .lifetimes() + .enumerate() + .map(|(i, l)| { + let field_ident = format_ident!("__lifetime{}", i); + quote! { + #field_ident: &#l () + } + }) + .collect(); + + let scope_ident = format_ident!("__unpin_scope_{}", orig_ident); + + let vis = &self.vis; + let full_generics = &self.generics; + let mut full_where_clause = where_clause.clone(); + + let unpin_clause: WherePredicate = syn::parse_quote! { + #struct_ident #ty_generics: ::core::marker::Unpin + }; + + full_where_clause.predicates.push(unpin_clause); + + let attrs = if cfg!(proc_macro_def_site) { quote!() } else { quote!(#[doc(hidden)]) }; + + let inner_data = quote! { + struct #always_unpin_ident { + val: ::core::marker::PhantomData + } + + impl ::core::marker::Unpin for #always_unpin_ident {} + + // This needs to have the same visibility as the original type, + // due to the limitations of the 'public in private' error. + // + // Out 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 take + // steps to ensure that the user can never actually reference + // this 'public' type. These steps are described below. + // + // See also https://github.com/taiki-e/pin-project/pull/53. + #[allow(dead_code)] + #attrs + #vis struct #struct_ident #full_generics #where_clause { + __pin_project_use_generics: #always_unpin_ident <(#(#type_params),*)>, + + #(#fields,)* + #(#lifetime_fields,)* + } + + impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #full_where_clause {} + }; + + if cfg!(proc_macro_def_site) { + // On nightly, we use def-site hygiene to make it impossible + // for user code to refer to any of the types we define. + // This allows us to omit wrapping the generated types + // in an fn() scope, allowing rustdoc to properly document + // them. + inner_data + } else { + // When we're not on nightly, we need to create an enclosing fn() scope + // for all of our generated items. This makes it impossible for + // user code to refer to any of our generated types, but has + // the advantage of preventing Rustdoc from displaying + // docs for any of our types. In particular, users cannot see + // the automatically generated Unpin impl for the '__UnpinStruct$Name' types. + quote! { + #[allow(non_snake_case)] + fn #scope_ident() { + #inner_data + } + } + } + } +} diff --git a/pin-project-internal/src/pin_project/enums.rs b/pin-project-internal/src/pin_project/enums.rs index 6a529dcf..1f177314 100644 --- a/pin-project-internal/src/pin_project/enums.rs +++ b/pin-project-internal/src/pin_project/enums.rs @@ -1,19 +1,19 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; -use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, ItemEnum, Result, Variant}; +use syn::{token, Field, Fields, FieldsNamed, FieldsUnnamed, ItemEnum, Result, Variant}; use crate::utils::collect_cfg; -use super::Context; +use super::{Context, Variants}; -pub(super) fn parse(cx: &mut Context, mut item: ItemEnum) -> Result { - if item.variants.is_empty() { +pub(super) fn validate(brace_token: token::Brace, variants: &Variants) -> Result<()> { + if variants.is_empty() { return Err(syn::Error::new( - item.brace_token.span, + brace_token.span, "#[pin_project] attribute may not be used on enums without variants", )); } - let has_field = item.variants.iter().try_fold(false, |has_field, v| { + 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 Fields::Unit = v.fields { @@ -22,12 +22,18 @@ pub(super) fn parse(cx: &mut Context, mut item: ItemEnum) -> Result Ok(true) } })?; - if !has_field { - return Err(error!( - item.variants, + if has_field { + Ok(()) + } else { + Err(error!( + variants, "#[pin_project] attribute may not be used on enums that have no field" - )); + )) } +} + +pub(super) fn parse(cx: &mut Context, mut item: ItemEnum) -> Result { + validate(item.brace_token, &item.variants)?; let (proj_variants, proj_arms) = variants(cx, &mut item)?; diff --git a/pin-project-internal/src/pin_project/mod.rs b/pin-project-internal/src/pin_project/mod.rs index b5aff2a7..50e37c3e 100644 --- a/pin-project-internal/src/pin_project/mod.rs +++ b/pin-project-internal/src/pin_project/mod.rs @@ -1,8 +1,8 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::{ - parse::Nothing, - parse::{Parse, ParseStream}, + parse::{Nothing, Parse, ParseStream}, + punctuated::Punctuated, token::Comma, *, }; @@ -12,207 +12,21 @@ use crate::utils::{ DEFAULT_LIFETIME_NAME, TRAIT_LIFETIME_NAME, }; +mod derive; mod enums; mod structs; /// The annotation for pinned type. const PIN: &str = "pin"; +type Variants = Punctuated; + pub(crate) fn attribute(args: TokenStream, input: Item) -> TokenStream { parse_attribute(args, input).unwrap_or_else(|e| e.to_compile_error()) } pub(crate) fn derive(input: DeriveInput) -> TokenStream { - let DeriveInput { vis, ident, generics, mut data, .. } = input; - let mut cx = DeriveContext::new(ident, vis, generics); - match &mut data { - Data::Struct(data) => cx.fields(&mut data.fields), - Data::Enum(data) => cx.variants(data), - Data::Union(_) => unreachable!(), - } - - cx.make_unpin_impl() -} - -struct DeriveContext { - /// Name of the original type. - orig_ident: Ident, - - /// Visibility of the original type. - vis: Visibility, - - /// Generics of the original type. - generics: Generics, - - /// Types of the pinned fields. - pinned_fields: Vec, -} - -impl DeriveContext { - fn new(orig_ident: Ident, vis: Visibility, generics: Generics) -> Self { - Self { orig_ident, vis, generics, pinned_fields: Vec::new() } - } - - fn variants(&mut self, data: &mut DataEnum) { - for Variant { fields, .. } in &mut data.variants { - self.fields(fields) - } - } - - fn fields(&mut self, fields: &mut Fields) { - let fields = match fields { - Fields::Unnamed(fields) => &mut fields.unnamed, - Fields::Named(fields) => &mut fields.named, - Fields::Unit => return, - }; - - for Field { attrs, ty, .. } in fields { - if let Some(attr) = attrs.position(PIN).and_then(|i| attrs.get(i)) { - let _: Nothing = syn::parse2(attr.tokens.clone()).unwrap(); - self.pinned_fields.push(ty.clone()); - } - } - } - - /// Creates conditional `Unpin` implementation for original type. - fn make_unpin_impl(&mut self) -> TokenStream { - let where_clause = self.generics.make_where_clause().clone(); - let orig_ident = &self.orig_ident; - let (impl_generics, ty_generics, _) = self.generics.split_for_impl(); - let type_params: Vec<_> = self.generics.type_params().map(|t| t.ident.clone()).collect(); - - let make_span = || { - #[cfg(proc_macro_def_site)] - { - proc_macro::Span::def_site().into() - } - #[cfg(not(proc_macro_def_site))] - { - Span::call_site() - } - }; - - let struct_ident = if cfg!(proc_macro_def_site) { - format_ident!("UnpinStruct{}", orig_ident, span = make_span()) - } else { - format_ident!("__UnpinStruct{}", orig_ident) - }; - let always_unpin_ident = format_ident!("AlwaysUnpin{}", orig_ident, span = make_span()); - - // Generate a field in our new struct for every - // pinned field in the original type. - let fields: Vec<_> = self - .pinned_fields - .iter() - .enumerate() - .map(|(i, ty)| { - let field_ident = format_ident!("__field{}", i); - quote! { - #field_ident: #ty - } - }) - .collect(); - - // 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(PhantomData) {} - // impl Unpin for AlwaysUnpin {} - // - // ... - // _field: AlwaysUnpin<(A, B, C)> - // ``` - // - // This ensures that any unused type paramters - // don't end up with Unpin bounds. - let lifetime_fields: Vec<_> = self - .generics - .lifetimes() - .enumerate() - .map(|(i, l)| { - let field_ident = format_ident!("__lifetime{}", i); - quote! { - #field_ident: &#l () - } - }) - .collect(); - - let scope_ident = format_ident!("__unpin_scope_{}", orig_ident); - - let vis = &self.vis; - let full_generics = &self.generics; - let mut full_where_clause = where_clause.clone(); - - let unpin_clause: WherePredicate = syn::parse_quote! { - #struct_ident #ty_generics: ::core::marker::Unpin - }; - - full_where_clause.predicates.push(unpin_clause); - - let attrs = if cfg!(proc_macro_def_site) { quote!() } else { quote!(#[doc(hidden)]) }; - - let inner_data = quote! { - struct #always_unpin_ident { - val: ::core::marker::PhantomData - } - - impl ::core::marker::Unpin for #always_unpin_ident {} - - // This needs to have the same visibility as the original type, - // due to the limitations of the 'public in private' error. - // - // Out 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 take - // steps to ensure that the user can never actually reference - // this 'public' type. These steps are described below. - // - // See also https://github.com/taiki-e/pin-project/pull/53. - #[allow(dead_code)] - #attrs - #vis struct #struct_ident #full_generics #where_clause { - __pin_project_use_generics: #always_unpin_ident <(#(#type_params),*)>, - - #(#fields,)* - #(#lifetime_fields,)* - } - - impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #full_where_clause {} - }; - - if cfg!(proc_macro_def_site) { - // On nightly, we use def-site hygiene to make it impossible - // for user code to refer to any of the types we define. - // This allows us to omit wrapping the generated types - // in an fn() scope, allowing rustdoc to properly document - // them. - inner_data - } else { - // When we're not on nightly, we need to create an enclosing fn() scope - // for all of our generated items. This makes it impossible for - // user code to refer to any of our generated types, but has - // the advantage of preventing Rustdoc from displaying - // docs for any of our types. In particular, users cannot see - // the automatically generated Unpin impl for the '__UnpinStruct$Name' types. - quote! { - #[allow(non_snake_case)] - fn #scope_ident() { - #inner_data - } - } - } - } + derive::parse_derive(input).unwrap_or_else(|e| e.to_compile_error()) } #[allow(dead_code)] // https://github.com/rust-lang/rust/issues/56750 diff --git a/pin-project-internal/src/pin_project/structs.rs b/pin-project-internal/src/pin_project/structs.rs index 75ce3497..ad389e9d 100644 --- a/pin-project-internal/src/pin_project/structs.rs +++ b/pin-project-internal/src/pin_project/structs.rs @@ -1,31 +1,36 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, Index, ItemStruct, Result}; +use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, Ident, Index, ItemStruct, Result}; use crate::utils::collect_cfg; use super::Context; -pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result { - let (proj_fields, proj_init) = match &mut item.fields { - Fields::Named(FieldsNamed { named: fields, .. }) - | Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. }) - if fields.is_empty() => +pub(super) fn validate(ident: &Ident, fields: &Fields) -> Result<()> { + match fields { + Fields::Named(FieldsNamed { named: f, .. }) + | Fields::Unnamed(FieldsUnnamed { unnamed: f, .. }) + if f.is_empty() => { - return Err(error!( - item.fields, + Err(error!( + fields, "#[pin_project] attribute may not be used on structs with zero fields" - )); + )) } Fields::Unit => { - return Err(error!( - item.ident, - "#[pin_project] attribute may not be used on structs with units" - )); + Err(error!(ident, "#[pin_project] attribute may not be used on structs with units")) } + _ => Ok(()), + } +} +pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result { + validate(&item.ident, &item.fields)?; + + let (proj_fields, proj_init) = match &mut item.fields { Fields::Named(fields) => named(cx, fields)?, Fields::Unnamed(fields) => unnamed(cx, fields)?, + Fields::Unit => unreachable!(), }; let proj_ident = &cx.proj_ident; diff --git a/tests/ui/cfg/unsupported.rs b/tests/ui/cfg/unsupported.rs new file mode 100644 index 00000000..3eaceaee --- /dev/null +++ b/tests/ui/cfg/unsupported.rs @@ -0,0 +1,13 @@ +// compile-fail + +use pin_project::pin_project; + +#[pin_project] +struct Struct1 { + //~^ ERROR may not be used on structs with zero fields + #[cfg(any())] + #[pin] + f: u8, +} + +fn main() {} diff --git a/tests/ui/cfg/unsupported.stderr b/tests/ui/cfg/unsupported.stderr new file mode 100644 index 00000000..8f820964 --- /dev/null +++ b/tests/ui/cfg/unsupported.stderr @@ -0,0 +1,13 @@ +error: #[pin_project] attribute may not be used on structs with zero fields + +error[E0392]: parameter `'_pin` is never used + --> $DIR/unsupported.rs:5:1 + | +5 | #[pin_project] + | ^^^^^^^^^^^^^^ unused parameter + | + = help: consider removing `'_pin` or using a marker such as `std::marker::PhantomData` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0392`. From 5b09db5f83cd4870f7c90b671e77589babec411e Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sat, 7 Sep 2019 22:55:17 +0900 Subject: [PATCH 3/6] Prevent other proc macros from adding fields after the #[pin_project] attribute is applied --- pin-project-internal/src/pin_project/enums.rs | 13 ++- .../src/pin_project/structs.rs | 91 ++----------------- tests/ui/cfg/auxiliary/sneaky_macro.rs | 62 +++++++++++++ tests/ui/cfg/field_sneaky.rs | 19 ++++ tests/ui/cfg/field_sneaky.stderr | 9 ++ 5 files changed, 107 insertions(+), 87 deletions(-) create mode 100644 tests/ui/cfg/auxiliary/sneaky_macro.rs create mode 100644 tests/ui/cfg/field_sneaky.rs create mode 100644 tests/ui/cfg/field_sneaky.stderr diff --git a/pin-project-internal/src/pin_project/enums.rs b/pin-project-internal/src/pin_project/enums.rs index 1f177314..7a7c653d 100644 --- a/pin-project-internal/src/pin_project/enums.rs +++ b/pin-project-internal/src/pin_project/enums.rs @@ -70,8 +70,8 @@ fn variants(cx: &mut Context, item: &mut ItemEnum) -> Result<(Vec, let mut proj_arms = Vec::with_capacity(item.variants.len()); for Variant { attrs, fields, ident, .. } in &mut item.variants { let (proj_pat, proj_body, proj_fields) = match fields { - Fields::Unnamed(fields) => unnamed(cx, fields)?, Fields::Named(fields) => named(cx, fields)?, + Fields::Unnamed(fields) => unnamed(cx, fields, false)?, Fields::Unit => (TokenStream::new(), TokenStream::new(), TokenStream::new()), }; let cfg = collect_cfg(attrs); @@ -89,7 +89,7 @@ fn variants(cx: &mut Context, item: &mut ItemEnum) -> Result<(Vec, Ok((proj_variants, proj_arms)) } -fn named( +pub(super) fn named( cx: &mut Context, FieldsNamed { named: fields, .. }: &mut FieldsNamed, ) -> Result<(TokenStream, TokenStream, TokenStream)> { @@ -126,9 +126,10 @@ fn named( Ok((proj_pat, proj_body, proj_fields)) } -fn unnamed( +pub(super) fn unnamed( cx: &mut Context, FieldsUnnamed { unnamed: fields, .. }: &mut FieldsUnnamed, + is_struct: bool, ) -> Result<(TokenStream, TokenStream, TokenStream)> { let mut proj_pat = Vec::with_capacity(fields.len()); let mut proj_body = Vec::with_capacity(fields.len()); @@ -139,7 +140,8 @@ fn unnamed( if !cfg.is_empty() { return Err(error!( cfg.first(), - "`cfg` attributes on the field of tuple variants are not supported" + "`cfg` attributes on the field of tuple {} are not supported", + if is_struct { "structs" } else { "variants" } )); } if cx.find_pin_attr(attrs)? { @@ -166,6 +168,7 @@ fn unnamed( let proj_pat = quote!((#(#proj_pat),*)); let proj_body = quote!((#(#proj_body),*)); - let proj_fields = quote!((#(#proj_fields),*)); + let proj_fields = + if is_struct { quote!((#(#proj_fields),*);) } else { quote!((#(#proj_fields),*)) }; Ok((proj_pat, proj_body, proj_fields)) } diff --git a/pin-project-internal/src/pin_project/structs.rs b/pin-project-internal/src/pin_project/structs.rs index ad389e9d..4a6c8e77 100644 --- a/pin-project-internal/src/pin_project/structs.rs +++ b/pin-project-internal/src/pin_project/structs.rs @@ -1,8 +1,6 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, Ident, Index, ItemStruct, Result}; - -use crate::utils::collect_cfg; +use syn::{Fields, FieldsNamed, FieldsUnnamed, Ident, ItemStruct, Result}; use super::Context; @@ -27,13 +25,13 @@ pub(super) fn validate(ident: &Ident, fields: &Fields) -> Result<()> { pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result { validate(&item.ident, &item.fields)?; - let (proj_fields, proj_init) = match &mut item.fields { - Fields::Named(fields) => named(cx, fields)?, - Fields::Unnamed(fields) => unnamed(cx, fields)?, + let (proj_pat, proj_body, proj_fields) = match &mut item.fields { + Fields::Named(fields) => super::enums::named(cx, fields)?, + Fields::Unnamed(fields) => super::enums::unnamed(cx, fields, true)?, Fields::Unit => unreachable!(), }; - let proj_ident = &cx.proj_ident; + let Context { orig_ident, proj_ident, .. } = &cx; let proj_generics = cx.proj_generics(); let where_clause = item.generics.split_for_impl().2; @@ -44,12 +42,12 @@ pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result Result Result<(TokenStream, TokenStream)> { - let mut proj_fields = Vec::with_capacity(fields.len()); - let mut proj_init = Vec::with_capacity(fields.len()); - for Field { attrs, ident, ty, .. } in fields { - let cfg = collect_cfg(attrs); - if cx.find_pin_attr(attrs)? { - let lifetime = &cx.lifetime; - proj_fields.push(quote! { - #(#cfg)* #ident: ::core::pin::Pin<&#lifetime mut #ty> - }); - proj_init.push(quote! { - #(#cfg)* #ident: ::core::pin::Pin::new_unchecked(&mut this.#ident) - }); - } else { - let lifetime = &cx.lifetime; - proj_fields.push(quote! { - #(#cfg)* #ident: &#lifetime mut #ty - }); - proj_init.push(quote! { - #(#cfg)* #ident: &mut this.#ident - }); - } - } - - let proj_fields = quote!({ #(#proj_fields,)* }); - let proj_init = quote!({ #(#proj_init,)* }); - Ok((proj_fields, proj_init)) -} - -fn unnamed( - cx: &mut Context, - FieldsUnnamed { unnamed: fields, .. }: &mut FieldsUnnamed, -) -> Result<(TokenStream, TokenStream)> { - let mut proj_fields = Vec::with_capacity(fields.len()); - let mut proj_init = Vec::with_capacity(fields.len()); - for (index, Field { attrs, ty, .. }) in fields.iter_mut().enumerate() { - let index = Index::from(index); - let cfg = collect_cfg(attrs); - if !cfg.is_empty() { - return Err(error!( - cfg.first(), - "`cfg` attributes on the field of tuple structs are not supported" - )); - } - if cx.find_pin_attr(attrs)? { - let lifetime = &cx.lifetime; - proj_fields.push(quote! { - ::core::pin::Pin<&#lifetime mut #ty> - }); - proj_init.push(quote! { - ::core::pin::Pin::new_unchecked(&mut this.#index) - }); - } else { - let lifetime = &cx.lifetime; - proj_fields.push(quote! { - &#lifetime mut #ty - }); - proj_init.push(quote! { - &mut this.#index - }); - } - } - - let proj_fields = quote!((#(#proj_fields,)*);); - let proj_init = quote!((#(#proj_init,)*)); - Ok((proj_fields, proj_init)) -} diff --git a/tests/ui/cfg/auxiliary/sneaky_macro.rs b/tests/ui/cfg/auxiliary/sneaky_macro.rs new file mode 100644 index 00000000..ccf56cc6 --- /dev/null +++ b/tests/ui/cfg/auxiliary/sneaky_macro.rs @@ -0,0 +1,62 @@ +// force-host +// no-prefer-dynamic + +#![crate_type = "proc-macro"] + +extern crate proc_macro; + +use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; + +fn op(c: char) -> TokenTree { + Punct::new(c, Spacing::Alone).into() +} + +fn ident(sym: &str) -> Ident { + Ident::new(sym, Span::call_site()) +} + +fn word(sym: &str) -> TokenTree { + ident(sym).into() +} + +#[proc_macro_attribute] +pub fn add_pinned_field(_: TokenStream, input: TokenStream) -> TokenStream { + let mut tokens: Vec<_> = input.into_iter().collect(); + if let Some(TokenTree::Group(g)) = tokens.pop() { + let mut vec = vec![]; + vec.extend(g.stream()); + + // #[pin] + // __field: __HiddenPinnedField + vec.push(op('#')); + vec.push(TokenTree::Group(Group::new(Delimiter::Bracket, word("pin").into()))); + vec.push(word("__field")); + vec.push(op(':')); + vec.push(word("__HiddenPinnedField")); + + tokens.extend(TokenStream::from(TokenTree::Group(Group::new( + Delimiter::Brace, + vec.into_iter().collect(), + )))); + let mut vec = vec![]; + + // pub struct __HiddenPinnedField; + vec.push(word("pub")); + vec.push(word("struct")); + vec.push(word("__HiddenPinnedField")); + vec.push(op(';')); + + // impl !Unpin for __HiddenPinnedField {} + vec.push(word("impl")); + vec.push(op('!')); + vec.push(word("Unpin")); + vec.push(word("for")); + vec.push(word("__HiddenPinnedField")); + vec.push(TokenTree::Group(Group::new(Delimiter::Brace, TokenStream::new()))); + tokens.extend(vec); + + tokens.into_iter().collect() + } else { + unreachable!() + } +} diff --git a/tests/ui/cfg/field_sneaky.rs b/tests/ui/cfg/field_sneaky.rs new file mode 100644 index 00000000..270f53c6 --- /dev/null +++ b/tests/ui/cfg/field_sneaky.rs @@ -0,0 +1,19 @@ +// compile-fail +// aux-build:sneaky_macro.rs + +#![feature(optin_builtin_traits)] +#![feature(trivial_bounds)] + +#[macro_use] +extern crate sneaky_macro; + +use pin_project::pin_project; + +#[pin_project] //~ ERROR pattern does not mention field `__field` +#[add_pinned_field] +struct Foo { + #[pin] + field: u32, +} + +fn main() {} diff --git a/tests/ui/cfg/field_sneaky.stderr b/tests/ui/cfg/field_sneaky.stderr new file mode 100644 index 00000000..b3bac20e --- /dev/null +++ b/tests/ui/cfg/field_sneaky.stderr @@ -0,0 +1,9 @@ +error[E0027]: pattern does not mention field `__field` + --> $DIR/field_sneaky.rs:12:14 + | +12 | #[pin_project] //~ ERROR pattern does not mention field `__field` + | ^ missing field `__field` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0027`. From 9e04e2a3dc2bf4cbb3be60f6acc492765b434908 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 8 Sep 2019 00:33:33 +0900 Subject: [PATCH 4/6] Merge enums.rs and struct.rs into attribute.rs --- .../src/pin_project/attribute.rs | 611 ++++++++++++++++++ .../src/pin_project/derive.rs | 19 +- pin-project-internal/src/pin_project/enums.rs | 174 ----- pin-project-internal/src/pin_project/mod.rs | 449 ++----------- .../src/pin_project/structs.rs | 58 -- 5 files changed, 659 insertions(+), 652 deletions(-) create mode 100644 pin-project-internal/src/pin_project/attribute.rs delete mode 100644 pin-project-internal/src/pin_project/enums.rs delete mode 100644 pin-project-internal/src/pin_project/structs.rs diff --git a/pin-project-internal/src/pin_project/attribute.rs b/pin-project-internal/src/pin_project/attribute.rs new file mode 100644 index 00000000..233e8e95 --- /dev/null +++ b/pin-project-internal/src/pin_project/attribute.rs @@ -0,0 +1,611 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use syn::{ + parse::{Nothing, Parse, ParseStream}, + token::Comma, + *, +}; + +use crate::utils::{ + self, collect_cfg, crate_path, proj_ident, proj_lifetime_name, proj_trait_ident, VecExt, + DEFAULT_LIFETIME_NAME, TRAIT_LIFETIME_NAME, +}; + +use super::PIN; + +pub(super) fn parse_attribute(args: TokenStream, input: Item) -> Result { + match input { + Item::Struct(mut item) => { + let mut cx = + Context::new(args, &mut item.attrs, item.ident.clone(), item.generics.clone())?; + + let packed_check = ensure_not_packed(&item)?; + let proj_items = cx.parse_struct(&mut item)?; + let mut item = item.into_token_stream(); + item.extend(proj_items); + item.extend(cx.make_proj_trait()); + item.extend(cx.make_unpin_impl()); + item.extend(cx.make_drop_impl()); + item.extend(packed_check); + Ok(item) + } + Item::Enum(mut item) => { + let mut cx = + Context::new(args, &mut item.attrs, item.ident.clone(), item.generics.clone())?; + + // We don't need to check for '#[repr(packed)]', + // since it does not apply to enums. + let proj_items = cx.parse_enum(&mut item)?; + let mut item = item.into_token_stream(); + item.extend(proj_items); + item.extend(cx.make_proj_trait()); + item.extend(cx.make_unpin_impl()); + item.extend(cx.make_drop_impl()); + Ok(item) + } + item => Err(error!(item, "#[pin_project] attribute may only be used on structs or enums")), + } +} + +#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/56750 +struct Args { + pinned_drop: Option, + unsafe_unpin: Option, +} + +impl Parse for Args { + fn parse(input: ParseStream<'_>) -> Result { + let mut pinned_drop = None; + let mut unsafe_unpin = None; + while !input.is_empty() { + let arg = input.parse::()?; + match &*arg.to_string() { + "PinnedDrop" => { + if pinned_drop.is_some() { + return Err(error!(arg, "duplicate `PinnedDrop` argument")); + } + pinned_drop = Some(arg.span()); + } + "UnsafeUnpin" => { + if unsafe_unpin.is_some() { + return Err(error!(arg, "duplicate `UnsafeUnpin` argument")); + } + unsafe_unpin = Some(arg.span()); + } + _ => { + return Err(error!( + arg, + "an invalid argument was passed to #[pin_project] attribute" + )); + } + } + + if !input.is_empty() { + let _: Comma = input.parse()?; + } + } + + Ok(Self { pinned_drop, unsafe_unpin }) + } +} + +struct Context { + crate_path: Ident, + + /// Name of the original type. + orig_ident: Ident, + + /// Name of the projected type. + proj_ident: Ident, + + /// Name of the trait generated to provide a 'project' method. + proj_trait: Ident, + + /// Generics of the original type. + generics: Generics, + + /// Lifetime on the generated projected type. + lifetime: Lifetime, + + /// Lifetime on the generated projection trait. + trait_lifetime: Lifetime, + + /// `UnsafeUnpin` attribute. + unsafe_unpin: Option, + + /// `PinnedDrop` attribute. + pinned_drop: Option, +} + +impl Context { + fn new( + args: TokenStream, + attrs: &mut Vec, + orig_ident: Ident, + generics: Generics, + ) -> Result { + let Args { pinned_drop, unsafe_unpin } = syn::parse2(args)?; + + let crate_path = crate_path(); + if unsafe_unpin.is_none() { + attrs.push( + syn::parse_quote!(#[derive(#crate_path::__private::__PinProjectAutoImplUnpin)]), + ); + } + + let proj_ident = proj_ident(&orig_ident); + let proj_trait = proj_trait_ident(&orig_ident); + + let mut lifetime_name = String::from(DEFAULT_LIFETIME_NAME); + proj_lifetime_name(&mut lifetime_name, &generics.params); + let lifetime = Lifetime::new(&lifetime_name, Span::call_site()); + + let mut trait_lifetime_name = String::from(TRAIT_LIFETIME_NAME); + proj_lifetime_name(&mut trait_lifetime_name, &generics.params); + let trait_lifetime = Lifetime::new(&trait_lifetime_name, Span::call_site()); + + Ok(Self { + crate_path, + orig_ident, + proj_ident, + proj_trait, + generics, + lifetime, + trait_lifetime, + unsafe_unpin, + pinned_drop, + }) + } + + /// Creates the generics of projected type. + fn proj_generics(&self) -> Generics { + let mut generics = self.generics.clone(); + utils::proj_generics(&mut generics, self.lifetime.clone()); + generics + } + + /// Creates the generics for the 'project_into' method. + fn project_into_generics(&self) -> Generics { + let mut generics = self.generics.clone(); + utils::proj_generics(&mut generics, self.trait_lifetime.clone()); + generics + } + + fn find_pin_attr(&self, attrs: &mut Vec) -> Result { + if let Some(pos) = attrs.position(PIN) { + let tokens = if self.unsafe_unpin.is_some() { + attrs.remove(pos).tokens + } else { + attrs[pos].tokens.clone() + }; + let _: Nothing = syn::parse2(tokens)?; + Ok(true) + } else { + Ok(false) + } + } + + /// Creates `Unpin` implementation for original type if `UnsafeUnpin` argument used. + fn make_unpin_impl(&self) -> TokenStream { + let unsafe_unpin = if let Some(unsafe_unpin) = self.unsafe_unpin { + unsafe_unpin + } else { + // To generate the correct `Unpin` implementation, + // we need to collect the types of the pinned fields. + // However, since proc-macro-attribute is applied before cfg, + // we cannot be collecting field types at this timing. + // So instead of generating the `Unpin` implementation here, + // we need to delegate automatic generation of the `Unpin` + // implementation to proc-macro-derive. + return TokenStream::new(); + }; + + let mut generics = self.generics.clone(); + let crate_path = &self.crate_path; + let orig_ident = &self.orig_ident; + + generics.make_where_clause().predicates.push( + syn::parse2(quote_spanned! { unsafe_unpin => + ::#crate_path::__private::Wrapper: ::#crate_path::UnsafeUnpin + }) + .unwrap(), + ); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #where_clause {} + } + } + + /// Creates `Drop` implementation for original type. + fn make_drop_impl(&self) -> TokenStream { + let orig_ident = &self.orig_ident; + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + + if let Some(pinned_drop) = self.pinned_drop { + let crate_path = &self.crate_path; + + let call = quote_spanned! { pinned_drop => + ::#crate_path::__private::UnsafePinnedDrop::pinned_drop(pinned_self) + }; + + quote! { + #[allow(single_use_lifetimes)] + impl #impl_generics ::core::ops::Drop for #orig_ident #ty_generics #where_clause { + fn drop(&mut self) { + // Safety - we're in 'drop', so we know that 'self' will + // never move again. + let pinned_self = unsafe { ::core::pin::Pin::new_unchecked(self) }; + // We call `pinned_drop` only once. Since `UnsafePinnedDrop::pinned_drop` + // is an unsafe function and a private API, it is never called again in safe + // code *unless the user uses a maliciously crafted macro*. + unsafe { + #call; + } + } + } + } + } else { + // If the user does not provide a pinned_drop impl, + // we need to ensure that they don't provide a `Drop` impl of their + // own. + // Based on https://github.com/upsuper/assert-impl/blob/f503255b292ab0ba8d085b657f4065403cfa46eb/src/lib.rs#L80-L87 + // + // We create a new identifier for each struct, so that the traits + // for different types do not conflcit with each other. + // + // Another approach would be to provide an empty Drop impl, + // which would conflict with a user-provided Drop impl. + // However, this would trigger the compiler's special handling + // of Drop types (e.g. fields cannot be moved out of a Drop type). + // This approach prevents the creation of needless Drop impls, + // giving users more flexibility. + let trait_ident = format_ident!("{}MustNotImplDrop", orig_ident); + + quote! { + // There are two possible cases: + // 1. The user type does not implement Drop. In this case, + // the first blanked impl will not apply to it. This code + // will compile, as there is only one impl of MustNotImplDrop for the user type + // 2. The user type does impl Drop. This will make the blanket impl applicable, + // which will then comflict 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 #trait_ident for T {} + #[allow(single_use_lifetimes)] + impl #impl_generics #trait_ident for #orig_ident #ty_generics #where_clause {} + } + } + } + + /// Creates a definition of the projection trait. + fn make_proj_trait(&self) -> TokenStream { + let Self { proj_ident, proj_trait, lifetime, .. } = self; + let proj_generics = self.proj_generics(); + let proj_ty_generics = proj_generics.split_for_impl().1; + + // Add trait lifetime to trait generics. + let mut trait_generics = self.generics.clone(); + utils::proj_generics(&mut trait_generics, self.trait_lifetime.clone()); + + let (trait_generics, trait_ty_generics, orig_where_clause) = + trait_generics.split_for_impl(); + + quote! { + trait #proj_trait #trait_generics { + fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #orig_where_clause; + fn project_into(self) -> #proj_ident #trait_ty_generics #orig_where_clause; + } + } + } + + /// Creates an implementation of the projection trait. + fn make_proj_impl( + &self, + project_body: &TokenStream, + project_into_body: &TokenStream, + ) -> TokenStream { + let Context { proj_ident, proj_trait, orig_ident, lifetime, trait_lifetime, .. } = &self; + let proj_generics = self.proj_generics(); + let project_into_generics = self.project_into_generics(); + + let proj_ty_generics = proj_generics.split_for_impl().1; + let (impl_generics, project_into_ty_generics, _) = project_into_generics.split_for_impl(); + let (_, ty_generics, where_clause) = self.generics.split_for_impl(); + + quote! { + impl #impl_generics #proj_trait #project_into_ty_generics + for ::core::pin::Pin<&#trait_lifetime mut #orig_ident #ty_generics> #where_clause + { + fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #where_clause { + unsafe { + #project_body + } + } + fn project_into(self) -> #proj_ident #project_into_ty_generics #where_clause { + unsafe { + #project_into_body + } + } + } + } + } +} + +fn ensure_not_packed(item: &ItemStruct) -> Result { + for meta in item.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { + if let Meta::List(l) = meta { + if l.path.is_ident("repr") { + for repr in &l.nested { + if let NestedMeta::Meta(Meta::Path(p)) = repr { + if p.is_ident("packed") { + return Err(error!( + p, + "#[pin_project] attribute may not be used on #[repr(packed)] types" + )); + } + } + } + } + } + } + + // 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: + // + // #[deny(safe_packed_borrows)] + // fn enforce_not_packed_for_MyStruct(val: MyStruct) { + // let _field1 = &val.field1; + // let _field2 = &val.field2; + // ... + // let _fieldn = &val.fieldn; + // } + // + // Taking a reference to a packed field is unsafe, amd appplying + // #[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 &item.fields { + Fields::Named(FieldsNamed { named, .. }) => { + for Field { attrs, ident, .. } in named { + let cfg = collect_cfg(attrs); + field_refs.push(quote! { + #(#cfg)* { &val.#ident; } + }); + } + } + Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { + for (index, _) in unnamed.iter().enumerate() { + let index = Index::from(index); + field_refs.push(quote! { + &val.#index; + }); + } + } + Fields::Unit => {} + } + + let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl(); + + let struct_name = &item.ident; + let method_name = format_ident!("__pin_project_assert_not_repr_packed_{}", item.ident); + let test_fn = quote! { + #[allow(single_use_lifetimes)] + #[allow(non_snake_case)] + #[deny(safe_packed_borrows)] + fn #method_name #impl_generics (val: #struct_name #ty_generics) #where_clause { + #(#field_refs)* + } + }; + Ok(test_fn) +} + +// Visit structs/enums +impl Context { + fn parse_struct(&mut self, item: &mut ItemStruct) -> Result { + super::validate_struct(&item.ident, &item.fields)?; + + let (proj_pat, proj_body, proj_fields) = match &mut item.fields { + Fields::Named(fields) => self.visit_named(fields)?, + Fields::Unnamed(fields) => self.visit_unnamed(fields, true)?, + Fields::Unit => unreachable!(), + }; + + let Context { orig_ident, proj_ident, .. } = &self; + let proj_generics = self.proj_generics(); + let where_clause = item.generics.split_for_impl().2; + + let mut proj_items = quote! { + #[allow(clippy::mut_mut)] // This lint warns `&mut &mut `. + #[allow(dead_code)] // This lint warns unused fields/variants. + struct #proj_ident #proj_generics #where_clause #proj_fields + }; + + let project_body = quote! { + let #orig_ident #proj_pat = self.as_mut().get_unchecked_mut(); + #proj_ident #proj_body + }; + let project_into_body = quote! { + let #orig_ident #proj_pat = self.get_unchecked_mut(); + #proj_ident #proj_body + }; + + proj_items.extend(self.make_proj_impl(&project_body, &project_into_body)); + + Ok(proj_items) + } + + fn parse_enum(&mut self, item: &mut ItemEnum) -> Result { + super::validate_enum(item.brace_token, &item.variants)?; + + let (proj_variants, proj_arms) = self.visit_variants(item)?; + + let proj_ident = &self.proj_ident; + let proj_generics = self.proj_generics(); + let where_clause = item.generics.split_for_impl().2; + + let mut proj_items = quote! { + #[allow(clippy::mut_mut)] // This lint warns `&mut &mut `. + #[allow(dead_code)] // This lint warns unused fields/variants. + enum #proj_ident #proj_generics #where_clause { + #(#proj_variants,)* + } + }; + + let project_body = quote! { + match self.as_mut().get_unchecked_mut() { + #(#proj_arms,)* + } + }; + let project_into_body = quote! { + match self.get_unchecked_mut() { + #(#proj_arms,)* + } + }; + + proj_items.extend(self.make_proj_impl(&project_body, &project_into_body)); + + Ok(proj_items) + } + + fn visit_variants( + &mut self, + item: &mut ItemEnum, + ) -> Result<(Vec, Vec)> { + let mut proj_variants = Vec::with_capacity(item.variants.len()); + let mut proj_arms = Vec::with_capacity(item.variants.len()); + for Variant { attrs, fields, ident, .. } in &mut item.variants { + let (proj_pat, proj_body, proj_fields) = match fields { + Fields::Named(fields) => self.visit_named(fields)?, + Fields::Unnamed(fields) => self.visit_unnamed(fields, false)?, + Fields::Unit => (TokenStream::new(), TokenStream::new(), TokenStream::new()), + }; + let cfg = collect_cfg(attrs); + let Self { orig_ident, proj_ident, .. } = &self; + proj_variants.push(quote! { + #(#cfg)* + #ident #proj_fields + }); + proj_arms.push(quote! { + #(#cfg)* + #orig_ident::#ident #proj_pat => { + #proj_ident::#ident #proj_body + } + }); + } + + Ok((proj_variants, proj_arms)) + } + + fn visit_named( + &mut self, + FieldsNamed { named: fields, .. }: &mut FieldsNamed, + ) -> Result<(TokenStream, TokenStream, TokenStream)> { + let mut proj_pat = Vec::with_capacity(fields.len()); + let mut proj_body = Vec::with_capacity(fields.len()); + let mut proj_fields = Vec::with_capacity(fields.len()); + for Field { attrs, ident, ty, .. } in fields { + let cfg = collect_cfg(attrs); + if self.find_pin_attr(attrs)? { + let lifetime = &self.lifetime; + proj_fields.push(quote! { + #(#cfg)* + #ident: ::core::pin::Pin<&#lifetime mut #ty> + }); + proj_body.push(quote! { + #(#cfg)* + #ident: ::core::pin::Pin::new_unchecked(#ident) + }); + } else { + let lifetime = &self.lifetime; + proj_fields.push(quote! { + #(#cfg)* + #ident: &#lifetime mut #ty + }); + proj_body.push(quote! { + #(#cfg)* + #ident: #ident + }); + } + proj_pat.push(quote! { + #(#cfg)* #ident + }); + } + + let proj_pat = quote!({ #(#proj_pat),* }); + let proj_body = quote!({ #(#proj_body),* }); + let proj_fields = quote!({ #(#proj_fields),* }); + Ok((proj_pat, proj_body, proj_fields)) + } + + fn visit_unnamed( + &mut self, + FieldsUnnamed { unnamed: fields, .. }: &mut FieldsUnnamed, + is_struct: bool, + ) -> Result<(TokenStream, TokenStream, TokenStream)> { + let mut proj_pat = Vec::with_capacity(fields.len()); + let mut proj_body = Vec::with_capacity(fields.len()); + let mut proj_fields = Vec::with_capacity(fields.len()); + for (i, Field { attrs, ty, .. }) in fields.iter_mut().enumerate() { + let id = format_ident!("_x{}", i); + let cfg = collect_cfg(attrs); + if !cfg.is_empty() { + return Err(error!( + cfg.first(), + "`cfg` attributes on the field of tuple {} are not supported", + if is_struct { "structs" } else { "variants" } + )); + } + if self.find_pin_attr(attrs)? { + let lifetime = &self.lifetime; + proj_fields.push(quote! { + ::core::pin::Pin<&#lifetime mut #ty> + }); + proj_body.push(quote! { + ::core::pin::Pin::new_unchecked(#id) + }); + } else { + let lifetime = &self.lifetime; + proj_fields.push(quote! { + &#lifetime mut #ty + }); + proj_body.push(quote! { + #id + }); + } + proj_pat.push(quote! { + #id + }); + } + + let proj_pat = quote!((#(#proj_pat),*)); + let proj_body = quote!((#(#proj_body),*)); + let proj_fields = + if is_struct { quote!((#(#proj_fields),*);) } else { quote!((#(#proj_fields),*)) }; + Ok((proj_pat, proj_body, proj_fields)) + } +} diff --git a/pin-project-internal/src/pin_project/derive.rs b/pin-project-internal/src/pin_project/derive.rs index 8c4c0441..31308726 100644 --- a/pin-project-internal/src/pin_project/derive.rs +++ b/pin-project-internal/src/pin_project/derive.rs @@ -11,17 +11,12 @@ pub(super) fn parse_derive(input: DeriveInput) -> Result { let mut cx = DeriveContext::new(ident, vis, generics); match &mut data { Data::Struct(data) => { - // We need to check this on proc-macro-derive because cfg may reduce - // the fields. On the other hand, if we check this only on - // proc-macro-derive, it may generate unhelpful error messages. - // So, we need to check this on both proc-macro-attribute - // and proc-macro-derive. - super::structs::validate(&cx.ident, &data.fields)?; - cx.fields(&mut data.fields) + super::validate_struct(&cx.ident, &data.fields)?; + cx.visit_fields(&mut data.fields) } Data::Enum(data) => { - super::enums::validate(data.brace_token, &data.variants)?; - cx.variants(data) + super::validate_enum(data.brace_token, &data.variants)?; + cx.visit_variants(data) } Data::Union(_) => unreachable!(), } @@ -48,13 +43,13 @@ impl DeriveContext { Self { ident, vis, generics, pinned_fields: Vec::new() } } - fn variants(&mut self, data: &mut DataEnum) { + fn visit_variants(&mut self, data: &mut DataEnum) { for Variant { fields, .. } in &mut data.variants { - self.fields(fields) + self.visit_fields(fields) } } - fn fields(&mut self, fields: &mut Fields) { + fn visit_fields(&mut self, fields: &mut Fields) { let fields = match fields { Fields::Unnamed(fields) => &mut fields.unnamed, Fields::Named(fields) => &mut fields.named, diff --git a/pin-project-internal/src/pin_project/enums.rs b/pin-project-internal/src/pin_project/enums.rs deleted file mode 100644 index 7a7c653d..00000000 --- a/pin-project-internal/src/pin_project/enums.rs +++ /dev/null @@ -1,174 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use syn::{token, Field, Fields, FieldsNamed, FieldsUnnamed, ItemEnum, Result, Variant}; - -use crate::utils::collect_cfg; - -use super::{Context, Variants}; - -pub(super) fn validate(brace_token: token::Brace, variants: &Variants) -> Result<()> { - if variants.is_empty() { - return Err(syn::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 Fields::Unit = v.fields { - Ok(has_field) - } else { - Ok(true) - } - })?; - if has_field { - Ok(()) - } else { - Err(error!( - variants, - "#[pin_project] attribute may not be used on enums that have no field" - )) - } -} - -pub(super) fn parse(cx: &mut Context, mut item: ItemEnum) -> Result { - validate(item.brace_token, &item.variants)?; - - let (proj_variants, proj_arms) = variants(cx, &mut item)?; - - let proj_ident = &cx.proj_ident; - let proj_generics = cx.proj_generics(); - let where_clause = item.generics.split_for_impl().2; - - let mut proj_items = quote! { - #[allow(clippy::mut_mut)] // This lint warns `&mut &mut `. - #[allow(dead_code)] // This lint warns unused fields/variants. - enum #proj_ident #proj_generics #where_clause { #(#proj_variants,)* } - }; - - let project_body = quote! { - match self.as_mut().get_unchecked_mut() { - #(#proj_arms,)* - } - }; - let project_into_body = quote! { - match self.get_unchecked_mut() { - #(#proj_arms,)* - } - }; - - proj_items.extend(cx.make_proj_impl(&project_body, &project_into_body)); - - let mut item = item.into_token_stream(); - item.extend(proj_items); - Ok(item) -} - -fn variants(cx: &mut Context, item: &mut ItemEnum) -> Result<(Vec, Vec)> { - let mut proj_variants = Vec::with_capacity(item.variants.len()); - let mut proj_arms = Vec::with_capacity(item.variants.len()); - for Variant { attrs, fields, ident, .. } in &mut item.variants { - let (proj_pat, proj_body, proj_fields) = match fields { - Fields::Named(fields) => named(cx, fields)?, - Fields::Unnamed(fields) => unnamed(cx, fields, false)?, - Fields::Unit => (TokenStream::new(), TokenStream::new(), TokenStream::new()), - }; - let cfg = collect_cfg(attrs); - let Context { orig_ident, proj_ident, .. } = &cx; - proj_variants.push(quote! { - #(#cfg)* #ident #proj_fields - }); - proj_arms.push(quote! { - #(#cfg)* #orig_ident::#ident #proj_pat => { - #proj_ident::#ident #proj_body - } - }); - } - - Ok((proj_variants, proj_arms)) -} - -pub(super) fn named( - cx: &mut Context, - FieldsNamed { named: fields, .. }: &mut FieldsNamed, -) -> Result<(TokenStream, TokenStream, TokenStream)> { - let mut proj_pat = Vec::with_capacity(fields.len()); - let mut proj_body = Vec::with_capacity(fields.len()); - let mut proj_fields = Vec::with_capacity(fields.len()); - for Field { attrs, ident, ty, .. } in fields { - let cfg = collect_cfg(attrs); - if cx.find_pin_attr(attrs)? { - let lifetime = &cx.lifetime; - proj_fields.push(quote! { - #(#cfg)* #ident: ::core::pin::Pin<&#lifetime mut #ty> - }); - proj_body.push(quote! { - #(#cfg)* #ident: ::core::pin::Pin::new_unchecked(#ident) - }); - } else { - let lifetime = &cx.lifetime; - proj_fields.push(quote! { - #(#cfg)* #ident: &#lifetime mut #ty - }); - proj_body.push(quote! { - #(#cfg)* #ident: #ident - }); - } - proj_pat.push(quote! { - #(#cfg)* #ident - }); - } - - let proj_pat = quote!({ #(#proj_pat),* }); - let proj_body = quote!({ #(#proj_body),* }); - let proj_fields = quote!({ #(#proj_fields),* }); - Ok((proj_pat, proj_body, proj_fields)) -} - -pub(super) fn unnamed( - cx: &mut Context, - FieldsUnnamed { unnamed: fields, .. }: &mut FieldsUnnamed, - is_struct: bool, -) -> Result<(TokenStream, TokenStream, TokenStream)> { - let mut proj_pat = Vec::with_capacity(fields.len()); - let mut proj_body = Vec::with_capacity(fields.len()); - let mut proj_fields = Vec::with_capacity(fields.len()); - for (i, Field { attrs, ty, .. }) in fields.iter_mut().enumerate() { - let id = format_ident!("_x{}", i); - let cfg = collect_cfg(attrs); - if !cfg.is_empty() { - return Err(error!( - cfg.first(), - "`cfg` attributes on the field of tuple {} are not supported", - if is_struct { "structs" } else { "variants" } - )); - } - if cx.find_pin_attr(attrs)? { - let lifetime = &cx.lifetime; - proj_fields.push(quote! { - ::core::pin::Pin<&#lifetime mut #ty> - }); - proj_body.push(quote! { - ::core::pin::Pin::new_unchecked(#id) - }); - } else { - let lifetime = &cx.lifetime; - proj_fields.push(quote! { - &#lifetime mut #ty - }); - proj_body.push(quote! { - #id - }); - } - proj_pat.push(quote! { - #id - }); - } - - let proj_pat = quote!((#(#proj_pat),*)); - let proj_body = quote!((#(#proj_body),*)); - let proj_fields = - if is_struct { quote!((#(#proj_fields),*);) } else { quote!((#(#proj_fields),*)) }; - Ok((proj_pat, proj_body, proj_fields)) -} diff --git a/pin-project-internal/src/pin_project/mod.rs b/pin-project-internal/src/pin_project/mod.rs index 50e37c3e..ccba8c3d 100644 --- a/pin-project-internal/src/pin_project/mod.rs +++ b/pin-project-internal/src/pin_project/mod.rs @@ -1,20 +1,8 @@ -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, quote_spanned}; -use syn::{ - parse::{Nothing, Parse, ParseStream}, - punctuated::Punctuated, - token::Comma, - *, -}; - -use crate::utils::{ - self, collect_cfg, crate_path, proj_ident, proj_lifetime_name, proj_trait_ident, VecExt, - DEFAULT_LIFETIME_NAME, TRAIT_LIFETIME_NAME, -}; +use proc_macro2::TokenStream; +use syn::{punctuated::Punctuated, *}; +mod attribute; mod derive; -mod enums; -mod structs; /// The annotation for pinned type. const PIN: &str = "pin"; @@ -22,411 +10,56 @@ const PIN: &str = "pin"; type Variants = Punctuated; pub(crate) fn attribute(args: TokenStream, input: Item) -> TokenStream { - parse_attribute(args, input).unwrap_or_else(|e| e.to_compile_error()) + attribute::parse_attribute(args, input).unwrap_or_else(|e| e.to_compile_error()) } pub(crate) fn derive(input: DeriveInput) -> TokenStream { derive::parse_derive(input).unwrap_or_else(|e| e.to_compile_error()) } -#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/56750 -struct Args { - pinned_drop: Option, - unsafe_unpin: Option, -} - -impl Parse for Args { - fn parse(input: ParseStream<'_>) -> Result { - let mut pinned_drop = None; - let mut unsafe_unpin = None; - while !input.is_empty() { - let arg = input.parse::()?; - match &*arg.to_string() { - "PinnedDrop" => { - if pinned_drop.is_some() { - return Err(error!(arg, "duplicate `PinnedDrop` argument")); - } - pinned_drop = Some(arg.span()); - } - "UnsafeUnpin" => { - if unsafe_unpin.is_some() { - return Err(error!(arg, "duplicate `UnsafeUnpin` argument")); - } - unsafe_unpin = Some(arg.span()); - } - _ => { - return Err(error!( - arg, - "an invalid argument was passed to #[pin_project] attribute" - )); - } - } - - if !input.is_empty() { - let _: Comma = input.parse()?; - } - } - Ok(Self { pinned_drop, unsafe_unpin }) - } -} - -struct Context { - crate_path: Ident, - - /// Name of the original type. - orig_ident: Ident, - - /// Name of the projected type. - proj_ident: Ident, - - /// Name of the trait generated to provide a 'project' method. - proj_trait: Ident, - - /// Generics of the original type. - generics: Generics, - - /// Lifetime on the generated projected type. - lifetime: Lifetime, - - /// Lifetime on the generated projection trait. - trait_lifetime: Lifetime, - - unsafe_unpin: Option, - - pinned_drop: Option, -} - -impl Context { - fn new( - args: TokenStream, - attrs: &mut Vec, - orig_ident: Ident, - generics: Generics, - ) -> Result { - let Args { pinned_drop, unsafe_unpin } = syn::parse2(args)?; - - let crate_path = crate_path(); - if unsafe_unpin.is_none() { - attrs.push( - syn::parse_quote!(#[derive(#crate_path::__private::__PinProjectAutoImplUnpin)]), - ); +// We need to check this in both proc-macro-attribute and proc-macro-derive for the following reasons: +// * `cfg` may reduce fields after being checked by proc-macro-attribute. +// * If we check this only on proc-macro-derive, it may generate unhelpful error messages. +fn validate_struct(ident: &Ident, fields: &Fields) -> Result<()> { + match fields { + Fields::Named(FieldsNamed { named: f, .. }) + | Fields::Unnamed(FieldsUnnamed { unnamed: f, .. }) + if f.is_empty() => + { + Err(error!( + fields, + "#[pin_project] attribute may not be used on structs with zero fields" + )) } - - let proj_ident = proj_ident(&orig_ident); - let proj_trait = proj_trait_ident(&orig_ident); - - let mut lifetime_name = String::from(DEFAULT_LIFETIME_NAME); - proj_lifetime_name(&mut lifetime_name, &generics.params); - let lifetime = Lifetime::new(&lifetime_name, Span::call_site()); - - let mut trait_lifetime_name = String::from(TRAIT_LIFETIME_NAME); - proj_lifetime_name(&mut trait_lifetime_name, &generics.params); - let trait_lifetime = Lifetime::new(&trait_lifetime_name, Span::call_site()); - - Ok(Self { - crate_path, - orig_ident, - proj_ident, - proj_trait, - generics, - lifetime, - trait_lifetime, - unsafe_unpin, - pinned_drop, - }) - } - - /// Creates the generics of projected type. - fn proj_generics(&self) -> Generics { - let mut generics = self.generics.clone(); - utils::proj_generics(&mut generics, self.lifetime.clone()); - generics - } - - /// Creates the generics for the 'project_into' method. - fn project_into_generics(&self) -> Generics { - let mut generics = self.generics.clone(); - utils::proj_generics(&mut generics, self.trait_lifetime.clone()); - generics - } - - fn find_pin_attr(&self, attrs: &mut Vec) -> Result { - if let Some(pos) = attrs.position(PIN) { - let tokens = if self.unsafe_unpin.is_some() { - attrs.remove(pos).tokens - } else { - attrs[pos].tokens.clone() - }; - let _: Nothing = syn::parse2(tokens)?; - Ok(true) - } else { - Ok(false) - } - } - - /// Creates `Unpin` implementation for original type if `UnsafeUnpin` argument used. - fn make_unpin_impl(&mut self) -> TokenStream { - let unsafe_unpin = if let Some(unsafe_unpin) = self.unsafe_unpin { - unsafe_unpin - } else { - // To generate the correct `Unpin` implementation, - // we need to collect the types of the pinned fields. - // However, since proc-macro-attribute is applied before cfg, - // we cannot be collecting field types at this timing. - // So instead of generating the `Unpin` implementation here, - // we need to delegate automatic generation of the `Unpin` - // implementation to proc-macro-derive. - return TokenStream::new(); - }; - - let mut where_clause = self.generics.make_where_clause().clone(); - let crate_path = &self.crate_path; - let orig_ident = &self.orig_ident; - let (impl_generics, ty_generics, _) = self.generics.split_for_impl(); - - where_clause.predicates.push( - syn::parse2(quote_spanned! { unsafe_unpin => - ::#crate_path::__private::Wrapper: ::#crate_path::UnsafeUnpin - }) - .unwrap(), - ); - - quote! { - impl #impl_generics ::core::marker::Unpin for #orig_ident #ty_generics #where_clause {} - } - } - - /// Creates `Drop` implementation for original type. - fn make_drop_impl(&self) -> TokenStream { - let orig_ident = &self.orig_ident; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - - if let Some(pinned_drop) = self.pinned_drop { - let crate_path = &self.crate_path; - let call = quote_spanned! { pinned_drop => - ::#crate_path::__private::UnsafePinnedDrop::pinned_drop(pinned_self) - }; - - quote! { - #[allow(single_use_lifetimes)] - impl #impl_generics ::core::ops::Drop for #orig_ident #ty_generics #where_clause { - fn drop(&mut self) { - // Safety - we're in 'drop', so we know that 'self' will - // never move again. - let pinned_self = unsafe { ::core::pin::Pin::new_unchecked(self) }; - // We call `pinned_drop` only once. Since `UnsafePinnedDrop::pinned_drop` - // is an unsafe function and a private API, it is never called again in safe - // code *unless the user uses a maliciously crafted macro*. - unsafe { - #call; - } - } - } - } - } else { - // If the user does not provide a pinned_drop impl, - // we need to ensure that they don't provide a `Drop` impl of their - // own. - // Based on https://github.com/upsuper/assert-impl/blob/f503255b292ab0ba8d085b657f4065403cfa46eb/src/lib.rs#L80-L87 - // - // We create a new identifier for each struct, so that the traits - // for different types do not conflcit with each other. - // - // Another approach would be to provide an empty Drop impl, - // which would conflict with a user-provided Drop impl. - // However, this would trigger the compiler's special handling - // of Drop types (e.g. fields cannot be moved out of a Drop type). - // This approach prevents the creation of needless Drop impls, - // giving users more flexibility. - let trait_ident = format_ident!("{}MustNotImplDrop", orig_ident); - quote! { - // There are two possible cases: - // 1. The user type does not implement Drop. In this case, - // the first blanked impl will not apply to it. This code - // will compile, as there is only one impl of MustNotImplDrop for the user type - // 2. The user type does impl Drop. This will make the blanket impl applicable, - // which will then comflict 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 #trait_ident for T {} - #[allow(single_use_lifetimes)] - impl #impl_generics #trait_ident for #orig_ident #ty_generics #where_clause {} - } - } - } - - /// Creates a definition of the projection trait. - fn make_proj_trait(&self) -> TokenStream { - let Self { proj_ident, proj_trait, lifetime, .. } = self; - let proj_generics = self.proj_generics(); - let proj_ty_generics = proj_generics.split_for_impl().1; - - // Add trait lifetime to trait generics. - let mut trait_generics = self.generics.clone(); - utils::proj_generics(&mut trait_generics, self.trait_lifetime.clone()); - - let (trait_generics, trait_ty_generics, orig_where_clause) = - trait_generics.split_for_impl(); - - quote! { - trait #proj_trait #trait_generics { - fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #orig_where_clause; - fn project_into(self) -> #proj_ident #trait_ty_generics #orig_where_clause; - } - } - } - - /// Creates an implementation of the projection trait. - fn make_proj_impl( - &self, - project_body: &TokenStream, - project_into_body: &TokenStream, - ) -> TokenStream { - let Context { proj_ident, proj_trait, orig_ident, lifetime, trait_lifetime, .. } = &self; - let proj_generics = self.proj_generics(); - - let project_into_generics = self.project_into_generics(); - - let proj_ty_generics = proj_generics.split_for_impl().1; - let (impl_generics, project_into_ty_generics, _) = project_into_generics.split_for_impl(); - let (_, ty_generics, where_clause) = self.generics.split_for_impl(); - - quote! { - impl #impl_generics #proj_trait #project_into_ty_generics - for ::core::pin::Pin<&#trait_lifetime mut #orig_ident #ty_generics> #where_clause - { - fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #where_clause { - unsafe { - #project_body - } - } - fn project_into(self) -> #proj_ident #project_into_ty_generics #where_clause { - unsafe { - #project_into_body - } - } - } + Fields::Unit => { + Err(error!(ident, "#[pin_project] attribute may not be used on structs with units")) } + _ => Ok(()), } } -fn parse_attribute(args: TokenStream, input: Item) -> Result { - match input { - Item::Struct(mut item) => { - let mut cx = - Context::new(args, &mut item.attrs, item.ident.clone(), item.generics.clone())?; - - let packed_check = ensure_not_packed(&item)?; - let mut res = structs::parse(&mut cx, item)?; - res.extend(cx.make_proj_trait()); - res.extend(cx.make_unpin_impl()); - res.extend(cx.make_drop_impl()); - res.extend(packed_check); - Ok(res) - } - Item::Enum(mut item) => { - let mut cx = - Context::new(args, &mut item.attrs, item.ident.clone(), item.generics.clone())?; - - // We don't need to check for '#[repr(packed)]', - // since it does not apply to enums. - let mut res = enums::parse(&mut cx, item)?; - res.extend(cx.make_proj_trait()); - res.extend(cx.make_unpin_impl()); - res.extend(cx.make_drop_impl()); - Ok(res) - } - item => Err(error!(item, "#[pin_project] attribute may only be used on structs or enums")), +fn validate_enum(brace_token: token::Brace, variants: &Variants) -> Result<()> { + if variants.is_empty() { + return Err(syn::Error::new( + brace_token.span, + "#[pin_project] attribute may not be used on enums without variants", + )); } -} - -fn ensure_not_packed(item: &ItemStruct) -> Result { - for meta in item.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { - if let Meta::List(l) = meta { - if l.path.is_ident("repr") { - for repr in &l.nested { - if let NestedMeta::Meta(Meta::Path(p)) = repr { - if p.is_ident("packed") { - return Err(error!( - p, - "#[pin_project] attribute may not be used on #[repr(packed)] types" - )); - } - } - } - } - } - } - - // 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: - // - // #[deny(safe_packed_borrows)] - // fn enforce_not_packed_for_MyStruct(val: MyStruct) { - // let _field1 = &val.field1; - // let _field2 = &val.field2; - // ... - // let _fieldn = &val.fieldn; - // } - // - // Taking a reference to a packed field is unsafe, amd appplying - // #[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 &item.fields { - Fields::Named(FieldsNamed { named, .. }) => { - for Field { attrs, ident, .. } in named { - let cfg = collect_cfg(attrs); - field_refs.push(quote! { - #(#cfg)* { &val.#ident; } - }); - } - } - Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { - for (index, _) in unnamed.iter().enumerate() { - let index = Index::from(index); - field_refs.push(quote! { - &val.#index; - }); - } + 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 Fields::Unit = v.fields { + Ok(has_field) + } else { + Ok(true) } - Fields::Unit => {} + })?; + if has_field { + Ok(()) + } else { + Err(error!( + variants, + "#[pin_project] attribute may not be used on enums that have no field" + )) } - - let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl(); - - let struct_name = &item.ident; - let method_name = format_ident!("__pin_project_assert_not_repr_packed_{}", item.ident); - let test_fn = quote! { - #[allow(single_use_lifetimes)] - #[allow(non_snake_case)] - #[deny(safe_packed_borrows)] - fn #method_name #impl_generics (val: #struct_name #ty_generics) #where_clause { - #(#field_refs)* - } - }; - Ok(test_fn) } diff --git a/pin-project-internal/src/pin_project/structs.rs b/pin-project-internal/src/pin_project/structs.rs deleted file mode 100644 index 4a6c8e77..00000000 --- a/pin-project-internal/src/pin_project/structs.rs +++ /dev/null @@ -1,58 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::{Fields, FieldsNamed, FieldsUnnamed, Ident, ItemStruct, Result}; - -use super::Context; - -pub(super) fn validate(ident: &Ident, fields: &Fields) -> Result<()> { - match fields { - Fields::Named(FieldsNamed { named: f, .. }) - | Fields::Unnamed(FieldsUnnamed { unnamed: f, .. }) - if f.is_empty() => - { - Err(error!( - fields, - "#[pin_project] attribute may not be used on structs with zero fields" - )) - } - Fields::Unit => { - Err(error!(ident, "#[pin_project] attribute may not be used on structs with units")) - } - _ => Ok(()), - } -} - -pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result { - validate(&item.ident, &item.fields)?; - - let (proj_pat, proj_body, proj_fields) = match &mut item.fields { - Fields::Named(fields) => super::enums::named(cx, fields)?, - Fields::Unnamed(fields) => super::enums::unnamed(cx, fields, true)?, - Fields::Unit => unreachable!(), - }; - - let Context { orig_ident, proj_ident, .. } = &cx; - let proj_generics = cx.proj_generics(); - let where_clause = item.generics.split_for_impl().2; - - let mut proj_items = quote! { - #[allow(clippy::mut_mut)] // This lint warns `&mut &mut `. - #[allow(dead_code)] // This lint warns unused fields/variants. - struct #proj_ident #proj_generics #where_clause #proj_fields - }; - - let project_body = quote! { - let #orig_ident #proj_pat = self.as_mut().get_unchecked_mut(); - #proj_ident #proj_body - }; - let project_into_body = quote! { - let #orig_ident #proj_pat = self.get_unchecked_mut(); - #proj_ident #proj_body - }; - - proj_items.extend(cx.make_proj_impl(&project_body, &project_into_body)); - - let mut item = item.into_token_stream(); - item.extend(proj_items); - Ok(item) -} From fb8ebf411a08672abf71c4c7e8ee46b65e6e47ca Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 8 Sep 2019 00:56:22 +0900 Subject: [PATCH 5/6] Add tests --- tests/ui/cfg/auxiliary/sneaky_macro.rs | 5 ++++ tests/ui/cfg/packed_sneaky.rs | 31 +++++++++++++++++++++++ tests/ui/cfg/packed_sneaky.stderr | 17 +++++++++++++ tests/ui/cfg/proper_unpin.rs | 31 +++++++++++++++++++++++ tests/ui/cfg/proper_unpin.stderr | 17 +++++++++++++ tests/ui/cfg/unsupported.rs | 2 +- tests/ui/pin_project/packed_sneaky.rs | 2 +- tests/ui/project/type-mismatch.rs | 24 +++++++++--------- tests/ui/project/type-mismatch.stderr | 6 ++--- tests/ui/unsafe_unpin/proper_unpin.rs | 21 +++++++++++++++ tests/ui/unsafe_unpin/proper_unpin.stderr | 18 +++++++++++++ 11 files changed, 157 insertions(+), 17 deletions(-) create mode 100644 tests/ui/cfg/packed_sneaky.rs create mode 100644 tests/ui/cfg/packed_sneaky.stderr create mode 100644 tests/ui/cfg/proper_unpin.rs create mode 100644 tests/ui/cfg/proper_unpin.stderr create mode 100644 tests/ui/unsafe_unpin/proper_unpin.rs create mode 100644 tests/ui/unsafe_unpin/proper_unpin.stderr diff --git a/tests/ui/cfg/auxiliary/sneaky_macro.rs b/tests/ui/cfg/auxiliary/sneaky_macro.rs index ccf56cc6..764906ba 100644 --- a/tests/ui/cfg/auxiliary/sneaky_macro.rs +++ b/tests/ui/cfg/auxiliary/sneaky_macro.rs @@ -60,3 +60,8 @@ pub fn add_pinned_field(_: TokenStream, input: TokenStream) -> TokenStream { unreachable!() } } + +#[proc_macro_attribute] +pub fn hidden_repr(attr: TokenStream, item: TokenStream) -> TokenStream { + format!("#[repr({})] {}", attr, item).parse().unwrap() +} diff --git a/tests/ui/cfg/packed_sneaky.rs b/tests/ui/cfg/packed_sneaky.rs new file mode 100644 index 00000000..49645515 --- /dev/null +++ b/tests/ui/cfg/packed_sneaky.rs @@ -0,0 +1,31 @@ +// compile-fail +// aux-build:sneaky_macro.rs + +#[macro_use] +extern crate sneaky_macro; + +use pin_project::pin_project; + +#[pin_project] // Pass +#[hidden_repr(packed)] +struct Foo { + #[cfg(any())] + #[pin] + field: u32, + #[cfg(not(any()))] + #[pin] + field: u8, +} + +#[pin_project] //~ ERROR borrow of packed field is unsafe and requires unsafe function or block +#[hidden_repr(packed)] +struct Bar { + #[cfg(not(any()))] + #[pin] + field: u32, + #[cfg(any())] + #[pin] + field: u8, +} + +fn main() {} diff --git a/tests/ui/cfg/packed_sneaky.stderr b/tests/ui/cfg/packed_sneaky.stderr new file mode 100644 index 00000000..da598b61 --- /dev/null +++ b/tests/ui/cfg/packed_sneaky.stderr @@ -0,0 +1,17 @@ +error: borrow of packed field is unsafe and requires unsafe function or block (error E0133) + --> $DIR/packed_sneaky.rs:20:1 + | +20 | #[pin_project] //~ ERROR borrow of packed field is unsafe and requires unsafe function or block + | ^^^^^^^^^^^^^^ + | +note: lint level defined here + --> $DIR/packed_sneaky.rs:20:1 + | +20 | #[pin_project] //~ ERROR borrow of packed field is unsafe and requires unsafe function or block + | ^^^^^^^^^^^^^^ + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #46043 + = note: fields of packed structs might be misaligned: dereferencing a misaligned pointer or even just creating a misaligned reference is undefined behavior + +error: aborting due to previous error + diff --git a/tests/ui/cfg/proper_unpin.rs b/tests/ui/cfg/proper_unpin.rs new file mode 100644 index 00000000..d0e8e8fd --- /dev/null +++ b/tests/ui/cfg/proper_unpin.rs @@ -0,0 +1,31 @@ +// compile-fail + +use pin_project::pin_project; +use std::{marker::PhantomPinned, pin::Pin}; + +#[pin_project] +struct Foo { + #[cfg(any())] + #[pin] + inner: T, + #[cfg(not(any()))] + inner: T, +} + +#[pin_project] +struct Bar { + #[cfg(any())] + inner: T, + #[cfg(not(any()))] + #[pin] + inner: T, +} + +fn is_unpin() {} + +fn baz() { + is_unpin::>(); // Pass + is_unpin::>(); //~ ERROR E0277 +} + +fn main() {} diff --git a/tests/ui/cfg/proper_unpin.stderr b/tests/ui/cfg/proper_unpin.stderr new file mode 100644 index 00000000..4f1acef0 --- /dev/null +++ b/tests/ui/cfg/proper_unpin.stderr @@ -0,0 +1,17 @@ +error[E0277]: the trait bound `std::marker::PhantomPinned: std::marker::Unpin` is not satisfied in `UnpinStructBar` + --> $DIR/proper_unpin.rs:28:5 + | +24 | fn is_unpin() {} + | ----------------------- required by `is_unpin` +... +28 | is_unpin::>(); //~ ERROR E0277 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within `UnpinStructBar`, the trait `std::marker::Unpin` is not implemented for `std::marker::PhantomPinned` + | + = help: the following implementations were found: + + = note: required because it appears within the type `UnpinStructBar` + = note: required because of the requirements on the impl of `std::marker::Unpin` for `Bar` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/cfg/unsupported.rs b/tests/ui/cfg/unsupported.rs index 3eaceaee..db6fda72 100644 --- a/tests/ui/cfg/unsupported.rs +++ b/tests/ui/cfg/unsupported.rs @@ -3,7 +3,7 @@ use pin_project::pin_project; #[pin_project] -struct Struct1 { +struct Struct { //~^ ERROR may not be used on structs with zero fields #[cfg(any())] #[pin] diff --git a/tests/ui/pin_project/packed_sneaky.rs b/tests/ui/pin_project/packed_sneaky.rs index 37c24bf7..dcf68a4a 100644 --- a/tests/ui/pin_project/packed_sneaky.rs +++ b/tests/ui/pin_project/packed_sneaky.rs @@ -8,7 +8,7 @@ use pin_project::pin_project; #[pin_project] //~ ERROR borrow of packed field is unsafe and requires unsafe function or block #[hidden_repr(packed)] -struct Bar { +struct Foo { #[pin] field: u32, } diff --git a/tests/ui/project/type-mismatch.rs b/tests/ui/project/type-mismatch.rs index 5f418a8d..54938125 100644 --- a/tests/ui/project/type-mismatch.rs +++ b/tests/ui/project/type-mismatch.rs @@ -10,7 +10,7 @@ fn span() { // enum #[pin_project] - enum Baz { + enum Foo { Variant1(#[pin] A, B), Variant2 { #[pin] @@ -20,20 +20,20 @@ fn span() { None, } - let mut baz = Baz::Variant1(1, 2); + let mut foo = Foo::Variant1(1, 2); - let mut baz = Pin::new(&mut baz).project(); + let mut foo = Pin::new(&mut foo).project(); #[project] - match &mut baz { - Baz::Variant1(x, y) => { + match &mut foo { + Foo::Variant1(x, y) => { let x: &mut Pin<&mut i32> = x; assert_eq!(**x, 1); let y: &mut &mut i32 = y; assert_eq!(**y, 2); } - Baz::Variant2 { field1, field2 } => { + Foo::Variant2 { field1, field2 } => { let _x: &mut Pin<&mut i32> = field1; let _y: &mut &mut i32 = field2; } @@ -47,7 +47,7 @@ fn loses_span() { // enum #[pin_project] - enum Baz { + enum Foo { Variant1(#[pin] A, B), Variant2 { #[pin] @@ -57,20 +57,20 @@ fn loses_span() { None, } - let mut baz = Baz::Variant1(1, 2); + let mut foo = Foo::Variant1(1, 2); - let mut baz = Pin::new(&mut baz).project(); + let mut foo = Pin::new(&mut foo).project(); #[project] //~ ERROR mismatched types - match &mut baz { - Baz::Variant1(x, y) => { + match &mut foo { + Foo::Variant1(x, y) => { let x: &mut Pin<&mut i32> = x; assert_eq!(**x, 1); let y: &mut &mut i32 = y; assert_eq!(**y, 2); } - Baz::Variant2 { field1, field2 } => { + Foo::Variant2 { field1, field2 } => { let _x: &mut Pin<&mut i32> = field1; let _y: &mut &mut i32 = field2; } diff --git a/tests/ui/project/type-mismatch.stderr b/tests/ui/project/type-mismatch.stderr index 04efebff..de0f90ea 100644 --- a/tests/ui/project/type-mismatch.stderr +++ b/tests/ui/project/type-mismatch.stderr @@ -2,14 +2,14 @@ error[E0308]: mismatched types --> $DIR/type-mismatch.rs:40:9 | 40 | None => {} //~ ERROR mismatched types - | ^^^^ expected enum `span::__BazProjection`, found enum `std::option::Option` + | ^^^^ expected enum `span::__FooProjection`, found enum `std::option::Option` | - = note: expected type `span::__BazProjection<'_, {integer}, {integer}, _, _>` + = note: expected type `span::__FooProjection<'_, {integer}, {integer}, _, _>` found type `std::option::Option<_>` error[E0308]: mismatched types | - = note: expected type `loses_span::__BazProjection<'_, {integer}, {integer}, _, _>` + = note: expected type `loses_span::__FooProjection<'_, {integer}, {integer}, _, _>` found type `std::option::Option<_>` error: aborting due to 2 previous errors diff --git a/tests/ui/unsafe_unpin/proper_unpin.rs b/tests/ui/unsafe_unpin/proper_unpin.rs new file mode 100644 index 00000000..41d9d664 --- /dev/null +++ b/tests/ui/unsafe_unpin/proper_unpin.rs @@ -0,0 +1,21 @@ +// compile-fail + +use pin_project::{pin_project, UnsafeUnpin}; +use std::{marker::PhantomPinned, pin::Pin}; + +#[pin_project(UnsafeUnpin)] +struct Foo { + #[pin] + inner: T, + other: U, +} + +unsafe impl UnsafeUnpin for Foo {} + +fn is_unpin() {} + +fn bar() { + is_unpin::>(); //~ ERROR E0277 +} + +fn main() {} diff --git a/tests/ui/unsafe_unpin/proper_unpin.stderr b/tests/ui/unsafe_unpin/proper_unpin.stderr new file mode 100644 index 00000000..c93ee766 --- /dev/null +++ b/tests/ui/unsafe_unpin/proper_unpin.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `std::marker::PhantomPinned: std::marker::Unpin` is not satisfied + --> $DIR/proper_unpin.rs:18:5 + | +15 | fn is_unpin() {} + | ----------------------- required by `is_unpin` +... +18 | is_unpin::>(); //~ ERROR E0277 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::marker::Unpin` is not implemented for `std::marker::PhantomPinned` + | + = help: the following implementations were found: + + = note: required because of the requirements on the impl of `pin_project::UnsafeUnpin` for `Foo` + = note: required because of the requirements on the impl of `pin_project::UnsafeUnpin` for `pin_project::__private::Wrapper>` + = note: required because of the requirements on the impl of `std::marker::Unpin` for `Foo` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. From 6a0d4191e3e0891f203f55b520d371c5d4434a3d Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 8 Sep 2019 01:26:57 +0900 Subject: [PATCH 6/6] Update examples --- examples/struct-default.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/struct-default.rs b/examples/struct-default.rs index be35d56a..debc8ecb 100644 --- a/examples/struct-default.rs +++ b/examples/struct-default.rs @@ -33,19 +33,19 @@ impl<'_outer_pin, T, U> __StructProjectionTrait<'_outer_pin, T, U> { fn project<'_pin>(&'_pin mut self) -> __StructProjection<'_pin, T, U> { unsafe { - let this = self.as_mut().get_unchecked_mut(); + let Struct { pinned, unpinned } = self.as_mut().get_unchecked_mut(); __StructProjection { - pinned: ::core::pin::Pin::new_unchecked(&mut this.pinned), - unpinned: &mut this.unpinned, + pinned: ::core::pin::Pin::new_unchecked(pinned), + unpinned: unpinned, } } } fn project_into(self) -> __StructProjection<'_outer_pin, T, U> { unsafe { - let this = self.get_unchecked_mut(); + let Struct { pinned, unpinned } = self.get_unchecked_mut(); __StructProjection { - pinned: ::core::pin::Pin::new_unchecked(&mut this.pinned), - unpinned: &mut this.unpinned, + pinned: ::core::pin::Pin::new_unchecked(pinned), + unpinned: unpinned, } } } @@ -91,8 +91,12 @@ impl StructMustNotImplDrop for Struct {} #[allow(non_snake_case)] #[deny(safe_packed_borrows)] fn __pin_project_assert_not_repr_packed_Struct(val: Struct) { - &val.pinned; - &val.unpinned; + { + &val.pinned; + } + { + &val.unpinned; + } } fn main() {}