Skip to content

Commit

Permalink
Merge #85
Browse files Browse the repository at this point in the history
85: Add use statements support to #[project] attribute r=taiki-e a=taiki-e

This adds a feature to convert annotated imports to a projected type's imports.

~~For now, this works as an import for both projected type and projection trait.~~ Now, this works as an import for projected type.

### Examples

```rust
use pin_project::pin_project;

#[pin_project]
struct Foo<A> {
    #[pin]
    field: A,
}

mod bar {
    use pin_project::project;
    use super::Foo;
    use std::pin::Pin;

    #[project]
    use super::Foo;

    #[project]
    fn baz<A>(foo: Pin<&mut Foo<A>>) {
        #[project]
        let Foo { field } = foo.project();
        let _: Pin<&mut A> = field;
    }
}
```

Co-authored-by: Taiki Endo <te316e89@gmail.com>
  • Loading branch information
bors[bot] and taiki-e authored Sep 19, 2019
2 parents 68e783d + b036c2f commit d6f860e
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 1 deletion.
34 changes: 33 additions & 1 deletion pin-project-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
// TODO: Move this doc into pin-project crate when https://github.com/rust-lang/rust/pull/62855 merged.
/// An attribute to provide way to refer to the projected type.
///
/// The following three syntaxes are supported.
/// The following syntaxes are supported.
///
/// ## `impl` blocks
///
Expand Down Expand Up @@ -472,6 +472,38 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
/// }
/// }
/// ```
///
/// ## `use` statements
///
/// ### Examples
///
/// ```rust
/// # mod dox {
/// use pin_project::pin_project;
///
/// #[pin_project]
/// struct Foo<A> {
/// #[pin]
/// field: A,
/// }
///
/// mod bar {
/// use super::Foo;
/// use pin_project::project;
/// use std::pin::Pin;
///
/// #[project]
/// use super::Foo;
///
/// #[project]
/// fn baz<A>(foo: Pin<&mut Foo<A>>) {
/// #[project]
/// let Foo { field } = foo.project();
/// let _: Pin<&mut A> = field;
/// }
/// }
/// # }
/// ```
#[proc_macro_attribute]
pub fn project(args: TokenStream, input: TokenStream) -> TokenStream {
let _: Nothing = syn::parse_macro_input!(args);
Expand Down
36 changes: 36 additions & 0 deletions pin-project-internal/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fn parse(mut stmt: Stmt) -> Result<TokenStream> {
Stmt::Local(local) => Context::default().replace_local(local)?,
Stmt::Item(Item::Fn(ItemFn { block, .. })) => Dummy.visit_block_mut(block),
Stmt::Item(Item::Impl(item)) => replace_item_impl(item),
Stmt::Item(Item::Use(item)) => replace_item_use(item)?,
_ => {}
}

Expand Down Expand Up @@ -156,6 +157,12 @@ fn replace_item_impl(item: &mut ItemImpl) {
}
}

fn replace_item_use(item: &mut ItemUse) -> Result<()> {
let mut visitor = UseTreeVisitor { res: Ok(()) };
visitor.visit_item_use_mut(item);
visitor.res
}

fn replace_ident(ident: &mut Ident) {
*ident = proj_ident(ident);
}
Expand Down Expand Up @@ -197,3 +204,32 @@ impl VisitMut for Dummy {
// Do not recurse into nested items.
}
}

struct UseTreeVisitor {
res: Result<()>,
}

impl VisitMut for UseTreeVisitor {
fn visit_use_tree_mut(&mut self, node: &mut UseTree) {
if self.res.is_err() {
return;
}

match node {
// Desugar `use tree::<name>` into `tree::__<name>Projection`.
UseTree::Name(name) => replace_ident(&mut name.ident),
UseTree::Glob(glob) => {
self.res =
Err(error!(glob, "#[project] attribute may not be used on glob imports"));
}
UseTree::Rename(rename) => {
// TODO: Consider allowing the projected type to be renamed by `#[project] use Foo as Bar`.
self.res =
Err(error!(rename, "#[project] attribute may not be used on renamed imports"));
}
node @ UseTree::Path(_) | node @ UseTree::Group(_) => {
visit_mut::visit_use_tree_mut(self, node)
}
}
}
}
35 changes: 35 additions & 0 deletions tests/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,38 @@ fn project_impl() {
fn foo<'_pin>(&'_pin self) {}
}
}

#[pin_project]
struct A {
#[pin]
field: u8,
}

mod project_use_1 {
use crate::A;
use core::pin::Pin;
use pin_project::project;

#[project]
use crate::A;

#[project]
#[test]
fn project_use() {
let mut x = A { field: 0 };
#[project]
let A { field } = Pin::new(&mut x).project();
let _: Pin<&mut u8> = field;
}
}

mod project_use_2 {
#[project]
use crate::A;
use pin_project::project;

#[project]
impl A {
fn project_use(self) {}
}
}
17 changes: 17 additions & 0 deletions tests/ui/project/use-public.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// compile-fail

use pin_project::pin_project;

#[pin_project]
struct A {
field: u8,
}

pub mod b {
use pin_project::project;

#[project]
pub use crate::A;
}

fn main() {}
11 changes: 11 additions & 0 deletions tests/ui/project/use-public.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error[E0365]: `__AProjection` is private, and cannot be re-exported
--> $DIR/use-public.rs:14:13
|
14 | pub use crate::A;
| ^^^^^^^^ re-export of private `__AProjection`
|
= note: consider declaring type or module `__AProjection` with `pub`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0365`.
19 changes: 19 additions & 0 deletions tests/ui/project/use.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// compile-fail

use pin_project::pin_project;

#[pin_project]
struct A {
field: u8,
}

mod b {
use pin_project::project;

#[project]
use crate::A as B; //~ ERROR #[project] attribute may not be used on renamed imports
#[project]
use crate::*; //~ ERROR #[project] attribute may not be used on glob imports
}

fn main() {}
14 changes: 14 additions & 0 deletions tests/ui/project/use.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: #[project] attribute may not be used on renamed imports
--> $DIR/use.rs:14:16
|
14 | use crate::A as B; //~ ERROR #[project] attribute may not be used on renamed imports
| ^^^^^^

error: #[project] attribute may not be used on glob imports
--> $DIR/use.rs:16:16
|
16 | use crate::*; //~ ERROR #[project] attribute may not be used on glob imports
| ^

error: aborting due to 2 previous errors

0 comments on commit d6f860e

Please sign in to comment.