Skip to content

Commit

Permalink
Allow naming the projected types
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed May 8, 2020
1 parent 9eff271 commit 7657fde
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 65 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ jobs:
# This is the minimum supported Rust version of this crate.
# When updating this, the reminder to update the minimum supported
# Rust version in README.md.
- build: 1.33.0
rust: 1.33.0
- build: 1.34.0
rust: 1.34.0
- build: 1.36.0
rust: 1.36.0
- build: 1.37.0
Expand Down
73 changes: 59 additions & 14 deletions pin-project-internal/src/pin_project/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ struct Args {
unsafe_unpin: Option<Span>,
/// `Replace`.
replace: Option<Span>,
/// `project = <ident>`.
project: Option<Ident>,
/// `project_ref = <ident>`.
project_ref: Option<Ident>,
/// `project_replace = <ident>`.
project_replace: Option<Ident>,
}

const DUPLICATE_PIN: &str = "duplicate #[pin] attribute";
Expand Down Expand Up @@ -205,6 +211,18 @@ impl Parse for Args {
"UnsafeUnpin" => {
replace(&mut args.unsafe_unpin, token.span(), &token)?;
}
"project" => {
let _: token::Eq = input.parse()?;
replace(&mut args.project, input.parse()?, &token)?;
}
"project_ref" => {
let _: token::Eq = input.parse()?;
replace(&mut args.project_ref, input.parse()?, &token)?;
}
"project_replace" => {
let _: token::Eq = input.parse()?;
replace(&mut args.project_replace, input.parse()?, &token)?;
}
_ => return Err(error!(token, "unexpected argument: {}", token)),
}

Expand All @@ -213,7 +231,14 @@ impl Parse for Args {
}
}

Ok(args)
if args.project_replace.is_some() && args.replace.is_none() {
Err(error!(
args.project_replace,
"`project_replace` argument can only be used together with `Replace` argument"
))
} else {
Ok(args)
}
}
}

Expand Down Expand Up @@ -279,6 +304,12 @@ struct Context<'a> {
unsafe_unpin: Option<Span>,
/// `Replace` argument (requires Sized bound)
replace: Option<Span>,
/// `project` argument.
project: bool,
/// `project_ref` argument.
project_ref: bool,
/// `project_replace` argument.
project_replace: bool,
}

