Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add use statements support to #[project] attribute #85

Merged
merged 1 commit into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc #43

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