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
+