Skip to content

Commit

Permalink
Merge #188
Browse files Browse the repository at this point in the history
188: Add `OriginalType<A, B>: 'pin` bound to projected type r=taiki-e a=Aaron1011

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<A, B>: '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.

Co-authored-by: Aaron Hill <aa1ronham@gmail.com>
  • Loading branch information
bors[bot] and Aaron1011 committed Apr 14, 2020
2 parents 389ce9d + 9e3dc88 commit 1ead860
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 12 deletions.
33 changes: 21 additions & 12 deletions pin-project-internal/src/pin_project/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -250,7 +263,7 @@ impl Context {
fn parse_struct(&mut self, fields: &Fields) -> Result<TokenStream> {
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!(),
};

Expand All @@ -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 }`
Expand All @@ -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!(),
};
Expand Down Expand Up @@ -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 <ty>`.
Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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))
}
Expand Down
23 changes: 23 additions & 0 deletions pin-project-internal/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,29 @@ pub(crate) fn determine_lifetime_name(
}
}

/// Like `insert_lifetime`, but also generates a bound of the form
/// `OriginalType<A, B>: '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() {
Expand Down
19 changes: 19 additions & 0 deletions tests/no_infer_outlives.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use pin_project::{pin_project, project};

trait Bar<X> {
type Y;
}

struct Example<A>(A);

impl<X, T> Bar<X> for Example<T> {
type Y = Option<T>;
}

#[pin_project]
struct Foo<A, B> {
_x: <Example<A> as Bar<B>>::Y,
}

#[project]
impl<A, B> Foo<A, B> {}

0 comments on commit 1ead860

Please sign in to comment.