Skip to content

Commit

Permalink
Prevent other proc macros from adding fields after the #[pin_project]…
Browse files Browse the repository at this point in the history
… attribute is applied
  • Loading branch information
taiki-e committed Sep 7, 2019
1 parent fe55c43 commit 5b09db5
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 87 deletions.
13 changes: 8 additions & 5 deletions pin-project-internal/src/pin_project/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ fn variants(cx: &mut Context, item: &mut ItemEnum) -> Result<(Vec<TokenStream>,
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);
Expand All @@ -89,7 +89,7 @@ fn variants(cx: &mut Context, item: &mut ItemEnum) -> Result<(Vec<TokenStream>,
Ok((proj_variants, proj_arms))
}

fn named(
pub(super) fn named(
cx: &mut Context,
FieldsNamed { named: fields, .. }: &mut FieldsNamed,
) -> Result<(TokenStream, TokenStream, TokenStream)> {
Expand Down Expand Up @@ -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());
Expand All @@ -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)? {
Expand All @@ -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))
}
91 changes: 9 additions & 82 deletions pin-project-internal/src/pin_project/structs.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -27,13 +25,13 @@ pub(super) fn validate(ident: &Ident, fields: &Fields) -> Result<()> {
pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result<TokenStream> {
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;

Expand All @@ -44,12 +42,12 @@ pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result<TokenStrea
};

let project_body = quote! {
let this = self.as_mut().get_unchecked_mut();
#proj_ident #proj_init
let #orig_ident #proj_pat = self.as_mut().get_unchecked_mut();
#proj_ident #proj_body
};
let project_into_body = quote! {
let this = self.get_unchecked_mut();
#proj_ident #proj_init
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));
Expand All @@ -58,74 +56,3 @@ pub(super) fn parse(cx: &mut Context, mut item: ItemStruct) -> Result<TokenStrea
item.extend(proj_items);
Ok(item)
}

fn named(
cx: &mut Context,
FieldsNamed { named: fields, .. }: &mut FieldsNamed,
) -> 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))
}
62 changes: 62 additions & 0 deletions tests/ui/cfg/auxiliary/sneaky_macro.rs
Original file line number Diff line number Diff line change
@@ -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!()
}
}
19 changes: 19 additions & 0 deletions tests/ui/cfg/field_sneaky.rs
Original file line number Diff line number Diff line change
@@ -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() {}
9 changes: 9 additions & 0 deletions tests/ui/cfg/field_sneaky.stderr
Original file line number Diff line number Diff line change
@@ -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`.

0 comments on commit 5b09db5

Please sign in to comment.