Skip to content

Commit

Permalink
Add use statements support to #[project] attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Sep 10, 2019
1 parent 4cd166a commit 441545a
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 2 deletions.
33 changes: 32 additions & 1 deletion pin-project-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
/// *This attribute is available if pin-project is built with the
/// `"project_attr"` feature.*
///
/// The following three syntaxes are supported.
/// The following syntaxes are supported.
///
/// ## `impl` blocks
///
Expand Down Expand Up @@ -491,6 +491,37 @@ 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 pin_project::project;
/// #[project]
/// use super::Foo;
/// use super::Foo;
/// use std::pin::Pin;
///
/// #[project]
/// fn baz<A>(mut foo: Pin<&mut Foo<A>>) {
/// #[project]
/// let Foo { field } = foo.project();
/// let _: Pin<&mut A> = field;
/// }
/// }
/// # }
/// ```
#[cfg(feature = "project_attr")]
#[proc_macro_attribute]
pub fn project(args: TokenStream, input: TokenStream) -> TokenStream {
Expand Down
57 changes: 56 additions & 1 deletion pin-project-internal/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use syn::{
*,
};

use crate::utils::{proj_generics, proj_ident, proj_lifetime_name, VecExt, DEFAULT_LIFETIME_NAME};
use crate::utils::{
proj_generics, proj_ident, proj_lifetime_name, proj_trait_ident, VecExt, DEFAULT_LIFETIME_NAME,
};

/// The attribute name.
const NAME: &str = "project";
Expand All @@ -23,6 +25,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 +159,19 @@ fn replace_item_impl(item: &mut ItemImpl) {
}
}

fn replace_item_use(item: &mut ItemUse) -> Result<()> {
// Currently `#[project] use` imports both projected type and projection
// trait. As a result, you will receive a warning if either one is not used.
// Once `self: &mut Pin<&mut Self>` is stable, it will be removed in that
// and later versions. As a result, if both imported items are not actually
// used, you will only get a warning after that version.
item.attrs.push(syn::parse_quote!(#[allow(unused_imports)]));

let mut visitor = UseTreeVisitor::default();
visitor.visit_item_use_mut(item);
visitor.error.map_or_else(|| Ok(()), Err)
}

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

#[derive(Default)]
struct UseTreeVisitor {
error: Option<Error>,
}

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

match node {
// `use super::Foo` into `super::{__FooProjection, __FooProjectionTrait}`
UseTree::Name(name) => {
let trait_ = UseTree::Name(UseName { ident: proj_trait_ident(&name.ident) });
let mut name = name.clone();
replace_ident(&mut name.ident);
*node = UseTree::Group(UseGroup {
brace_token: token::Brace::default(),
items: vec![name.into(), trait_].into_iter().collect(),
});
}
UseTree::Glob(glob) => {
self.error
.replace(error!(glob, "#[project] attribute may not be used on glob imports"));
}
UseTree::Rename(rename) => {
self.error.replace(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)
}
}
}
}
33 changes: 33 additions & 0 deletions tests/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,36 @@ fn project_impl() {
fn foo<'_pin>(&'_pin self) {}
}
}

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

mod b {
#[project]
use crate::A;
use crate::A;
use core::pin::Pin;
use pin_project::project;

#[test]
fn project_use1() {
let mut x = A { field: 0 };
let mut x = Pin::new(&mut x);
let _x = x.project();
let _x = x.project_into();
}
}

mod c {
#[project]
use crate::A;
use core::pin::Pin;
use pin_project::project;

#[project]
impl A {
fn project_use1(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

// Currently, the visibility of projected type is always `pub(self)`,
// so this test is fine, but once https://github.com/taiki-e/pin-project/issues/81
// is merged, the test needs to be extended.

use pin_project::{pin_project, project};

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

#[project]
pub use crate::A;

fn main() {}
50 changes: 50 additions & 0 deletions tests/ui/project/use-public.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
error[E0255]: the name `__AProjection` is defined multiple times
--> $DIR/use-public.rs:15:16
|
9 | #[pin_project]
| -------------- previous definition of the type `__AProjection` here
...
15 | pub use crate::A;
| ^ `__AProjection` reimported here
|
= note: `__AProjection` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
|
15 | pub use crate::A as other___AProjection;
| ^^^^^^^^^^^^^^^^^^^^^^^^

error[E0255]: the name `__AProjectionTrait` is defined multiple times
--> $DIR/use-public.rs:15:16
|
9 | #[pin_project]
| -------------- previous definition of the trait `__AProjectionTrait` here
...
15 | pub use crate::A;
| ^ `__AProjectionTrait` reimported here
|
= note: `__AProjectionTrait` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
|
15 | pub use crate::A as other___AProjectionTrait;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0365]: `__AProjection` is private, and cannot be re-exported
--> $DIR/use-public.rs:15:16
|
15 | pub use crate::A;
| ^ re-export of private `__AProjection`
|
= note: consider declaring type or module `__AProjection` with `pub`

error[E0365]: `__AProjectionTrait` is private, and cannot be re-exported
--> $DIR/use-public.rs:15:16
|
15 | pub use crate::A;
| ^ re-export of private `__AProjectionTrait`
|
= note: consider declaring type or module `__AProjectionTrait` with `pub`

error: aborting due to 4 previous errors

Some errors have detailed explanations: E0255, E0365.
For more information about an error, try `rustc --explain E0255`.
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 441545a

Please sign in to comment.