From b036c2fc3acc2005748fa1a25c23937ad0bb2431 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Thu, 19 Sep 2019 14:35:19 +0900 Subject: [PATCH] Add use statements support to #[project] attribute --- pin-project-internal/src/lib.rs | 34 ++++++++++++++++++++++++++- pin-project-internal/src/project.rs | 36 +++++++++++++++++++++++++++++ tests/project.rs | 35 ++++++++++++++++++++++++++++ tests/ui/project/use-public.rs | 17 ++++++++++++++ tests/ui/project/use-public.stderr | 11 +++++++++ tests/ui/project/use.rs | 19 +++++++++++++++ tests/ui/project/use.stderr | 14 +++++++++++ 7 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 tests/ui/project/use-public.rs create mode 100644 tests/ui/project/use-public.stderr create mode 100644 tests/ui/project/use.rs create mode 100644 tests/ui/project/use.stderr diff --git a/pin-project-internal/src/lib.rs b/pin-project-internal/src/lib.rs index 9b9854d5..b29d45a3 100644 --- a/pin-project-internal/src/lib.rs +++ b/pin-project-internal/src/lib.rs @@ -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 /// @@ -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 { +/// #[pin] +/// field: A, +/// } +/// +/// mod bar { +/// use super::Foo; +/// use pin_project::project; +/// use std::pin::Pin; +/// +/// #[project] +/// use super::Foo; +/// +/// #[project] +/// fn baz(foo: Pin<&mut Foo>) { +/// #[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); diff --git a/pin-project-internal/src/project.rs b/pin-project-internal/src/project.rs index 181a2ee1..000ed863 100644 --- a/pin-project-internal/src/project.rs +++ b/pin-project-internal/src/project.rs @@ -23,6 +23,7 @@ fn parse(mut stmt: Stmt) -> Result { 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)?, _ => {} } @@ -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); } @@ -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::` into `tree::__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) + } + } + } +} diff --git a/tests/project.rs b/tests/project.rs index 8dd9be99..bf283e96 100644 --- a/tests/project.rs +++ b/tests/project.rs @@ -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) {} + } +} diff --git a/tests/ui/project/use-public.rs b/tests/ui/project/use-public.rs new file mode 100644 index 00000000..26334c98 --- /dev/null +++ b/tests/ui/project/use-public.rs @@ -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() {} diff --git a/tests/ui/project/use-public.stderr b/tests/ui/project/use-public.stderr new file mode 100644 index 00000000..fe190c84 --- /dev/null +++ b/tests/ui/project/use-public.stderr @@ -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`. diff --git a/tests/ui/project/use.rs b/tests/ui/project/use.rs new file mode 100644 index 00000000..adf2bb91 --- /dev/null +++ b/tests/ui/project/use.rs @@ -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() {} diff --git a/tests/ui/project/use.stderr b/tests/ui/project/use.stderr new file mode 100644 index 00000000..1506c2e2 --- /dev/null +++ b/tests/ui/project/use.stderr @@ -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 +