From 9e3dc88e3218d17785a3f9eb810b447a3f96a42d Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sat, 11 Apr 2020 01:25:55 -0400 Subject: [PATCH] Add `OriginalType: 'pin` bound to projected type Fixes #185 Usually, Rust will automatically add lifetime bounds of the form `T: 'lifetime` when required (e.g. when a struct contains `&'a T`). However, the compiler will not normalize associated types before performing this analysis, which can require users to manually add `T: 'lifetime` bounds themselves. When this happens with a generated projection struct, the user has no ability to add such a bound, since they cannot name the 'pin lifetime. This PR adds a bound of the form `OriginalType: 'pin` to the `where` clause of the generated projection types. This ensures that any type parameters which need to outlive 'pin will do so, without unnecessarily constraining other type parameters. The generated bound does not need to be added to the `where` clause of any impls for the generated projection types. For more details, see https://github.com/rust-lang/rfcs/blob/d89de6a5857597cca9f6e1c7ebb70476f31a9159/text/2093-infer-outlives.md#background-outlives-requirements-today --- .../src/pin_project/derive.rs | 33 ++++++++++++------- pin-project-internal/src/utils.rs | 23 +++++++++++++ tests/no_infer_outlives.rs | 19 +++++++++++ 3 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 tests/no_infer_outlives.rs diff --git a/pin-project-internal/src/pin_project/derive.rs b/pin-project-internal/src/pin_project/derive.rs index 1ed1c569..30318f04 100644 --- a/pin-project-internal/src/pin_project/derive.rs +++ b/pin-project-internal/src/pin_project/derive.rs @@ -196,6 +196,9 @@ struct ProjectedType { lifetime: Lifetime, /// Generics of the projected type. generics: Generics, + /// `where` clause of the projected type. This has an additional + /// bound generated by `insert_lifetime_and_bound` + where_clause: WhereClause, } struct Context { @@ -230,7 +233,16 @@ impl Context { let lifetime = Lifetime::new(&lifetime_name, Span::call_site()); let mut proj_generics = generics.clone(); - insert_lifetime(&mut proj_generics, lifetime.clone()); + let ty_generics = generics.split_for_impl().1; + let ty_generics_as_generics = syn::parse_quote!(#ty_generics); + let pred = insert_lifetime_and_bound( + &mut proj_generics, + lifetime.clone(), + &ty_generics_as_generics, + ident.clone(), + ); + let mut where_clause = generics.clone().make_where_clause().clone(); + where_clause.predicates.push(pred); Ok(Self { proj: ProjectedType { @@ -239,6 +251,7 @@ impl Context { ref_ident: proj_ident(&ident, Immutable), lifetime, generics: proj_generics, + where_clause, }, orig: OriginalType { attrs, vis, ident, generics }, pinned_drop, @@ -250,7 +263,7 @@ impl Context { fn parse_struct(&mut self, fields: &Fields) -> Result { let (proj_pat, proj_init, proj_fields, proj_ref_fields) = match fields { Fields::Named(fields) => self.visit_named(fields)?, - Fields::Unnamed(fields) => self.visit_unnamed(fields, true)?, + Fields::Unnamed(fields) => self.visit_unnamed(fields)?, Fields::Unit => unreachable!(), }; @@ -259,7 +272,7 @@ impl Context { let proj_ref_ident = &self.proj.ref_ident; let vis = &self.proj.vis; let proj_generics = &self.proj.generics; - let where_clause = self.orig.generics.split_for_impl().2; + let where_clause = &self.proj.where_clause; // For tuple structs, we need to generate `(T1, T2) where Foo: Bar` // For non-tuple structs, we need to generate `where Foo: Bar { field1: T }` @@ -268,7 +281,7 @@ impl Context { (quote!(#where_clause #proj_fields), quote!(#where_clause #proj_ref_fields)) } Fields::Unnamed(_) => { - (quote!(#proj_fields #where_clause), quote!(#proj_ref_fields #where_clause)) + (quote!(#proj_fields #where_clause;), quote!(#proj_ref_fields #where_clause;)) } Fields::Unit => unreachable!(), }; @@ -303,7 +316,7 @@ impl Context { let proj_ref_ident = &self.proj.ref_ident; let vis = &self.proj.vis; let proj_generics = &self.proj.generics; - let where_clause = self.orig.generics.split_for_impl().2; + let where_clause = &self.proj.where_clause; let mut proj_items = quote! { #[allow(clippy::mut_mut)] // This lint warns `&mut &mut `. @@ -344,7 +357,7 @@ impl Context { for Variant { ident, fields, .. } in variants { let (proj_pat, proj_body, proj_fields, proj_ref_fields) = match fields { Fields::Named(fields) => self.visit_named(fields)?, - Fields::Unnamed(fields) => self.visit_unnamed(fields, false)?, + Fields::Unnamed(fields) => self.visit_unnamed(fields)?, Fields::Unit => { (TokenStream::new(), TokenStream::new(), TokenStream::new(), TokenStream::new()) } @@ -422,7 +435,6 @@ impl Context { fn visit_unnamed( &mut self, FieldsUnnamed { unnamed: fields, .. }: &FieldsUnnamed, - is_struct: bool, ) -> Result<(TokenStream, TokenStream, TokenStream, TokenStream)> { let mut proj_pat = Vec::with_capacity(fields.len()); let mut proj_body = Vec::with_capacity(fields.len()); @@ -460,11 +472,8 @@ impl Context { let proj_pat = quote!((#(#proj_pat),*)); let proj_body = quote!((#(#proj_body),*)); - let (proj_fields, proj_ref_fields) = if is_struct { - (quote!((#(#proj_fields),*);), quote!((#(#proj_ref_fields),*);)) - } else { - (quote!((#(#proj_fields),*)), quote!((#(#proj_ref_fields),*))) - }; + let (proj_fields, proj_ref_fields) = + (quote!((#(#proj_fields),*)), quote!((#(#proj_ref_fields),*))); Ok((proj_pat, proj_body, proj_fields, proj_ref_fields)) } diff --git a/pin-project-internal/src/utils.rs b/pin-project-internal/src/utils.rs index b96aa569..1890afcf 100644 --- a/pin-project-internal/src/utils.rs +++ b/pin-project-internal/src/utils.rs @@ -61,6 +61,29 @@ pub(crate) fn determine_lifetime_name( } } +/// Like `insert_lifetime`, but also generates a bound of the form +/// `OriginalType: 'lifetime`. Used when generating the definition +/// of a projection type +pub(crate) fn insert_lifetime_and_bound( + generics: &mut Generics, + lifetime: Lifetime, + orig_generics: &Generics, + orig_ident: Ident, +) -> WherePredicate { + insert_lifetime(generics, lifetime.clone()); + + let orig_type: syn::Type = syn::parse_quote!(#orig_ident #orig_generics); + let mut punct = Punctuated::new(); + punct.push(TypeParamBound::Lifetime(lifetime)); + + WherePredicate::Type(PredicateType { + lifetimes: None, + bounded_ty: orig_type, + colon_token: syn::token::Colon::default(), + bounds: punct, + }) +} + /// Inserts a `lifetime` at position `0` of `generics.params`. pub(crate) fn insert_lifetime(generics: &mut Generics, lifetime: Lifetime) { if generics.lt_token.is_none() { diff --git a/tests/no_infer_outlives.rs b/tests/no_infer_outlives.rs new file mode 100644 index 00000000..6602b62c --- /dev/null +++ b/tests/no_infer_outlives.rs @@ -0,0 +1,19 @@ +use pin_project::{pin_project, project}; + +trait Bar { + type Y; +} + +struct Example(A); + +impl Bar for Example { + type Y = Option; +} + +#[pin_project] +struct Foo { + _x: as Bar>::Y, +} + +#[project] +impl Foo {}