Skip to content

Commit d6f860e

Browse files
bors[bot]taiki-e
andauthored
Merge taiki-e#85
85: Add use statements support to #[project] attribute r=taiki-e a=taiki-e This adds a feature to convert annotated imports to a projected type's imports. ~~For now, this works as an import for both projected type and projection trait.~~ Now, this works as an import for projected type. ### Examples ```rust use pin_project::pin_project; #[pin_project] struct Foo<A> { #[pin] field: A, } mod bar { use pin_project::project; use super::Foo; 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; } } ``` Co-authored-by: Taiki Endo <te316e89@gmail.com>
2 parents 68e783d + b036c2f commit d6f860e

File tree

7 files changed

+165
-1
lines changed

7 files changed

+165
-1
lines changed

pin-project-internal/src/lib.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
364364
// TODO: Move this doc into pin-project crate when https://github.com/rust-lang/rust/pull/62855 merged.
365365
/// An attribute to provide way to refer to the projected type.
366366
///
367-
/// The following three syntaxes are supported.
367+
/// The following syntaxes are supported.
368368
///
369369
/// ## `impl` blocks
370370
///
@@ -472,6 +472,38 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
472472
/// }
473473
/// }
474474
/// ```
475+
///
476+
/// ## `use` statements
477+
///
478+
/// ### Examples
479+
///
480+
/// ```rust
481+
/// # mod dox {
482+
/// use pin_project::pin_project;
483+
///
484+
/// #[pin_project]
485+
/// struct Foo<A> {
486+
/// #[pin]
487+
/// field: A,
488+
/// }
489+
///
490+
/// mod bar {
491+
/// use super::Foo;
492+
/// use pin_project::project;
493+
/// use std::pin::Pin;
494+
///
495+
/// #[project]
496+
/// use super::Foo;
497+
///
498+
/// #[project]
499+
/// fn baz<A>(foo: Pin<&mut Foo<A>>) {
500+
/// #[project]
501+
/// let Foo { field } = foo.project();
502+
/// let _: Pin<&mut A> = field;
503+
/// }
504+
/// }
505+
/// # }
506+
/// ```
475507
#[proc_macro_attribute]
476508
pub fn project(args: TokenStream, input: TokenStream) -> TokenStream {
477509
let _: Nothing = syn::parse_macro_input!(args);

pin-project-internal/src/project.rs

Lines changed: 36 additions & 0 deletions
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

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

tests/ui/project/use-public.rs

Lines changed: 17 additions & 0 deletions
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

Lines changed: 11 additions & 0 deletions
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:14: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

Lines changed: 19 additions & 0 deletions
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

Lines changed: 14 additions & 0 deletions
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)