Skip to content

Commit 577d55a

Browse files
committed
Add use statements support to #[project] attribute
1 parent ddc0122 commit 577d55a

File tree

7 files changed

+165
-1
lines changed

7 files changed

+165
-1
lines changed

pin-project-internal/src/lib.rs

+33-1
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
371371
/// *This attribute is available if pin-project is built with the
372372
/// `"project_attr"` feature.*
373373
///
374-
/// The following three syntaxes are supported.
374+
/// The following syntaxes are supported.
375375
///
376376
/// ## `impl` blocks
377377
///
@@ -479,6 +479,38 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
479479
/// }
480480
/// }
481481
/// ```
482+
///
483+
/// ## `use` statements
484+
///
485+
/// ### Examples
486+
///
487+
/// ```rust
488+
/// # mod dox {
489+
/// use pin_project::pin_project;
490+
///
491+
/// #[pin_project]
492+
/// struct Foo<A> {
493+
/// #[pin]
494+
/// field: A,
495+
/// }
496+
///
497+
/// mod bar {
498+
/// use super::Foo;
499+
/// use pin_project::project;
500+
/// use std::pin::Pin;
501+
///
502+
/// #[project]
503+
/// use super::Foo;
504+
///
505+
/// #[project]
506+
/// fn baz<A>(foo: Pin<&mut Foo<A>>) {
507+
/// #[project]
508+
/// let Foo { field } = foo.project();
509+
/// let _: Pin<&mut A> = field;
510+
/// }
511+
/// }
512+
/// # }
513+
/// ```
482514
#[cfg(feature = "project_attr")]
483515
#[proc_macro_attribute]
484516
pub fn project(args: TokenStream, input: TokenStream) -> TokenStream {

pin-project-internal/src/project.rs

+36
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ fn parse(mut stmt: Stmt) -> Result<TokenStream> {
2323
Stmt::Local(local) => Context::default().replace_local(local)?,
2424
Stmt::Item(Item::Fn(ItemFn { block, .. })) => Dummy.visit_block_mut(block),
2525
Stmt::Item(Item::Impl(item)) => replace_item_impl(item),
26+
Stmt::Item(Item::Use(item)) => replace_item_use(item)?,
2627
_ => {}
2728
}
2829

@@ -156,6 +157,12 @@ fn replace_item_impl(item: &mut ItemImpl) {
156157
}
157158
}
158159

160+
fn replace_item_use(item: &mut ItemUse) -> Result<()> {
161+
let mut visitor = UseTreeVisitor { res: Ok(()) };
162+
visitor.visit_item_use_mut(item);
163+
visitor.res
164+
}
165+
159166
fn replace_ident(ident: &mut Ident) {
160167
*ident = proj_ident(ident);
161168
}
@@ -197,3 +204,32 @@ impl VisitMut for Dummy {
197204
// Do not recurse into nested items.
198205
}
199206
}
207+
208+
struct UseTreeVisitor {
209+
res: Result<()>,
210+
}
211+
212+
impl VisitMut for UseTreeVisitor {
213+
fn visit_use_tree_mut(&mut self, node: &mut UseTree) {
214+
if self.res.is_err() {
215+
return;
216+
}
217+
218+
match node {
219+
// Desugar `use tree::<name>` into `tree::__<name>Projection`.
220+
UseTree::Name(name) => replace_ident(&mut name.ident),
221+
UseTree::Glob(glob) => {
222+
self.res =
223+
Err(error!(glob, "#[project] attribute may not be used on glob imports"));
224+
}
225+
UseTree::Rename(rename) => {
226+
// TODO: Consider allowing the projected type to be renamed by `#[project] use Foo as Bar`.
227+
self.res =
228+
Err(error!(rename, "#[project] attribute may not be used on renamed imports"));
229+
}
230+
node @ UseTree::Path(_) | node @ UseTree::Group(_) => {
231+
visit_mut::visit_use_tree_mut(self, node)
232+
}
233+
}
234+
}
235+
}

tests/project.rs

+35
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,38 @@ fn project_impl() {
150150
fn foo<'_pin>(&'_pin self) {}
151151
}
152152
}
153+
154+
#[pin_project]
155+
struct A {
156+
#[pin]
157+
field: u8,
158+
}
159+
160+
mod project_use_1 {
161+
use crate::A;
162+
use core::pin::Pin;
163+
use pin_project::project;
164+
165+
#[project]
166+
use crate::A;
167+
168+
#[project]
169+
#[test]
170+
fn project_use() {
171+
let mut x = A { field: 0 };
172+
#[project]
173+
let A { field } = Pin::new(&mut x).project();
174+
let _: Pin<&mut u8> = field;
175+
}
176+
}
177+
178+
mod project_use_2 {
179+
#[project]
180+
use crate::A;
181+
use pin_project::project;
182+
183+
#[project]
184+
impl A {
185+
fn project_use(self) {}
186+
}
187+
}

tests/ui/project/use-public.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// compile-fail
2+
3+
use pin_project::pin_project;
4+
5+
#[pin_project]
6+
struct A {
7+
field: u8,
8+
}
9+
10+
pub mod b {
11+
use pin_project::project;
12+
13+
#[project]
14+
pub use crate::A;
15+
}
16+
17+
fn main() {}

tests/ui/project/use-public.stderr

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0365]: `__AProjection` is private, and cannot be re-exported
2+
--> $DIR/use-public.rs:18:13
3+
|
4+
14 | pub use crate::A;
5+
| ^^^^^^^^ re-export of private `__AProjection`
6+
|
7+
= note: consider declaring type or module `__AProjection` with `pub`
8+
9+
error: aborting due to previous error
10+
11+
For more information about this error, try `rustc --explain E0365`.

tests/ui/project/use.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// compile-fail
2+
3+
use pin_project::pin_project;
4+
5+
#[pin_project]
6+
struct A {
7+
field: u8,
8+
}
9+
10+
mod b {
11+
use pin_project::project;
12+
13+
#[project]
14+
use crate::A as B; //~ ERROR #[project] attribute may not be used on renamed imports
15+
#[project]
16+
use crate::*; //~ ERROR #[project] attribute may not be used on glob imports
17+
}
18+
19+
fn main() {}

tests/ui/project/use.stderr

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: #[project] attribute may not be used on renamed imports
2+
--> $DIR/use.rs:14:16
3+
|
4+
14 | use crate::A as B; //~ ERROR #[project] attribute may not be used on renamed imports
5+
| ^^^^^^
6+
7+
error: #[project] attribute may not be used on glob imports
8+
--> $DIR/use.rs:16:16
9+
|
10+
16 | use crate::*; //~ ERROR #[project] attribute may not be used on glob imports
11+
| ^
12+
13+
error: aborting due to 2 previous errors
14+

0 commit comments

Comments
 (0)