impl<'a> Context<'a> {
Expand All @@ -288,7 +319,8 @@ impl<'a> Context<'a> {
ident: &'a Ident,
generics: &'a mut Generics,
) -> Result<Self> {
let Args { pinned_drop, unsafe_unpin, replace } = Args::get(attrs)?;
let Args { pinned_drop, unsafe_unpin, replace, project, project_ref, project_replace } =
Args::get(attrs)?;

let ty_generics = generics.split_for_impl().1;
let self_ty = syn::parse_quote!(#ident #ty_generics);
Expand All @@ -312,19 +344,22 @@ impl<'a> Context<'a> {
where_clause.predicates.push(pred);

Ok(Self {
pinned_drop,
unsafe_unpin,
replace,
project: project.is_some(),
project_ref: project_ref.is_some(),
project_replace: project_replace.is_some(),
proj: ProjectedType {
vis: determine_visibility(vis),
mut_ident: Mutable.proj_ident(ident),
ref_ident: Immutable.proj_ident(ident),
own_ident: Owned.proj_ident(ident),
mut_ident: project.unwrap_or_else(|| Mutable.proj_ident(ident)),
ref_ident: project_ref.unwrap_or_else(|| Immutable.proj_ident(ident)),
own_ident: project_replace.unwrap_or_else(|| Owned.proj_ident(ident)),
lifetime,
generics: proj_generics,
where_clause,
},
orig: OriginalType { attrs, vis, ident, generics },
pinned_drop,
unsafe_unpin,
replace,
pinned_fields: Vec::new(),
})
}
Expand Down Expand Up @@ -374,21 +409,26 @@ impl<'a> Context<'a> {
Fields::Unit => unreachable!(),
};

// If the user gave it a name, it should appear in the document.
let doc_attr = quote!(#[doc(hidden)]);
let doc_proj = if self.project { None } else { Some(&doc_attr) };
let doc_proj_ref = if self.project_ref { None } else { Some(&doc_attr) };
let doc_proj_own = if self.project_replace { None } else { Some(&doc_attr) };
let mut proj_items = quote! {
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj
#[allow(clippy::mut_mut)] // This lint warns `&mut &mut <ty>`.
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis struct #proj_ident #proj_generics #where_clause_fields
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj_ref
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis struct #proj_ref_ident #proj_generics #where_clause_ref_fields
};
if self.replace.is_some() {
// Currently, using quote_spanned here does not seem to have any effect on the diagnostics.
proj_items.extend(quote! {
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj_own
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis struct #proj_own_ident #orig_generics #where_clause_own_fields
Expand Down Expand Up @@ -458,15 +498,20 @@ impl<'a> Context<'a> {
let proj_generics = &self.proj.generics;
let where_clause = &self.proj.where_clause;

// If the user gave it a name, it should appear in the document.
let doc_attr = quote!(#[doc(hidden)]);
let doc_proj = if self.project { None } else { Some(&doc_attr) };
let doc_proj_ref = if self.project_ref { None } else { Some(&doc_attr) };
let doc_proj_own = if self.project_replace { None } else { Some(&doc_attr) };
let mut proj_items = quote! {
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj
#[allow(clippy::mut_mut)] // This lint warns `&mut &mut <ty>`.
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis enum #proj_ident #proj_generics #where_clause {
#proj_variants
}
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj_ref
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis enum #proj_ref_ident #proj_generics #where_clause {
Expand All @@ -476,7 +521,7 @@ impl<'a> Context<'a> {
if self.replace.is_some() {
// Currently, using quote_spanned here does not seem to have any effect on the diagnostics.
proj_items.extend(quote! {
#[doc(hidden)] // TODO: If the user gave it a name, it should appear in the document.
#doc_proj_own
#[allow(dead_code)] // This lint warns unused fields/variants.
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
#vis enum #proj_own_ident #orig_generics #orig_where_clause {
Expand Down
45 changes: 25 additions & 20 deletions tests/pin_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ use pin_project::{pin_project, pinned_drop, UnsafeUnpin};

#[test]
fn projection() {
#[pin_project(Replace)]
#[pin_project(
Replace,
project = StructProj,
project_ref = StructProjRef,
project_replace = StructProjOwn,
)]
struct Struct<T, U> {
#[pin]
field1: T,
Expand All @@ -31,16 +36,16 @@ fn projection() {

let mut s = Struct { field1: 1, field2: 2 };

let __StructProjection { field1, field2 } = Pin::new(&mut s).project();
let StructProj { field1, field2 } = Pin::new(&mut s).project();
let _: Pin<&mut i32> = field1;
let _: &mut i32 = field2;

let __StructProjectionRef { field1, field2 } = Pin::new(&s).project_ref();
let StructProjRef { field1, field2 } = Pin::new(&s).project_ref();
let _: Pin<&i32> = field1;
let _: &i32 = field2;

let mut s = Pin::new(&mut s);
let __StructProjectionOwned { field1, field2 } =
let StructProjOwn { field1, field2 } =
s.as_mut().project_replace(Struct { field1: 3, field2: 4 });
let _: PhantomData<i32> = field1;
let _: i32 = field2;
Expand All @@ -60,7 +65,7 @@ fn projection() {
let y: &mut i32 = s.1;
assert_eq!(*y, 2);

#[pin_project(Replace)]
#[pin_project(Replace, project = EnumProj)]
#[derive(Eq, PartialEq, Debug)]
enum Enum<A, B, C, D> {
Variant1(#[pin] A, B),
Expand All @@ -77,18 +82,18 @@ fn projection() {
let e = e_orig.as_mut().project();

match e {
__EnumProjection::Variant1(x, y) => {
EnumProj::Variant1(x, y) => {
let x: Pin<&mut i32> = x;
assert_eq!(*x, 1);

let y: &mut i32 = y;
assert_eq!(*y, 2);
}
__EnumProjection::Variant2 { field1, field2 } => {
EnumProj::Variant2 { field1, field2 } => {
let _x: Pin<&mut i32> = field1;
let _y: &mut i32 = field2;
}
__EnumProjection::None => {}
EnumProj::None => {}
}

assert_eq!(Pin::into_ref(e_orig).get_ref(), &Enum::Variant1(1, 2));
Expand All @@ -97,21 +102,21 @@ fn projection() {
let mut e = Pin::new(&mut e).project();

match &mut e {
__EnumProjection::Variant1(x, y) => {
EnumProj::Variant1(x, y) => {
let _x: &mut Pin<&mut i32> = x;
let _y: &mut &mut i32 = y;
}
__EnumProjection::Variant2 { field1, field2 } => {
EnumProj::Variant2 { field1, field2 } => {
let x: &mut Pin<&mut i32> = field1;
assert_eq!(**x, 3);

let y: &mut &mut i32 = field2;
assert_eq!(**y, 4);
}
__EnumProjection::None => {}
EnumProj::None => {}
}

if let __EnumProjection::Variant2 { field1, field2 } = e {
if let EnumProj::Variant2 { field1, field2 } = e {
let x: Pin<&mut i32> = field1;
assert_eq!(*x, 3);

Expand All @@ -122,7 +127,7 @@ fn projection() {

#[test]
fn enum_project_set() {
#[pin_project(Replace)]
#[pin_project(Replace, project = EnumProj)]
#[derive(Eq, PartialEq, Debug)]
enum Enum {
Variant1(#[pin] u8),
Expand All @@ -134,7 +139,7 @@ fn enum_project_set() {
let e_proj = e_orig.as_mut().project();

match e_proj {
__EnumProjection::Variant1(val) => {
EnumProj::Variant1(val) => {
let new_e = Enum::Variant2(val.as_ref().get_ref() == &25);
e_orig.set(new_e);
}
Expand Down Expand Up @@ -392,7 +397,7 @@ fn lifetime_project() {
unpinned: U,
}

#[pin_project(Replace)]
#[pin_project(Replace, project = EnumProj, project_ref = EnumProjRef)]
enum Enum<T, U> {
Variant {
#[pin]
Expand Down Expand Up @@ -422,12 +427,12 @@ fn lifetime_project() {
impl<T, U> Enum<T, U> {
fn get_pin_ref<'a>(self: Pin<&'a Self>) -> Pin<&'a T> {
match self.project_ref() {
__EnumProjectionRef::Variant { pinned, .. } => pinned,
EnumProjRef::Variant { pinned, .. } => pinned,
}
}
fn get_pin_mut<'a>(self: Pin<&'a mut Self>) -> Pin<&'a mut T> {
match self.project() {
__EnumProjection::Variant { pinned, .. } => pinned,
EnumProj::Variant { pinned, .. } => pinned,
}
}
}
Expand All @@ -450,7 +455,7 @@ fn lifetime_project_elided() {
unpinned: U,
}

#[pin_project(Replace)]
#[pin_project(Replace, project = EnumProj, project_ref = EnumProjRef)]
enum Enum<T, U> {
Variant {
#[pin]
Expand Down Expand Up @@ -480,12 +485,12 @@ fn lifetime_project_elided() {
impl<T, U> Enum<T, U> {
fn get_pin_ref(self: Pin<&Self>) -> Pin<&T> {
match self.project_ref() {
__EnumProjectionRef::Variant { pinned, .. } => pinned,
EnumProjRef::Variant { pinned, .. } => pinned,
}
}
fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
match self.project() {
__EnumProjection::Variant { pinned, .. } => pinned,
EnumProj::Variant { pinned, .. } => pinned,
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions tests/ui/pin_project/conflict-naming.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use pin_project::pin_project;

#[pin_project(project = A, project_ref = A)] //~ ERROR E0428,E0308
struct Struct(#[pin] ());

fn main() {}
21 changes: 21 additions & 0 deletions tests/ui/pin_project/conflict-naming.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
error[E0428]: the name `A` is defined multiple times
--> $DIR/conflict-naming.rs:3:1
|
3 | #[pin_project(project = A, project_ref = A)] //~ ERROR E0428,E0308
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| `A` redefined here
| previous definition of the type `A` here
|
= note: `A` must be defined only once in the type namespace of this module
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
--> $DIR/conflict-naming.rs:3:1
|
3 | #[pin_project(project = A, project_ref = A)] //~ ERROR E0428,E0308
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability
|
= note: expected mutable reference `&mut ()`
found reference `&()`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
30 changes: 30 additions & 0 deletions tests/ui/pin_project/invalid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,38 @@ mod pin_project_argument {
#[pin_project(PinnedDrop, UnsafeUnpin, PinnedDrop, UnsafeUnpin)] //~ ERROR duplicate `PinnedDrop` argument
struct Duplicate4(#[pin] ());

#[pin_project(project = A, project = B)] //~ ERROR duplicate `project` argument
struct DuplicateProject(#[pin] ());

#[pin_project(project_ref = A, project_ref = B)] //~ ERROR duplicate `project_ref` argument
struct DuplicateProjectRef(#[pin] ());

#[pin_project(project_replace = A, project_replace = B)] //~ ERROR duplicate `project_replace` argument
struct DuplicateProjectReplace(#[pin] ());

#[pin_project(project_replace = A)] //~ ERROR `project_replace` argument can only be used together with `Replace` argument
struct ProjectReplaceWithoutReplace(#[pin] ());

#[pin_project(PinnedDrop, Replace)] //~ ERROR arguments `PinnedDrop` and `Replace` are mutually exclusive
struct PinnedDropWithReplace(#[pin] ());

#[pin_project(project)] //~ ERROR expected `=`
struct Project1(#[pin] ());

#[pin_project(project = )] //~ ERROR unexpected end of input, expected identifier
struct Project2(#[pin] ());

#[pin_project(project_ref)] //~ ERROR expected `=`
struct ProjectRef1(#[pin] ());

#[pin_project(project_ref = )] //~ ERROR unexpected end of input, expected identifier
struct ProjectRef2(#[pin] ());

#[pin_project(project_replace)] //~ ERROR expected `=`
struct ProjectReplace1(#[pin] ());

#[pin_project(project_replace = )] //~ ERROR unexpected end of input, expected identifier
struct ProjectReplace2(#[pin] ());
}

mod pin_project_attribute {
Expand Down
Loading

0 comments on commit 7657fde

Please sign in to comment.