From a693e45a05a86040d1c3a008d296c93f71812a50 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Sat, 11 Oct 2025 14:43:07 +0200 Subject: [PATCH 01/14] tests: check that enums cannot derive `Zeroable` Signed-off-by: Benno Lossin --- tests/ui/compile-fail/zeroable/enum.rs | 16 +++++++++++++++ tests/ui/compile-fail/zeroable/enum.stderr | 23 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 tests/ui/compile-fail/zeroable/enum.rs create mode 100644 tests/ui/compile-fail/zeroable/enum.stderr diff --git a/tests/ui/compile-fail/zeroable/enum.rs b/tests/ui/compile-fail/zeroable/enum.rs new file mode 100644 index 00000000..2b676fe0 --- /dev/null +++ b/tests/ui/compile-fail/zeroable/enum.rs @@ -0,0 +1,16 @@ +extern crate pin_init; +use pin_init::*; + +#[derive(Zeroable)] +enum Num { + A(u32), + B(i32), +} + +#[derive(MaybeZeroable)] +enum Num2 { + A(u32), + B(i32), +} + +fn main() {} diff --git a/tests/ui/compile-fail/zeroable/enum.stderr b/tests/ui/compile-fail/zeroable/enum.stderr new file mode 100644 index 00000000..a9d25903 --- /dev/null +++ b/tests/ui/compile-fail/zeroable/enum.stderr @@ -0,0 +1,23 @@ +error: no rules expected keyword `enum` + --> tests/ui/compile-fail/zeroable/enum.rs:5:1 + | +5 | enum Num { + | ^^^^ no rules expected this token in macro call + | +note: while trying to match keyword `struct` + --> src/macros.rs + | + | $vis:vis struct $name:ident + | ^^^^^^ + +error: no rules expected keyword `enum` + --> tests/ui/compile-fail/zeroable/enum.rs:11:1 + | +11 | enum Num2 { + | ^^^^ no rules expected this token in macro call + | +note: while trying to match keyword `struct` + --> src/macros.rs + | + | $vis:vis struct $name:ident + | ^^^^^^ From dc0131a0b2a9bd38a5e8697ee56bebdf40b7b337 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Mon, 20 Oct 2025 09:57:18 +0200 Subject: [PATCH 02/14] tests: ensure the guards cannot be accessed The guards created in order to drop already initialized fields correctly when the initializer errors or panics must not be accessible by user controlled code. (They could for example `mem::forget` the guard and thus prevent its normal function, making the initializer macros unsound.) Thus add a test to ensure that this is true. Signed-off-by: Benno Lossin --- tests/ui/compile-fail/init/guard_access.rs | 14 ++++++++++++++ tests/ui/compile-fail/init/guard_access.stderr | 5 +++++ 2 files changed, 19 insertions(+) create mode 100644 tests/ui/compile-fail/init/guard_access.rs create mode 100644 tests/ui/compile-fail/init/guard_access.stderr diff --git a/tests/ui/compile-fail/init/guard_access.rs b/tests/ui/compile-fail/init/guard_access.rs new file mode 100644 index 00000000..bc837e55 --- /dev/null +++ b/tests/ui/compile-fail/init/guard_access.rs @@ -0,0 +1,14 @@ +use pin_init::*; + +struct Foo { + x: usize, +} + +fn main() { + let _ = init!(Foo { + x: 0, + _: { + let _ = __x_guard; + }, + }); +} diff --git a/tests/ui/compile-fail/init/guard_access.stderr b/tests/ui/compile-fail/init/guard_access.stderr new file mode 100644 index 00000000..687df536 --- /dev/null +++ b/tests/ui/compile-fail/init/guard_access.stderr @@ -0,0 +1,5 @@ +error[E0425]: cannot find value `__x_guard` in this scope + --> tests/ui/compile-fail/init/guard_access.rs:11:21 + | +11 | let _ = __x_guard; + | ^^^^^^^^^ not found in this scope From 252dc482bad847ddef61832e2937c221789cbd25 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Mon, 20 Oct 2025 09:58:41 +0200 Subject: [PATCH 03/14] tests: ensure that the data cannot be accessed Similarly to guards, the pin-init data generated by the `#[pin_data]` attribute macro and instantiated by an initializer macro should not be accessible by user-controlled code. Signed-off-by: Benno Lossin --- tests/ui/compile-fail/init/data_access.rs | 12 ++++++++++++ tests/ui/compile-fail/init/data_access.stderr | 5 +++++ 2 files changed, 17 insertions(+) create mode 100644 tests/ui/compile-fail/init/data_access.rs create mode 100644 tests/ui/compile-fail/init/data_access.stderr diff --git a/tests/ui/compile-fail/init/data_access.rs b/tests/ui/compile-fail/init/data_access.rs new file mode 100644 index 00000000..d8574aa1 --- /dev/null +++ b/tests/ui/compile-fail/init/data_access.rs @@ -0,0 +1,12 @@ +use pin_init::*; + +#[pin_data] +struct Foo {} + +fn main() { + let _ = pin_init!(Foo { + _: { + let _ = __data; + }, + }); +} diff --git a/tests/ui/compile-fail/init/data_access.stderr b/tests/ui/compile-fail/init/data_access.stderr new file mode 100644 index 00000000..c14114b4 --- /dev/null +++ b/tests/ui/compile-fail/init/data_access.stderr @@ -0,0 +1,5 @@ +error[E0425]: cannot find value `__data` in this scope + --> tests/ui/compile-fail/init/data_access.rs:9:21 + | +9 | let _ = __data; + | ^^^^^^ not found in this scope From b81126f921f68111a18f39c8c1067f88e18f011c Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Wed, 22 Oct 2025 19:22:45 +0200 Subject: [PATCH 04/14] tests: add new test case to underscore The construct `_: { foo()? }` will result in the expanded code to contain stuff like: { ...; } { foo()? } { ...; } Which will trigger the `unused_braces` lint from rustc. Thus test that it's correctly ignored. Signed-off-by: Benno Lossin --- tests/underscore.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/underscore.rs b/tests/underscore.rs index 583b3b39..515a78ce 100644 --- a/tests/underscore.rs +++ b/tests/underscore.rs @@ -12,6 +12,10 @@ fn bar() -> bool { true } +fn baz() -> Result<(), ()> { + Err(()) +} + impl Foo { pub fn new() -> impl Init { try_init!(Self { @@ -28,4 +32,11 @@ impl Foo { } }? ()) } + + pub fn create(x: u64) -> impl Init { + try_init!(Self { + _: { baz()? }, + x, + }? ()) + } } From e78e60b9e2ebf196658efc7ede24e6f89b54f6aa Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Mon, 20 Oct 2025 14:55:58 +0200 Subject: [PATCH 05/14] remove `try_` versions of the initializer macros The `try_[pin_]init!` versions of the initializer macros are superfluous. Instead of forcing the user to always write an error in `try_[pin_]init!` and not allowing one in `[pin_]init!`, combine them into `[pin_]init!` that defaults the error to `core::convert::Infallible`, but also allows to specify a custom one. Projects using pin-init still can provide their own defaulting initializers using the `try_` prefix. Signed-off-by: Benno Lossin --- README.md | 2 +- examples/linked_list.rs | 19 ++- examples/pthread_mutex.rs | 10 +- src/lib.rs | 110 ++---------------- tests/ring_buf.rs | 4 +- .../compile-fail/init/missing_error_type.rs | 2 +- .../init/missing_error_type.stderr | 6 +- tests/ui/compile-fail/init/missing_field.rs | 4 +- .../ui/compile-fail/init/missing_field.stderr | 12 +- .../compile-fail/init/missing_pin_data.stderr | 2 +- .../ui/compile-fail/init/no_error_coercion.rs | 2 +- .../init/no_error_coercion.stderr | 4 +- .../compile-fail/init/wrong_generics2.stderr | 2 +- tests/underscore.rs | 6 +- 14 files changed, 49 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index 74bbb4e0..6cee6ab1 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ struct DriverData { impl DriverData { fn new() -> impl PinInit { - try_pin_init!(Self { + pin_init!(Self { status <- CMutex::new(0), buffer: Box::init(pin_init::init_zeroed())?, }? Error) diff --git a/examples/linked_list.rs b/examples/linked_list.rs index f9e117c7..8445a589 100644 --- a/examples/linked_list.rs +++ b/examples/linked_list.rs @@ -6,7 +6,6 @@ use core::{ cell::Cell, - convert::Infallible, marker::PhantomPinned, pin::Pin, ptr::{self, NonNull}, @@ -31,31 +30,31 @@ pub struct ListHead { impl ListHead { #[inline] - pub fn new() -> impl PinInit { - try_pin_init!(&this in Self { + pub fn new() -> impl PinInit { + pin_init!(&this in Self { next: unsafe { Link::new_unchecked(this) }, prev: unsafe { Link::new_unchecked(this) }, pin: PhantomPinned, - }? Infallible) + }) } #[inline] #[allow(dead_code)] - pub fn insert_next(list: &ListHead) -> impl PinInit + '_ { - try_pin_init!(&this in Self { + pub fn insert_next(list: &ListHead) -> impl PinInit + '_ { + pin_init!(&this in Self { prev: list.next.prev().replace(unsafe { Link::new_unchecked(this)}), next: list.next.replace(unsafe { Link::new_unchecked(this)}), pin: PhantomPinned, - }? Infallible) + }) } #[inline] - pub fn insert_prev(list: &ListHead) -> impl PinInit + '_ { - try_pin_init!(&this in Self { + pub fn insert_prev(list: &ListHead) -> impl PinInit + '_ { + pin_init!(&this in Self { next: list.prev.next().replace(unsafe { Link::new_unchecked(this)}), prev: list.prev.replace(unsafe { Link::new_unchecked(this)}), pin: PhantomPinned, - }? Infallible) + }) } #[inline] diff --git a/examples/pthread_mutex.rs b/examples/pthread_mutex.rs index 49b004c8..4e082ec7 100644 --- a/examples/pthread_mutex.rs +++ b/examples/pthread_mutex.rs @@ -98,11 +98,11 @@ mod pthread_mtx { // SAFETY: mutex has been initialized unsafe { pin_init_from_closure(init) } } - try_pin_init!(Self { - data: UnsafeCell::new(data), - raw <- init_raw(), - pin: PhantomPinned, - }? Error) + pin_init!(Self { + data: UnsafeCell::new(data), + raw <- init_raw(), + pin: PhantomPinned, + }? Error) } #[allow(dead_code)] diff --git a/src/lib.rs b/src/lib.rs index dd553212..4654148c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,7 +146,7 @@ //! //! impl DriverData { //! fn new() -> impl PinInit { -//! try_pin_init!(Self { +//! pin_init!(Self { //! status <- CMutex::new(0), //! buffer: Box::init(pin_init::init_zeroed())?, //! }? Error) @@ -528,7 +528,7 @@ macro_rules! stack_pin_init { /// x: u32, /// } /// -/// stack_try_pin_init!(let foo: Foo = try_pin_init!(Foo { +/// stack_try_pin_init!(let foo: Foo = pin_init!(Foo { /// a <- CMutex::new(42), /// b: Box::try_new(Bar { /// x: 64, @@ -555,7 +555,7 @@ macro_rules! stack_pin_init { /// x: u32, /// } /// -/// stack_try_pin_init!(let foo: Foo =? try_pin_init!(Foo { +/// stack_try_pin_init!(let foo: Foo =? pin_init!(Foo { /// a <- CMutex::new(42), /// b: Box::try_new(Bar { /// x: 64, @@ -584,10 +584,10 @@ macro_rules! stack_try_pin_init { }; } -/// Construct an in-place, pinned initializer for `struct`s. +/// Construct an in-place, fallible pinned initializer for `struct`s. /// -/// This macro defaults the error to [`Infallible`]. If you need a different error, then use -/// [`try_pin_init!`]. +/// The error type defaults to [`Infallible`]; if you need a different one, write `? Error` at the +/// end, after the struct initializer. /// /// The syntax is almost identical to that of a normal `struct` initializer: /// @@ -783,54 +783,10 @@ macro_rules! pin_init { ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) => { - $crate::try_pin_init!($(&$this in)? $t $(::<$($generics),*>)? { + $crate::pin_init!($(&$this in)? $t $(::<$($generics),*>)? { $($fields)* }? ::core::convert::Infallible) }; -} - -/// Construct an in-place, fallible pinned initializer for `struct`s. -/// -/// If the initialization can complete without error (or [`Infallible`]), then use [`pin_init!`]. -/// -/// You can use the `?` operator or use `return Err(err)` inside the initializer to stop -/// initialization and return the error. -/// -/// IMPORTANT: if you have `unsafe` code inside of the initializer you have to ensure that when -/// initialization fails, the memory can be safely deallocated without any further modifications. -/// -/// The syntax is identical to [`pin_init!`] with the following exception: you must append `? $type` -/// after the `struct` initializer to specify the error type you want to use. -/// -/// # Examples -/// -/// ```rust -/// # #![feature(allocator_api)] -/// # #[path = "../examples/error.rs"] mod error; use error::Error; -/// use pin_init::{pin_data, try_pin_init, PinInit, InPlaceInit, init_zeroed}; -/// -/// #[pin_data] -/// struct BigBuf { -/// big: Box<[u8; 1024 * 1024 * 1024]>, -/// small: [u8; 1024 * 1024], -/// ptr: *mut u8, -/// } -/// -/// impl BigBuf { -/// fn new() -> impl PinInit { -/// try_pin_init!(Self { -/// big: Box::init(init_zeroed())?, -/// small: [0; 1024 * 1024], -/// ptr: core::ptr::null_mut(), -/// }? Error) -/// } -/// } -/// # let _ = Box::pin_init(BigBuf::new()); -/// ``` -// For a detailed example of how this macro works, see the module documentation of the hidden -// module `macros` inside of `macros.rs`. -#[macro_export] -macro_rules! try_pin_init { ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) => { @@ -847,10 +803,10 @@ macro_rules! try_pin_init { } } -/// Construct an in-place initializer for `struct`s. +/// Construct an in-place, fallible initializer for `struct`s. /// -/// This macro defaults the error to [`Infallible`]. If you need a different error, then use -/// [`try_init!`]. +/// This macro defaults the error to [`Infallible`]; if you need a different one, write `? Error` +/// at the end, after the struct initializer. /// /// The syntax is identical to [`pin_init!`] and its safety caveats also apply: /// - `unsafe` code must guarantee either full initialization or return an error and allow @@ -890,52 +846,10 @@ macro_rules! init { ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) => { - $crate::try_init!($(&$this in)? $t $(::<$($generics),*>)? { + $crate::init!($(&$this in)? $t $(::<$($generics),*>)? { $($fields)* }? ::core::convert::Infallible) - } -} - -/// Construct an in-place fallible initializer for `struct`s. -/// -/// If the initialization can complete without error (or [`Infallible`]), then use -/// [`init!`]. -/// -/// The syntax is identical to [`try_pin_init!`]. You need to specify a custom error -/// via `? $type` after the `struct` initializer. -/// The safety caveats from [`try_pin_init!`] also apply: -/// - `unsafe` code must guarantee either full initialization or return an error and allow -/// deallocation of the memory. -/// - the fields are initialized in the order given in the initializer. -/// - no references to fields are allowed to be created inside of the initializer. -/// -/// # Examples -/// -/// ```rust -/// # #![feature(allocator_api)] -/// # use core::alloc::AllocError; -/// # use pin_init::InPlaceInit; -/// use pin_init::{try_init, Init, init_zeroed}; -/// -/// struct BigBuf { -/// big: Box<[u8; 1024 * 1024 * 1024]>, -/// small: [u8; 1024 * 1024], -/// } -/// -/// impl BigBuf { -/// fn new() -> impl Init { -/// try_init!(Self { -/// big: Box::init(init_zeroed())?, -/// small: [0; 1024 * 1024], -/// }? AllocError) -/// } -/// } -/// # let _ = Box::init(BigBuf::new()); -/// ``` -// For a detailed example of how this macro works, see the module documentation of the hidden -// module `macros` inside of `macros.rs`. -#[macro_export] -macro_rules! try_init { + }; ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) => { diff --git a/tests/ring_buf.rs b/tests/ring_buf.rs index 1d21ce1e..9029f511 100644 --- a/tests/ring_buf.rs +++ b/tests/ring_buf.rs @@ -166,7 +166,7 @@ pub struct EvenU64 { impl EvenU64 { #[allow(clippy::manual_is_multiple_of)] pub fn new2(value: u64) -> impl Init { - try_init!(Self { + init!(Self { info: "Hello world!".to_owned(), data: if value % 2 == 0 { value @@ -178,7 +178,7 @@ impl EvenU64 { #[allow(clippy::manual_is_multiple_of)] pub fn new(value: u64) -> impl Init { - try_init!(Self { + init!(Self { info: "Hello world!".to_owned(), data: if value % 2 == 0 { value diff --git a/tests/ui/compile-fail/init/missing_error_type.rs b/tests/ui/compile-fail/init/missing_error_type.rs index 13fcf9a7..352a94b5 100644 --- a/tests/ui/compile-fail/init/missing_error_type.rs +++ b/tests/ui/compile-fail/init/missing_error_type.rs @@ -5,5 +5,5 @@ struct Foo { } fn main() { - let _ = try_init!(Foo { x: Box::new(0)? }?); + let _ = init!(Foo { x: Box::new(0)? }?); } diff --git a/tests/ui/compile-fail/init/missing_error_type.stderr b/tests/ui/compile-fail/init/missing_error_type.stderr index 8f9f3ef3..38a114ee 100644 --- a/tests/ui/compile-fail/init/missing_error_type.stderr +++ b/tests/ui/compile-fail/init/missing_error_type.stderr @@ -1,8 +1,8 @@ error: unexpected end of macro invocation - --> tests/ui/compile-fail/init/missing_error_type.rs:8:47 + --> tests/ui/compile-fail/init/missing_error_type.rs:8:43 | -8 | let _ = try_init!(Foo { x: Box::new(0)? }?); - | ^ missing tokens in macro arguments +8 | let _ = init!(Foo { x: Box::new(0)? }?); + | ^ missing tokens in macro arguments | note: while trying to match meta-variable `$err:ty` --> src/lib.rs diff --git a/tests/ui/compile-fail/init/missing_field.rs b/tests/ui/compile-fail/init/missing_field.rs index 048d067b..ec2f3f7a 100644 --- a/tests/ui/compile-fail/init/missing_field.rs +++ b/tests/ui/compile-fail/init/missing_field.rs @@ -9,7 +9,7 @@ struct Foo { fn main() { let _foo = pin_init!(Foo { a: 0 }); - let _foo = try_pin_init!(Foo { a: 0 }? ::std::convert::Infallible); + let _foo = pin_init!(Foo { a: 0 }? ::std::convert::Infallible); let _foo = init!(Foo { a: 0 }); - let _foo = try_init!(Foo { a: 0 }? ::std::convert::Infallible); + let _foo = init!(Foo { a: 0 }? ::std::convert::Infallible); } diff --git a/tests/ui/compile-fail/init/missing_field.stderr b/tests/ui/compile-fail/init/missing_field.stderr index 14dcded1..4a6c1d89 100644 --- a/tests/ui/compile-fail/init/missing_field.stderr +++ b/tests/ui/compile-fail/init/missing_field.stderr @@ -5,10 +5,10 @@ error[E0063]: missing field `b` in initializer of `Foo` | ^^^ missing `b` error[E0063]: missing field `b` in initializer of `Foo` - --> tests/ui/compile-fail/init/missing_field.rs:12:30 + --> tests/ui/compile-fail/init/missing_field.rs:12:26 | -12 | let _foo = try_pin_init!(Foo { a: 0 }? ::std::convert::Infallible); - | ^^^ missing `b` +12 | let _foo = pin_init!(Foo { a: 0 }? ::std::convert::Infallible); + | ^^^ missing `b` error[E0063]: missing field `b` in initializer of `Foo` --> tests/ui/compile-fail/init/missing_field.rs:13:22 @@ -17,7 +17,7 @@ error[E0063]: missing field `b` in initializer of `Foo` | ^^^ missing `b` error[E0063]: missing field `b` in initializer of `Foo` - --> tests/ui/compile-fail/init/missing_field.rs:14:26 + --> tests/ui/compile-fail/init/missing_field.rs:14:22 | -14 | let _foo = try_init!(Foo { a: 0 }? ::std::convert::Infallible); - | ^^^ missing `b` +14 | let _foo = init!(Foo { a: 0 }? ::std::convert::Infallible); + | ^^^ missing `b` diff --git a/tests/ui/compile-fail/init/missing_pin_data.stderr b/tests/ui/compile-fail/init/missing_pin_data.stderr index 22f34abd..c228da5a 100644 --- a/tests/ui/compile-fail/init/missing_pin_data.stderr +++ b/tests/ui/compile-fail/init/missing_pin_data.stderr @@ -10,4 +10,4 @@ error[E0599]: no associated item named `__pin_data` found for struct `Foo` in th = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `__pin_data`, perhaps you need to implement it: candidate #1: `HasPinData` - = note: this error originates in the macro `$crate::try_pin_init` which comes from the expansion of the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::pin_init` which comes from the expansion of the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/no_error_coercion.rs b/tests/ui/compile-fail/init/no_error_coercion.rs index 5f6844aa..818eacb5 100644 --- a/tests/ui/compile-fail/init/no_error_coercion.rs +++ b/tests/ui/compile-fail/init/no_error_coercion.rs @@ -13,7 +13,7 @@ struct Bar { impl Foo { fn new() -> impl Init { - try_init!(Self { + init!(Self { a: Box::new(42), bar <- init!(Bar { b: 42 }), }? AllocError) diff --git a/tests/ui/compile-fail/init/no_error_coercion.stderr b/tests/ui/compile-fail/init/no_error_coercion.stderr index dae2783e..37ba6916 100644 --- a/tests/ui/compile-fail/init/no_error_coercion.stderr +++ b/tests/ui/compile-fail/init/no_error_coercion.stderr @@ -1,7 +1,7 @@ error[E0277]: `?` couldn't convert the error to `std::alloc::AllocError` --> tests/ui/compile-fail/init/no_error_coercion.rs:16:9 | -16 | / try_init!(Self { +16 | / init!(Self { 17 | | a: Box::new(42), 18 | | bar <- init!(Bar { b: 42 }), 19 | | }? AllocError) @@ -11,4 +11,4 @@ error[E0277]: `?` couldn't convert the error to `std::alloc::AllocError` | the trait `From` is not implemented for `std::alloc::AllocError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `try_init` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/wrong_generics2.stderr b/tests/ui/compile-fail/init/wrong_generics2.stderr index e8eab623..f45201eb 100644 --- a/tests/ui/compile-fail/init/wrong_generics2.stderr +++ b/tests/ui/compile-fail/init/wrong_generics2.stderr @@ -44,7 +44,7 @@ error[E0423]: expected value, found struct `Foo` 9 | | }); | |______^ help: use struct literal syntax instead: `Foo { value: val }` | - = note: this error originates in the macro `$crate::try_init` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::init` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0107]: struct takes 1 generic argument but 2 generic arguments were supplied --> tests/ui/compile-fail/init/wrong_generics2.rs:7:19 diff --git a/tests/underscore.rs b/tests/underscore.rs index 515a78ce..a55a238a 100644 --- a/tests/underscore.rs +++ b/tests/underscore.rs @@ -1,4 +1,4 @@ -use pin_init::{try_init, Init}; +use pin_init::{init, Init}; pub struct Foo { x: u64, @@ -18,7 +18,7 @@ fn baz() -> Result<(), ()> { impl Foo { pub fn new() -> impl Init { - try_init!(Self { + init!(Self { _: { if foo() { return Err(()); @@ -34,7 +34,7 @@ impl Foo { } pub fn create(x: u64) -> impl Init { - try_init!(Self { + init!(Self { _: { baz()? }, x, }? ()) From 3d39cd91fd978874cfdf9db8436291d4e1197a5f Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Mon, 20 Oct 2025 14:53:11 +0200 Subject: [PATCH 06/14] allow the crate to refer to itself as `pin-init` in doc tests The `syn` approach requires use of `::pin_init::...` instead of the `$crate::...` construct available to declarative macros. To be able to use the `pin_init` crate from itself (which includes doc tests), we have to declare it as such. Signed-off-by: Benno Lossin --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4654148c..d96f1b5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -290,6 +290,11 @@ use core::{ ptr::{self, NonNull}, }; +// This is used by doc-tests -- the proc-macros expand to `::pin_init::...` and without this the +// doc-tests wouldn't have an extern crate named `pin_init`. +#[allow(unused_extern_crates)] +extern crate self as pin_init; + #[doc(hidden)] pub mod __internal; #[doc(hidden)] From 2b60a29748ec42ed269a6c6c0d69dc9dd876a209 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Sat, 11 Oct 2025 14:02:56 +0200 Subject: [PATCH 07/14] internal: add `syn` dependency `syn` makes parsing Rust from proc-macros a lot simpler. `pin-init` has not used `syn` up until now, because the Linux kernel does not support using it. Since that changes now, we can finally utilize the added ergonomics of parsing proc-macro input with `syn`. Signed-off-by: Benno Lossin --- Cargo.lock | 9 +++++---- internal/Cargo.lock | 16 ++++++++++++++-- internal/Cargo.toml | 1 + 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8c38a00..a99484bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", + "syn", ] [[package]] @@ -117,9 +118,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -196,9 +197,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", diff --git a/internal/Cargo.lock b/internal/Cargo.lock index 4fe4f0fe..245de5da 100644 --- a/internal/Cargo.lock +++ b/internal/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", + "syn", ] [[package]] @@ -22,9 +23,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -44,6 +45,17 @@ version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "unicode-ident" version = "1.0.8" diff --git a/internal/Cargo.toml b/internal/Cargo.toml index 3e375db1..d3af5351 100644 --- a/internal/Cargo.toml +++ b/internal/Cargo.toml @@ -15,6 +15,7 @@ proc-macro = true [dependencies] quote = "1.0" proc-macro2 = "1.0" +syn = { version = "2.0.86", features = ["full", "parsing", "visit-mut"] } [build-dependencies] rustc_version = "0.4" From abf9cf9b1544876761a77cd1db1b29a35d6d4316 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Sat, 11 Oct 2025 14:11:02 +0200 Subject: [PATCH 08/14] internal: remove `proc-macro[2]` and `quote` workarounds The kernel only had the `proc-macro` library available, whereas the user-space version also used `proc-macro2` and `quote`. Now both are available to the kernel, making it possible to remove the workarounds. Signed-off-by: Benno Lossin --- internal/src/helpers.rs | 5 +---- internal/src/lib.rs | 16 ---------------- internal/src/pin_data.rs | 6 ++---- internal/src/pinned_drop.rs | 6 ++---- internal/src/zeroable.rs | 6 ++---- 5 files changed, 7 insertions(+), 32 deletions(-) diff --git a/internal/src/helpers.rs b/internal/src/helpers.rs index 236f989a..1d2dd27c 100644 --- a/internal/src/helpers.rs +++ b/internal/src/helpers.rs @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -#[cfg(not(kernel))] -use proc_macro2 as proc_macro; - -use proc_macro::{TokenStream, TokenTree}; +use proc_macro2::{TokenStream, TokenTree}; /// Parsed generics. /// diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 297b0129..4c4dc639 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -7,27 +7,11 @@ //! `pin-init` proc macros. #![cfg_attr(not(RUSTC_LINT_REASONS_IS_STABLE), feature(lint_reasons))] -// Allow `.into()` to convert -// - `proc_macro2::TokenStream` into `proc_macro::TokenStream` in the user-space version. -// - `proc_macro::TokenStream` into `proc_macro::TokenStream` in the kernel version. -// Clippy warns on this conversion, but it's required by the user-space version. -// -// Remove once we have `proc_macro2` in the kernel. -#![allow(clippy::useless_conversion)] // Documentation is done in the pin-init crate instead. #![allow(missing_docs)] use proc_macro::TokenStream; -#[cfg(kernel)] -#[path = "../../../macros/quote.rs"] -#[macro_use] -#[cfg_attr(not(kernel), rustfmt::skip)] -mod quote; -#[cfg(not(kernel))] -#[macro_use] -extern crate quote; - mod helpers; mod pin_data; mod pinned_drop; diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 87d4a7eb..12d9b02c 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -1,10 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -#[cfg(not(kernel))] -use proc_macro2 as proc_macro; - use crate::helpers::{parse_generics, Generics}; -use proc_macro::{Group, Punct, Spacing, TokenStream, TokenTree}; +use proc_macro2::{Group, Punct, Spacing, TokenStream, TokenTree}; +use quote::quote; pub(crate) fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream { // This proc-macro only does some pre-parsing and then delegates the actual parsing to diff --git a/internal/src/pinned_drop.rs b/internal/src/pinned_drop.rs index c4ca7a70..978c2594 100644 --- a/internal/src/pinned_drop.rs +++ b/internal/src/pinned_drop.rs @@ -1,9 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -#[cfg(not(kernel))] -use proc_macro2 as proc_macro; - -use proc_macro::{TokenStream, TokenTree}; +use proc_macro2::{TokenStream, TokenTree}; +use quote::quote; pub(crate) fn pinned_drop(_args: TokenStream, input: TokenStream) -> TokenStream { let mut toks = input.into_iter().collect::>(); diff --git a/internal/src/zeroable.rs b/internal/src/zeroable.rs index e0ed3998..d8a5ef38 100644 --- a/internal/src/zeroable.rs +++ b/internal/src/zeroable.rs @@ -1,10 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 -#[cfg(not(kernel))] -use proc_macro2 as proc_macro; - use crate::helpers::{parse_generics, Generics}; -use proc_macro::{TokenStream, TokenTree}; +use proc_macro2::{TokenStream, TokenTree}; +use quote::quote; pub(crate) fn parse_zeroable_derive_input( input: TokenStream, From b4dcb1a6851aa0053e8d79982b17070a9673744b Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Sat, 11 Oct 2025 14:33:19 +0200 Subject: [PATCH 09/14] rewrite `derive(Zeroable)` and `derive(MaybeZeroable)` using `syn` Rewrite the two derive macros for `Zeroable` using `syn`. One positive side effect of this change is that tuple structs are now supported by them. Additionally, syntax errors and the error emitted when trying to use one of the derive macros on an `enum` are improved. Otherwise no functional changes intended. For example: #[derive(Zeroable)] enum Num { A(u32), B(i32), } Produced this error before this commit: error: no rules expected keyword `enum` --> tests/ui/compile-fail/zeroable/enum.rs:5:1 | 5 | enum Num { | ^^^^ no rules expected this token in macro call | note: while trying to match keyword `struct` --> src/macros.rs | | $vis:vis struct $name:ident | ^^^^^^ Now the error is: error: cannot derive `Zeroable` for an enum --> tests/ui/compile-fail/zeroable/enum.rs:5:1 | 5 | enum Num { | ^^^^ error: cannot derive `Zeroable` for an enum Signed-off-by: Benno Lossin --- CHANGELOG.md | 1 + internal/src/lib.rs | 5 +- internal/src/zeroable.rs | 146 +++++++----------- src/macros.rs | 124 --------------- tests/ui/compile-fail/zeroable/enum.stderr | 20 +-- .../zeroable/not_all_fields_zeroable.stderr | 2 +- tests/ui/expand/zeroable.expanded.rs | 4 +- 7 files changed, 70 insertions(+), 232 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 137cf92a..7635c648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 code at that point. - Make the `[try_][pin_]init!` macros expose initialized fields via a `let` binding as `&mut T` or `Pin<&mut T>` for later fields. +- `derive([Maybe]Zeroable)` now support tuple structs ## [0.0.10] - 2025-08-19 diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 4c4dc639..ec593362 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -11,6 +11,7 @@ #![allow(missing_docs)] use proc_macro::TokenStream; +use syn::parse_macro_input; mod helpers; mod pin_data; @@ -29,10 +30,10 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_derive(Zeroable)] pub fn derive_zeroable(input: TokenStream) -> TokenStream { - zeroable::derive(input.into()).into() + zeroable::derive(parse_macro_input!(input as _)).into() } #[proc_macro_derive(MaybeZeroable)] pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream { - zeroable::maybe_derive(input.into()).into() + zeroable::maybe_derive(parse_macro_input!(input as _)).into() } diff --git a/internal/src/zeroable.rs b/internal/src/zeroable.rs index d8a5ef38..160fb954 100644 --- a/internal/src/zeroable.rs +++ b/internal/src/zeroable.rs @@ -1,99 +1,71 @@ // SPDX-License-Identifier: GPL-2.0 -use crate::helpers::{parse_generics, Generics}; -use proc_macro2::{TokenStream, TokenTree}; -use quote::quote; +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{parse_quote, Data, DeriveInput, Field, Fields}; -pub(crate) fn parse_zeroable_derive_input( - input: TokenStream, -) -> ( - Vec, - Vec, - Vec, - Option, -) { - let ( - Generics { - impl_generics, - decl_generics: _, - ty_generics, - }, - mut rest, - ) = parse_generics(input); - // This should be the body of the struct `{...}`. - let last = rest.pop(); - // Now we insert `Zeroable` as a bound for every generic parameter in `impl_generics`. - let mut new_impl_generics = Vec::with_capacity(impl_generics.len()); - // Are we inside of a generic where we want to add `Zeroable`? - let mut in_generic = !impl_generics.is_empty(); - // Have we already inserted `Zeroable`? - let mut inserted = false; - // Level of `<>` nestings. - let mut nested = 0; - for tt in impl_generics { - match &tt { - // If we find a `,`, then we have finished a generic/constant/lifetime parameter. - TokenTree::Punct(p) if nested == 0 && p.as_char() == ',' => { - if in_generic && !inserted { - new_impl_generics.extend(quote! { : ::pin_init::Zeroable }); - } - in_generic = true; - inserted = false; - new_impl_generics.push(tt); - } - // If we find `'`, then we are entering a lifetime. - TokenTree::Punct(p) if nested == 0 && p.as_char() == '\'' => { - in_generic = false; - new_impl_generics.push(tt); - } - TokenTree::Punct(p) if nested == 0 && p.as_char() == ':' => { - new_impl_generics.push(tt); - if in_generic { - new_impl_generics.extend(quote! { ::pin_init::Zeroable + }); - inserted = true; - } - } - TokenTree::Punct(p) if p.as_char() == '<' => { - nested += 1; - new_impl_generics.push(tt); - } - TokenTree::Punct(p) if p.as_char() == '>' => { - assert!(nested > 0); - nested -= 1; - new_impl_generics.push(tt); - } - _ => new_impl_generics.push(tt), +pub(crate) fn derive(input: DeriveInput) -> TokenStream { + let fields = match input.data { + Data::Struct(data_struct) => data_struct.fields, + Data::Union(data_union) => Fields::Named(data_union.fields), + Data::Enum(data_enum) => { + return quote_spanned! {data_enum.enum_token.span=> + ::core::compile_error!("cannot derive `Zeroable` for an enum"); + }; } + }; + let name = input.ident; + let mut generics = input.generics; + for param in generics.type_params_mut() { + param.bounds.push(parse_quote!(::pin_init::Zeroable)); } - assert_eq!(nested, 0); - if in_generic && !inserted { - new_impl_generics.extend(quote! { : ::pin_init::Zeroable }); - } - (rest, new_impl_generics, ty_generics, last) -} - -pub(crate) fn derive(input: TokenStream) -> TokenStream { - let (rest, new_impl_generics, ty_generics, last) = parse_zeroable_derive_input(input); + let (impl_gen, ty_gen, whr) = generics.split_for_impl(); + let field_type = fields.iter().map(|field| &field.ty); quote! { - ::pin_init::__derive_zeroable!( - parse_input: - @sig(#(#rest)*), - @impl_generics(#(#new_impl_generics)*), - @ty_generics(#(#ty_generics)*), - @body(#last), - ); + // SAFETY: Every field type implements `Zeroable` and padding bytes may be zero. + #[automatically_derived] + unsafe impl #impl_gen ::pin_init::Zeroable for #name #ty_gen + #whr + {} + const _: () = { + fn assert_zeroable() {} + fn ensure_zeroable #impl_gen () + #whr + { + #( + assert_zeroable::<#field_type>(); + )* + } + }; } } -pub(crate) fn maybe_derive(input: TokenStream) -> TokenStream { - let (rest, new_impl_generics, ty_generics, last) = parse_zeroable_derive_input(input); +pub(crate) fn maybe_derive(input: DeriveInput) -> TokenStream { + let fields = match input.data { + Data::Struct(data_struct) => data_struct.fields, + Data::Union(data_union) => Fields::Named(data_union.fields), + Data::Enum(data_enum) => { + return quote_spanned! {data_enum.enum_token.span=> + compile_error!("cannot derive `Zeroable` for an enum"); + }; + } + }; + let name = input.ident; + let mut generics = input.generics; + for Field { ty, .. } in fields { + generics + .make_where_clause() + .predicates + // the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds` + // feature . + .push(parse_quote!(#ty: for<'__dummy> ::pin_init::Zeroable)); + } + let (impl_gen, ty_gen, whr) = generics.split_for_impl(); quote! { - ::pin_init::__maybe_derive_zeroable!( - parse_input: - @sig(#(#rest)*), - @impl_generics(#(#new_impl_generics)*), - @ty_generics(#(#ty_generics)*), - @body(#last), - ); + // SAFETY: Every field type implements `Zeroable` and padding bytes may be zero. + #[automatically_derived] + unsafe impl #impl_gen ::pin_init::Zeroable for #name #ty_gen + #whr + {} } } diff --git a/src/macros.rs b/src/macros.rs index 682c61a5..53ed5ce8 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1551,127 +1551,3 @@ macro_rules! __init_internal { ); }; } - -#[doc(hidden)] -#[macro_export] -macro_rules! __derive_zeroable { - (parse_input: - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis struct $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @body({ - $( - $(#[$($field_attr:tt)*])* - $field_vis:vis $field:ident : $field_ty:ty - ),* $(,)? - }), - ) => { - // SAFETY: Every field type implements `Zeroable` and padding bytes may be zero. - #[automatically_derived] - unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*> - where - $($($whr)*)? - {} - const _: () = { - fn assert_zeroable() {} - fn ensure_zeroable<$($impl_generics)*>() - where $($($whr)*)? - { - $(assert_zeroable::<$field_ty>();)* - } - }; - }; - (parse_input: - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis union $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @body({ - $( - $(#[$($field_attr:tt)*])* - $field_vis:vis $field:ident : $field_ty:ty - ),* $(,)? - }), - ) => { - // SAFETY: Every field type implements `Zeroable` and padding bytes may be zero. - #[automatically_derived] - unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*> - where - $($($whr)*)? - {} - const _: () = { - fn assert_zeroable() {} - fn ensure_zeroable<$($impl_generics)*>() - where $($($whr)*)? - { - $(assert_zeroable::<$field_ty>();)* - } - }; - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __maybe_derive_zeroable { - (parse_input: - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis struct $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @body({ - $( - $(#[$($field_attr:tt)*])* - $field_vis:vis $field:ident : $field_ty:ty - ),* $(,)? - }), - ) => { - // SAFETY: Every field type implements `Zeroable` and padding bytes may be zero. - #[automatically_derived] - unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*> - where - $( - // the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds` - // feature . - $field_ty: for<'__dummy> $crate::Zeroable, - )* - $($($whr)*)? - {} - }; - (parse_input: - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis union $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @body({ - $( - $(#[$($field_attr:tt)*])* - $field_vis:vis $field:ident : $field_ty:ty - ),* $(,)? - }), - ) => { - // SAFETY: Every field type implements `Zeroable` and padding bytes may be zero. - #[automatically_derived] - unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*> - where - $( - // the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds` - // feature . - $field_ty: for<'__dummy> $crate::Zeroable, - )* - $($($whr)*)? - {} - }; -} diff --git a/tests/ui/compile-fail/zeroable/enum.stderr b/tests/ui/compile-fail/zeroable/enum.stderr index a9d25903..bc07203b 100644 --- a/tests/ui/compile-fail/zeroable/enum.stderr +++ b/tests/ui/compile-fail/zeroable/enum.stderr @@ -1,23 +1,11 @@ -error: no rules expected keyword `enum` +error: cannot derive `Zeroable` for an enum --> tests/ui/compile-fail/zeroable/enum.rs:5:1 | 5 | enum Num { - | ^^^^ no rules expected this token in macro call - | -note: while trying to match keyword `struct` - --> src/macros.rs - | - | $vis:vis struct $name:ident - | ^^^^^^ + | ^^^^ -error: no rules expected keyword `enum` +error: cannot derive `Zeroable` for an enum --> tests/ui/compile-fail/zeroable/enum.rs:11:1 | 11 | enum Num2 { - | ^^^^ no rules expected this token in macro call - | -note: while trying to match keyword `struct` - --> src/macros.rs - | - | $vis:vis struct $name:ident - | ^^^^^^ + | ^^^^ diff --git a/tests/ui/compile-fail/zeroable/not_all_fields_zeroable.stderr b/tests/ui/compile-fail/zeroable/not_all_fields_zeroable.stderr index 67b63241..aa54e9c8 100644 --- a/tests/ui/compile-fail/zeroable/not_all_fields_zeroable.stderr +++ b/tests/ui/compile-fail/zeroable/not_all_fields_zeroable.stderr @@ -9,7 +9,7 @@ note: required by a bound in `assert_zeroable` | 4 | #[derive(Zeroable)] | ^^^^^^^^ required by this bound in `assert_zeroable` - = note: this error originates in the macro `::pin_init::__derive_zeroable` which comes from the expansion of the derive macro `Zeroable` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Zeroable` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider removing the leading `&`-reference | 7 - b: &'static Foo, diff --git a/tests/ui/expand/zeroable.expanded.rs b/tests/ui/expand/zeroable.expanded.rs index 4f9d174c..08ad36fa 100644 --- a/tests/ui/expand/zeroable.expanded.rs +++ b/tests/ui/expand/zeroable.expanded.rs @@ -31,11 +31,11 @@ struct WithGenerics<'a, T, U: Trait> { unsafe impl< 'a, T: ::pin_init::Zeroable, - U: ::pin_init::Zeroable + Trait, + U: Trait + ::pin_init::Zeroable, > ::pin_init::Zeroable for WithGenerics<'a, T, U> {} const _: () = { fn assert_zeroable() {} - fn ensure_zeroable<'a, T: ::pin_init::Zeroable, U: ::pin_init::Zeroable + Trait>() { + fn ensure_zeroable<'a, T: ::pin_init::Zeroable, U: Trait + ::pin_init::Zeroable>() { assert_zeroable::(); assert_zeroable::<&'a U>(); } From 623e8d4d48a2fd181f9c760cdf1ad08f35760e15 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Sat, 11 Oct 2025 15:24:52 +0200 Subject: [PATCH 10/14] rewrite the `#[pinned_drop]` attribute macro using `syn` Rewrite the attribute macro for implementing `PinnedDrop` using `syn`. Otherwise no functional changes intended aside from improved error messages on syntactic and semantical errors. For example: When missing the `drop` function in the implementation, the old error was: error: no rules expected `)` --> tests/ui/compile-fail/pinned_drop/no_fn.rs:6:1 | 6 | #[pinned_drop] | ^^^^^^^^^^^^^^ no rules expected this token in macro call | note: while trying to match keyword `fn` --> src/macros.rs | | fn drop($($sig:tt)*) { | ^^ = note: this error originates in the attribute macro `pinned_drop` (in Nightly builds, run with -Z macro-backtrace for more info) And the new one is: error[E0046]: not all trait items implemented, missing: `drop` --> tests/ui/compile-fail/pinned_drop/no_fn.rs:7:1 | 7 | impl PinnedDrop for Foo {} | ^^^^^^^^^^^^^^^^^^^^^^^ missing `drop` in implementation | = help: implement the missing item: `fn drop(self: Pin<&mut Self>, _: OnlyCallFromDrop) { todo!() }` Signed-off-by: Benno Lossin --- internal/src/lib.rs | 6 +- internal/src/pinned_drop.rs | 87 ++++++++++--------- src/macros.rs | 28 ------ .../compile-fail/init/wrong_generics2.stderr | 2 +- .../ui/compile-fail/pinned_drop/no_fn.stderr | 15 ++-- .../pinned_drop/unexpected_additional_item.rs | 1 + .../unexpected_additional_item.stderr | 14 +-- .../pinned_drop/unexpected_generics.stderr | 14 +-- .../pinned_drop/unexpected_item.stderr | 14 +-- 9 files changed, 74 insertions(+), 107 deletions(-) diff --git a/internal/src/lib.rs b/internal/src/lib.rs index ec593362..d0156b82 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -25,7 +25,11 @@ pub fn pin_data(inner: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream { - pinned_drop::pinned_drop(args.into(), input.into()).into() + pinned_drop::pinned_drop( + parse_macro_input!(args as _), + parse_macro_input!(input as _), + ) + .into() } #[proc_macro_derive(Zeroable)] diff --git a/internal/src/pinned_drop.rs b/internal/src/pinned_drop.rs index 978c2594..4df2cb99 100644 --- a/internal/src/pinned_drop.rs +++ b/internal/src/pinned_drop.rs @@ -1,49 +1,56 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -use proc_macro2::{TokenStream, TokenTree}; -use quote::quote; +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{parse::Nothing, parse_quote, spanned::Spanned, ImplItem, ItemImpl, Token}; -pub(crate) fn pinned_drop(_args: TokenStream, input: TokenStream) -> TokenStream { - let mut toks = input.into_iter().collect::>(); - assert!(!toks.is_empty()); - // Ensure that we have an `impl` item. - assert!(matches!(&toks[0], TokenTree::Ident(i) if i.to_string() == "impl")); - // Ensure that we are implementing `PinnedDrop`. - let mut nesting: usize = 0; - let mut pinned_drop_idx = None; - for (i, tt) in toks.iter().enumerate() { - match tt { - TokenTree::Punct(p) if p.as_char() == '<' => { - nesting += 1; +pub(crate) fn pinned_drop(_args: Nothing, mut input: ItemImpl) -> TokenStream { + let mut errors = vec![]; + if let Some(unsafety) = input.unsafety { + errors.push(quote_spanned! {unsafety.span=> + ::core::compile_error!("implementing `PinnedDrop` is safe"); + }); + } + input.unsafety = Some(Token![unsafe](input.impl_token.span)); + match &mut input.trait_ { + Some((not, path, _for)) => { + if let Some(not) = not { + errors.push(quote_spanned! {not.span=> + ::core::compile_error!("cannot implement `!PinnedDrop`"); + }); } - TokenTree::Punct(p) if p.as_char() == '>' => { - nesting = nesting.checked_sub(1).unwrap(); - continue; + for (seg, expected) in path + .segments + .iter() + .rev() + .zip(["PinnedDrop", "pin_init", ""]) + { + if expected.is_empty() || seg.ident != expected { + errors.push(quote_spanned! {seg.span()=> + ::core::compile_error!("bad import path for `PinnedDrop`"); + }); + } + if !seg.arguments.is_none() { + errors.push(quote_spanned! {seg.arguments.span()=> + ::core::compile_error!("unexpected arguments for `PinnedDrop` path"); + }); + } } - _ => {} - } - if i >= 1 && nesting == 0 { - // Found the end of the generics, this should be `PinnedDrop`. - assert!( - matches!(tt, TokenTree::Ident(i) if i.to_string() == "PinnedDrop"), - "expected 'PinnedDrop', found: '{tt:?}'" - ); - pinned_drop_idx = Some(i); - break; + *path = parse_quote!(::pin_init::PinnedDrop); } + None => errors.push(quote_spanned! {input.impl_token.span=> + ::core::compile_error!("expected `impl ... PinnedDrop for ...`, got inherent impl"); + }), } - let idx = pinned_drop_idx - .unwrap_or_else(|| panic!("Expected an `impl` block implementing `PinnedDrop`.")); - // Fully qualify the `PinnedDrop`, as to avoid any tampering. - toks.splice(idx..idx, quote!(::pin_init::)); - // Take the `{}` body and call the declarative macro. - if let Some(TokenTree::Group(last)) = toks.pop() { - let last = last.stream(); - quote!(::pin_init::__pinned_drop! { - @impl_sig(#(#toks)*), - @impl_body(#last), - }) - } else { - TokenStream::from_iter(toks) + for item in &mut input.items { + if let ImplItem::Fn(fn_item) = item { + if fn_item.sig.ident == "drop" { + fn_item + .sig + .inputs + .push(parse_quote!(_: ::pin_init::__internal::OnlyCallFromDrop)); + } + } } + quote!(#(#errors)* #input) } diff --git a/src/macros.rs b/src/macros.rs index 53ed5ce8..b80c9561 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -503,34 +503,6 @@ pub use ::macros::paste; #[cfg(not(kernel))] pub use ::paste::paste; -/// Creates a `unsafe impl<...> PinnedDrop for $type` block. -/// -/// See [`PinnedDrop`] for more information. -/// -/// [`PinnedDrop`]: crate::PinnedDrop -#[doc(hidden)] -#[macro_export] -macro_rules! __pinned_drop { - ( - @impl_sig($($impl_sig:tt)*), - @impl_body( - $(#[$($attr:tt)*])* - fn drop($($sig:tt)*) { - $($inner:tt)* - } - ), - ) => { - // SAFETY: TODO. - unsafe $($impl_sig)* { - // Inherit all attributes and the type/ident tokens for the signature. - $(#[$($attr)*])* - fn drop($($sig)*, _: $crate::__internal::OnlyCallFromDrop) { - $($inner)* - } - } - } -} - /// This macro first parses the struct definition such that it separates pinned and not pinned /// fields. Afterwards it declares the struct and implement the `PinData` trait safely. #[doc(hidden)] diff --git a/tests/ui/compile-fail/init/wrong_generics2.stderr b/tests/ui/compile-fail/init/wrong_generics2.stderr index f45201eb..dbc9c342 100644 --- a/tests/ui/compile-fail/init/wrong_generics2.stderr +++ b/tests/ui/compile-fail/init/wrong_generics2.stderr @@ -12,7 +12,7 @@ help: you might have forgotten to add the struct literal inside the block --> src/macros.rs | ~ ::core::ptr::write($slot, $t { SomeStruct { - |2 $($acc)* + |4 $($acc)* ~ } }); | diff --git a/tests/ui/compile-fail/pinned_drop/no_fn.stderr b/tests/ui/compile-fail/pinned_drop/no_fn.stderr index fdef4b87..f82b3006 100644 --- a/tests/ui/compile-fail/pinned_drop/no_fn.stderr +++ b/tests/ui/compile-fail/pinned_drop/no_fn.stderr @@ -1,12 +1,7 @@ -error: no rules expected `)` - --> tests/ui/compile-fail/pinned_drop/no_fn.rs:6:1 +error[E0046]: not all trait items implemented, missing: `drop` + --> tests/ui/compile-fail/pinned_drop/no_fn.rs:7:1 | -6 | #[pinned_drop] - | ^^^^^^^^^^^^^^ no rules expected this token in macro call +7 | impl PinnedDrop for Foo {} + | ^^^^^^^^^^^^^^^^^^^^^^^ missing `drop` in implementation | -note: while trying to match keyword `fn` - --> src/macros.rs - | - | fn drop($($sig:tt)*) { - | ^^ - = note: this error originates in the attribute macro `pinned_drop` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: implement the missing item: `fn drop(self: Pin<&mut Self>, _: OnlyCallFromDrop) { todo!() }` diff --git a/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.rs b/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.rs index 851c4cff..d039fde1 100644 --- a/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.rs +++ b/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.rs @@ -1,4 +1,5 @@ use pin_init::*; +use std::pin::Pin; #[pin_data(PinnedDrop)] struct Foo {} diff --git a/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.stderr b/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.stderr index c2cfe925..cf632876 100644 --- a/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.stderr +++ b/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.stderr @@ -1,11 +1,5 @@ -error: no rules expected keyword `const` - --> tests/ui/compile-fail/pinned_drop/unexpected_additional_item.rs:10:5 +error[E0438]: const `BAZ` is not a member of trait `pin_init::PinnedDrop` + --> tests/ui/compile-fail/pinned_drop/unexpected_additional_item.rs:11:5 | -10 | const BAZ: usize = 0; - | ^^^^^ no rules expected this token in macro call - | -note: while trying to match `)` - --> src/macros.rs - | - | ), - | ^ +11 | const BAZ: usize = 0; + | ^^^^^^^^^^^^^^^^^^^^^ not a member of trait `pin_init::PinnedDrop` diff --git a/tests/ui/compile-fail/pinned_drop/unexpected_generics.stderr b/tests/ui/compile-fail/pinned_drop/unexpected_generics.stderr index 194ae81a..cf94d8ea 100644 --- a/tests/ui/compile-fail/pinned_drop/unexpected_generics.stderr +++ b/tests/ui/compile-fail/pinned_drop/unexpected_generics.stderr @@ -1,13 +1,5 @@ -error[E0107]: trait takes 0 generic arguments but 1 generic argument was supplied - --> tests/ui/compile-fail/pinned_drop/unexpected_generics.rs:10:9 +error: unexpected arguments for `PinnedDrop` path + --> tests/ui/compile-fail/pinned_drop/unexpected_generics.rs:10:19 | 10 | impl PinnedDrop for Foo { - | ^^^^^^^^^^--- help: remove the unnecessary generics - | | - | expected 0 generic arguments - | -note: trait defined here, with 0 generic parameters - --> src/lib.rs - | - | pub unsafe trait PinnedDrop: __internal::HasPinData { - | ^^^^^^^^^^ + | ^^^ diff --git a/tests/ui/compile-fail/pinned_drop/unexpected_item.stderr b/tests/ui/compile-fail/pinned_drop/unexpected_item.stderr index 5f63ad8f..6f564a2b 100644 --- a/tests/ui/compile-fail/pinned_drop/unexpected_item.stderr +++ b/tests/ui/compile-fail/pinned_drop/unexpected_item.stderr @@ -1,11 +1,13 @@ -error: no rules expected keyword `const` +error[E0438]: const `BAZ` is not a member of trait `pin_init::PinnedDrop` --> tests/ui/compile-fail/pinned_drop/unexpected_item.rs:8:5 | 8 | const BAZ: usize = 0; - | ^^^^^ no rules expected this token in macro call + | ^^^^^^^^^^^^^^^^^^^^^ not a member of trait `pin_init::PinnedDrop` + +error[E0046]: not all trait items implemented, missing: `drop` + --> tests/ui/compile-fail/pinned_drop/unexpected_item.rs:7:1 | -note: while trying to match keyword `fn` - --> src/macros.rs +7 | impl PinnedDrop for Foo { + | ^^^^^^^^^^^^^^^^^^^^^^^ missing `drop` in implementation | - | fn drop($($sig:tt)*) { - | ^^ + = help: implement the missing item: `fn drop(self: Pin<&mut Self>, _: OnlyCallFromDrop) { todo!() }` From 49cbd6b87fcf35e30feb76998ab6dc1c1fff5e0d Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Mon, 13 Oct 2025 01:01:17 +0200 Subject: [PATCH 11/14] rewrite `#[pin_data]` using `syn` Rewrite the attribute macro `#[pin_data]` using `syn`. No functional changes intended aside from improved error messages on syntactic and semantical errors. For example if one forgets a comma at the end of a field: #[pin_data] struct Foo { a: Box b: Box } The declarative macro reports the following errors: error: expected `,`, or `}`, found `b` --> tests/ui/compile-fail/pin_data/missing_comma.rs:5:16 | 5 | a: Box | ^ help: try adding a comma: `,` error: recursion limit reached while expanding `$crate::__pin_data!` --> tests/ui/compile-fail/pin_data/missing_comma.rs:3:1 | 3 | #[pin_data] | ^^^^^^^^^^^ | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`) = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) The new `syn` version reports: error: expected `,`, or `}`, found `b` --> tests/ui/compile-fail/pin_data/missing_comma.rs:5:16 | 5 | a: Box | ^ help: try adding a comma: `,` error: expected `,` --> tests/ui/compile-fail/pin_data/missing_comma.rs:6:5 | 6 | b: Box | ^ Signed-off-by: Benno Lossin --- internal/src/helpers.rs | 149 ---- internal/src/lib.rs | 9 +- internal/src/pin_data.rs | 634 ++++++++++++++---- src/macros.rs | 574 ---------------- tests/many_generics.rs | 1 + .../compile-fail/init/wrong_generics2.stderr | 2 +- .../pin_data/missing_comma.stderr | 15 +- .../compile-fail/pin_data/missing_pin.stderr | 2 +- .../pin_data/no_pin_on_phantompinned.stderr | 55 +- .../pin_data/pin_data_but_drop.stderr | 2 +- tests/ui/compile-fail/pin_data/twice.stderr | 56 +- .../pin_data/unexpected_args.stderr | 16 +- .../pin_data/unexpected_item.stderr | 14 +- .../pinned_drop/useless_pinned_drop.stderr | 2 +- tests/ui/expand/many_generics.expanded.rs | 67 +- tests/ui/expand/pin-data.expanded.rs | 53 +- tests/ui/expand/pinned_drop.expanded.rs | 53 +- 17 files changed, 724 insertions(+), 980 deletions(-) delete mode 100644 internal/src/helpers.rs diff --git a/internal/src/helpers.rs b/internal/src/helpers.rs deleted file mode 100644 index 1d2dd27c..00000000 --- a/internal/src/helpers.rs +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR MIT - -use proc_macro2::{TokenStream, TokenTree}; - -/// Parsed generics. -/// -/// See the field documentation for an explanation what each of the fields represents. -/// -/// # Examples -/// -/// ```rust,ignore -/// # let input = todo!(); -/// let (Generics { decl_generics, impl_generics, ty_generics }, rest) = parse_generics(input); -/// quote! { -/// struct Foo<$($decl_generics)*> { -/// // ... -/// } -/// -/// impl<$impl_generics> Foo<$ty_generics> { -/// fn foo() { -/// // ... -/// } -/// } -/// } -/// ``` -pub(crate) struct Generics { - /// The generics with bounds and default values (e.g. `T: Clone, const N: usize = 0`). - /// - /// Use this on type definitions e.g. `struct Foo<$decl_generics> ...` (or `union`/`enum`). - pub(crate) decl_generics: Vec, - /// The generics with bounds (e.g. `T: Clone, const N: usize`). - /// - /// Use this on `impl` blocks e.g. `impl<$impl_generics> Trait for ...`. - pub(crate) impl_generics: Vec, - /// The generics without bounds and without default values (e.g. `T, N`). - /// - /// Use this when you use the type that is declared with these generics e.g. - /// `Foo<$ty_generics>`. - pub(crate) ty_generics: Vec, -} - -/// Parses the given `TokenStream` into `Generics` and the rest. -/// -/// The generics are not present in the rest, but a where clause might remain. -pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec) { - // The generics with bounds and default values. - let mut decl_generics = vec![]; - // `impl_generics`, the declared generics with their bounds. - let mut impl_generics = vec![]; - // Only the names of the generics, without any bounds. - let mut ty_generics = vec![]; - // Tokens not related to the generics e.g. the `where` token and definition. - let mut rest = vec![]; - // The current level of `<`. - let mut nesting = 0; - let mut toks = input.into_iter(); - // If we are at the beginning of a generic parameter. - let mut at_start = true; - let mut skip_until_comma = false; - while let Some(tt) = toks.next() { - if nesting == 1 && matches!(&tt, TokenTree::Punct(p) if p.as_char() == '>') { - // Found the end of the generics. - break; - } else if nesting >= 1 { - decl_generics.push(tt.clone()); - } - match tt.clone() { - TokenTree::Punct(p) if p.as_char() == '<' => { - if nesting >= 1 && !skip_until_comma { - // This is inside of the generics and part of some bound. - impl_generics.push(tt); - } - nesting += 1; - } - TokenTree::Punct(p) if p.as_char() == '>' => { - // This is a parsing error, so we just end it here. - if nesting == 0 { - break; - } else { - nesting -= 1; - if nesting >= 1 && !skip_until_comma { - // We are still inside of the generics and part of some bound. - impl_generics.push(tt); - } - } - } - TokenTree::Punct(p) if skip_until_comma && p.as_char() == ',' => { - if nesting == 1 { - impl_generics.push(tt.clone()); - impl_generics.push(tt); - skip_until_comma = false; - } - } - _ if !skip_until_comma => { - match nesting { - // If we haven't entered the generics yet, we still want to keep these tokens. - 0 => rest.push(tt), - 1 => { - // Here depending on the token, it might be a generic variable name. - match tt.clone() { - TokenTree::Ident(i) if at_start && i.to_string() == "const" => { - let Some(name) = toks.next() else { - // Parsing error. - break; - }; - impl_generics.push(tt); - impl_generics.push(name.clone()); - ty_generics.push(name.clone()); - decl_generics.push(name); - at_start = false; - } - TokenTree::Ident(_) if at_start => { - impl_generics.push(tt.clone()); - ty_generics.push(tt); - at_start = false; - } - TokenTree::Punct(p) if p.as_char() == ',' => { - impl_generics.push(tt.clone()); - ty_generics.push(tt); - at_start = true; - } - // Lifetimes begin with `'`. - TokenTree::Punct(p) if p.as_char() == '\'' && at_start => { - impl_generics.push(tt.clone()); - ty_generics.push(tt); - } - // Generics can have default values, we skip these. - TokenTree::Punct(p) if p.as_char() == '=' => { - skip_until_comma = true; - } - _ => impl_generics.push(tt), - } - } - _ => impl_generics.push(tt), - } - } - _ => {} - } - } - rest.extend(toks); - ( - Generics { - impl_generics, - decl_generics, - ty_generics, - }, - rest, - ) -} diff --git a/internal/src/lib.rs b/internal/src/lib.rs index d0156b82..828f18db 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -13,14 +13,17 @@ use proc_macro::TokenStream; use syn::parse_macro_input; -mod helpers; mod pin_data; mod pinned_drop; mod zeroable; #[proc_macro_attribute] -pub fn pin_data(inner: TokenStream, item: TokenStream) -> TokenStream { - pin_data::pin_data(inner.into(), item.into()).into() +pub fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream { + pin_data::pin_data( + parse_macro_input!(args as _), + parse_macro_input!(input as _), + ) + .into() } #[proc_macro_attribute] diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 12d9b02c..e57b766d 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -1,130 +1,534 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::helpers::{parse_generics, Generics}; -use proc_macro2::{Group, Punct, Spacing, TokenStream, TokenTree}; -use quote::quote; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, quote_spanned}; +use syn::{ + parse::{End, Nothing, Parse}, + parse_quote, parse_quote_spanned, + spanned::Spanned, + visit_mut::VisitMut, + Field, Ident, Item, ItemStruct, PathSegment, Type, TypePath, WhereClause, +}; -pub(crate) fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream { - // This proc-macro only does some pre-parsing and then delegates the actual parsing to - // `pin_init::__pin_data!`. +pub(crate) mod kw { + syn::custom_keyword!(PinnedDrop); +} + +pub(crate) enum Args { + Nothing(Nothing), + #[allow(dead_code)] + PinnedDrop(kw::PinnedDrop), +} + +impl Parse for Args { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lh = input.lookahead1(); + if lh.peek(End) { + input.parse().map(Self::Nothing) + } else if lh.peek(kw::PinnedDrop) { + let res = input.parse().map(Self::PinnedDrop)?; + let lh = input.lookahead1(); + if lh.peek(End) { + Ok(res) + } else { + Err(lh.error()) + } + } else { + Err(lh.error()) + } + } +} + +pub(crate) fn pin_data(args: Args, input: Item) -> TokenStream { + let mut struct_ = match input { + Item::Struct(struct_) => struct_, + Item::Enum(_) | Item::Union(_) => { + return quote! { + ::core::compile_error!("`#[pin_data]` for now only supports structs"); + #input + }; + } + _ => { + return quote! { + ::core::compile_error!( + "`#[pin_data]` can only be applied to struct, enum and union defintions" + ); + #input + }; + } + }; + + // The generics might contain the `Self` type. Since this macro will define a new type with the + // same generics and bounds, this poses a problem: `Self` will refer to the new type as opposed + // to this struct definition. Therefore we have to replace `Self` with the concrete name. + let mut replacer = { + let name = &struct_.ident; + let (_, ty_generics, _) = struct_.generics.split_for_impl(); + SelfReplacer(parse_quote!(#name #ty_generics)) + }; + replacer.visit_generics_mut(&mut struct_.generics); + replacer.visit_fields_mut(&mut struct_.fields); + + let mut errors = TokenStream::new(); + for field in &struct_.fields { + if !is_field_structurally_pinned(field) && is_phantom_pinned(&field.ty) { + let message = format!( + "The field `{}` of type `PhantomData` only has an effect \ + if it has the `#[pin]` attribute", + field.ident.as_ref().expect(""), + ); + errors.extend(quote_spanned!(field.span()=> ::core::compile_error!(#message);)); + } + } + + let unpin_impl = generate_unpin_impl(&struct_); + let drop_impl = generate_drop_impl(&struct_, args); + let projections = generate_projections(&struct_); + let the_pin_data = generate_the_pin_data(&struct_); + + strip_pin_annotations(&mut struct_); + + quote! { + #struct_ + #projections + // We put the rest into this const item, because it then will not be accessible to anything + // outside. + const _: () = { + #the_pin_data + #unpin_impl + #drop_impl + }; + #errors + } +} + +fn is_phantom_pinned(ty: &Type) -> bool { + match ty { + Type::Path(TypePath { qself: None, path }) => { + // Cannot possibly refer to `PhantomPinned` (except alias, but that's on the user). + if path.segments.len() > 3 { + return false; + } + // If there is a `::`, then the path needs to be `::core::marker::PhantomPinned` or + // `::std::marker::PhantomPinned`. + if path.leading_colon.is_some() && path.segments.len() != 3 { + return false; + } + let expected: Vec<&[&str]> = vec![&["PhantomPinned"], &["marker"], &["core", "std"]]; + for (actual, expected) in path.segments.iter().rev().zip(expected) { + if !actual.arguments.is_empty() || expected.iter().all(|e| actual.ident != e) { + return false; + } + } + true + } + _ => false, + } +} +fn is_field_structurally_pinned(field: &Field) -> bool { + field.attrs.iter().any(|a| a.path().is_ident("pin")) +} + +fn generate_unpin_impl( + ItemStruct { + ident, + generics, + fields, + .. + }: &ItemStruct, +) -> TokenStream { + let generics_with_pinlt = { + let mut g = generics.clone(); + g.params.insert(0, parse_quote!('__pin)); + let _ = g.make_where_clause(); + g + }; let ( - Generics { - impl_generics, - decl_generics, - ty_generics, - }, - rest, - ) = parse_generics(input); - // The struct definition might contain the `Self` type. Since `__pin_data!` will define a new - // type with the same generics and bounds, this poses a problem, since `Self` will refer to the - // new type as opposed to this struct definition. Therefore we have to replace `Self` with the - // concrete name. - - // Errors that occur when replacing `Self` with `struct_name`. - let mut errs = TokenStream::new(); - // The name of the struct with ty_generics. - let struct_name = rest + impl_generics_with_pinlt, + ty_generics_with_pinlt, + Some(WhereClause { + where_token, + predicates, + }), + ) = generics_with_pinlt.split_for_impl() + else { + unreachable!() + }; + let (_, ty_generics, _) = generics.split_for_impl(); + let mut pinned_fields = fields .iter() - .skip_while(|tt| !matches!(tt, TokenTree::Ident(i) if i.to_string() == "struct")) - .nth(1) - .and_then(|tt| match tt { - TokenTree::Ident(_) => { - let tt = tt.clone(); - let mut res = vec![tt]; - if !ty_generics.is_empty() { - // We add this, so it is maximally compatible with e.g. `Self::CONST` which - // will be replaced by `StructName::<$generics>::CONST`. - res.push(TokenTree::Punct(Punct::new(':', Spacing::Joint))); - res.push(TokenTree::Punct(Punct::new(':', Spacing::Alone))); - res.push(TokenTree::Punct(Punct::new('<', Spacing::Alone))); - res.extend(ty_generics.iter().cloned()); - res.push(TokenTree::Punct(Punct::new('>', Spacing::Alone))); + .filter(|f| is_field_structurally_pinned(f)) + .cloned() + .collect::>(); + for field in &mut pinned_fields { + field.attrs.retain(|a| !a.path().is_ident("pin")); + } + quote! { + // This struct will be used for the unpin analysis. It is needed, because only structurally + // pinned fields are relevant whether the struct should implement `Unpin`. + #[allow(dead_code)] // The fields below are never used. + struct __Unpin #generics_with_pinlt + #where_token + #predicates + { + __phantom_pin: ::core::marker::PhantomData &'__pin ()>, + __phantom: ::core::marker::PhantomData< + fn(#ident #ty_generics) -> #ident #ty_generics + >, + #(#pinned_fields),* + } + + #[doc(hidden)] + impl #impl_generics_with_pinlt ::core::marker::Unpin for #ident #ty_generics + #where_token + __Unpin #ty_generics_with_pinlt: ::core::marker::Unpin, + #predicates + {} + } +} + +fn generate_drop_impl( + ItemStruct { + ident, generics, .. + }: &syn::ItemStruct, + args: Args, +) -> TokenStream { + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + let has_pinned_drop = matches!(args, Args::PinnedDrop(_)); + // We need to disallow normal `Drop` implementation, the exact behavior depends on whether + // `PinnedDrop` was specified in `args`. + if has_pinned_drop { + // When `PinnedDrop` was specified we just implement `Drop` and delegate. + quote! { + impl #impl_generics ::core::ops::Drop for #ident #ty_generics + #whr + { + fn drop(&mut self) { + // SAFETY: Since this is a destructor, `self` will not move after this function + // terminates, since it is inaccessible. + let pinned = unsafe { ::core::pin::Pin::new_unchecked(self) }; + // SAFETY: Since this is a drop function, we can create this token to call the + // pinned destructor of this type. + let token = unsafe { ::pin_init::__internal::OnlyCallFromDrop::new() }; + ::pin_init::PinnedDrop::drop(pinned, token); } - Some(res) } - _ => None, - }) - .unwrap_or_else(|| { - // If we did not find the name of the struct then we will use `Self` as the replacement - // and add a compile error to ensure it does not compile. - errs.extend( - "::core::compile_error!(\"Could not locate type name.\");" - .parse::() - .unwrap(), - ); - "Self".parse::().unwrap().into_iter().collect() - }); - let impl_generics = impl_generics - .into_iter() - .flat_map(|tt| replace_self_and_deny_type_defs(&struct_name, tt, &mut errs)) - .collect::>(); - let mut rest = rest - .into_iter() - .flat_map(|tt| { - // We ignore top level `struct` tokens, since they would emit a compile error. - if matches!(&tt, TokenTree::Ident(i) if i.to_string() == "struct") { - vec![tt] + } + } else { + // When no `PinnedDrop` was specified, then we have to prevent implementing drop. + quote! { + // We prevent this by creating a trait that will be implemented for all types implementing + // `Drop`. Additionally we will implement this trait for the struct leading to a conflict, + // if it also implements `Drop` + trait MustNotImplDrop {} + #[expect(drop_bounds)] + impl MustNotImplDrop for T {} + impl #impl_generics MustNotImplDrop for #ident #ty_generics + #whr + {} + // We also take care to prevent users from writing a useless `PinnedDrop` implementation. + // They might implement `PinnedDrop` correctly for the struct, but forget to give + // `PinnedDrop` as the parameter to `#[pin_data]`. + #[expect(non_camel_case_types)] + trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} + impl + UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} + impl #impl_generics + UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #ident #ty_generics + #whr + {} + } + } +} + +fn generate_projections( + ItemStruct { + vis, + ident, + generics, + fields, + .. + }: &ItemStruct, +) -> TokenStream { + let (og_impl_gen, og_ty_gen, _) = generics.split_for_impl(); + let mut generics = generics.clone(); + generics.params.insert(0, parse_quote!('__pin)); + let (_, ty_gen, whr) = generics.split_for_impl(); + let projection = format_ident!("{ident}Projection"); + let this = format_ident!("this"); + + let (fields_decl, fields_proj) = collect_tuple(fields.iter().map( + |f @ Field { + vis, + ident, + ty, + attrs, + .. + }| { + let mut attrs = attrs.clone(); + attrs.retain(|a| !a.path().is_ident("pin")); + let mut no_doc_attrs = attrs.clone(); + no_doc_attrs.retain(|a| !a.path().is_ident("doc")); + let ident = ident + .as_ref() + .expect("only structs with named fields are supported"); + if is_field_structurally_pinned(f) { + ( + quote!( + #(#attrs)* + #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, + ), + quote!( + #(#no_doc_attrs)* + // SAFETY: this field is structurally pinned. + #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) }, + ), + ) } else { - replace_self_and_deny_type_defs(&struct_name, tt, &mut errs) + ( + quote!( + #(#attrs)* + #vis #ident: &'__pin mut #ty, + ), + quote!( + #(#no_doc_attrs)* + #ident: &mut #this.#ident, + ), + ) } - }) - .collect::>(); - // This should be the body of the struct `{...}`. - let last = rest.pop(); - let mut quoted = quote!(::pin_init::__pin_data! { - parse_input: - @args(#args), - @sig(#(#rest)*), - @impl_generics(#(#impl_generics)*), - @ty_generics(#(#ty_generics)*), - @decl_generics(#(#decl_generics)*), - @body(#last), - }); - quoted.extend(errs); - quoted + }, + )); + let structurally_pinned_fields_docs = fields + .iter() + .filter(|f| is_field_structurally_pinned(f)) + .map(|Field { ident, .. }| { + let doc = format!(" - `{}`", ident.as_ref().expect("")); + quote!(#[doc = #doc]) + }); + let not_structurally_pinned_fields_docs = fields + .iter() + .filter(|f| !is_field_structurally_pinned(f)) + .map(|Field { ident, .. }| { + let doc = format!(" - `{}`", ident.as_ref().expect("")); + quote!(#[doc = #doc]) + }); + let docs = format!(" Pin-projections of [`{ident}`]"); + quote! { + #[doc = #docs] + #[allow(dead_code)] + #vis struct #projection #generics { + #(#fields_decl)* + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + } + + impl #og_impl_gen #ident #og_ty_gen + #whr + { + /// Pin-projects all fields of `Self`. + /// + /// These fields are structurally pinned: + #(#structurally_pinned_fields_docs)* + /// + /// These fields are **not** structurally pinned: + #(#not_structurally_pinned_fields_docs)* + #vis fn project<'__pin>( + self: ::core::pin::Pin<&'__pin mut Self>, + ) -> #projection #ty_gen { + // SAFETY: we only give access to `&mut` for fields not structurally pinned. + let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + #projection { + #(#fields_proj)* + ___pin_phantom_data: ::core::marker::PhantomData, + } + } + } + } } -/// Replaces `Self` with `struct_name` and errors on `enum`, `trait`, `struct` `union` and `impl` -/// keywords. -/// -/// The error is appended to `errs` to allow normal parsing to continue. -fn replace_self_and_deny_type_defs( - struct_name: &Vec, - tt: TokenTree, - errs: &mut TokenStream, -) -> Vec { - match tt { - TokenTree::Ident(ref i) - if i.to_string() == "enum" - || i.to_string() == "trait" - || i.to_string() == "struct" - || i.to_string() == "union" - || i.to_string() == "impl" => +fn generate_the_pin_data( + ItemStruct { + generics, + fields, + ident, + vis, + .. + }: &syn::ItemStruct, +) -> TokenStream { + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + + // For every field, we create an initializing projection function according to its projection + // type. If a field is structurally pinned, then it must be initialized via `PinInit`, if it is + // not structurally pinned, then it can be initialized via `Init`. + // + // The functions are `unsafe` to prevent accidentally calling them. + fn handle_field( + Field { + vis, + ident, + ty, + attrs, + .. + }: &Field, + struct_ident: &Ident, + pinned: bool, + ) -> TokenStream { + let mut attrs = attrs.clone(); + attrs.retain(|a| !a.path().is_ident("pin")); + let ident = ident + .as_ref() + .expect("only structs with named fields are supported"); + let project_ident = format_ident!("__project_{ident}"); + let (init_ty, init_fn, project_ty, project_body, pin_safety) = if pinned { + ( + quote!(PinInit), + quote!(__pinned_init), + quote!(::core::pin::Pin<&'__slot mut #ty>), + // SAFETY: this field is structurally pinned. + quote!(unsafe { ::core::pin::Pin::new_unchecked(slot) }), + quote!( + #[doc = " - `slot` will not move until it is dropped, i.e. it will be pinned."] + ), + ) + } else { + ( + quote!(Init), + quote!(__init), + quote!(&'__slot mut #ty), + quote!(slot), + quote!(#[doc = ""]), + ) + }; + let slot_safety = format!( + " `slot` points at the field `{ident}` inside of `{struct_ident}`, which is pinned.", + ); + quote! { + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + #pin_safety + #(#attrs)* + #vis unsafe fn #ident( + self, + slot: *mut #ty, + init: impl ::pin_init::#init_ty<#ty, E>, + ) -> ::core::result::Result<(), E> { + // SAFETY: this function has the same safety requirements as the __init function + // called below. + unsafe { ::pin_init::#init_ty::#init_fn(init, slot) } + } + + /// # Safety + /// + #[doc = #slot_safety] + #(#attrs)* + #vis unsafe fn #project_ident<'__slot>( + self, + slot: &'__slot mut #ty, + ) -> #project_ty { + #project_body + } + } + } + + let field_accessors = fields + .iter() + .map(|f| handle_field(f, ident, is_field_structurally_pinned(f))) + .collect::(); + quote! { + // We declare this struct which will host all of the projection function for our type. It + // will be invariant over all generic parameters which are inherited from the struct. + #[doc(hidden)] + #vis struct __ThePinData #generics + #whr { - errs.extend( - format!( - "::core::compile_error!(\"Cannot use `{i}` inside of struct definition with \ - `#[pin_data]`.\");" - ) - .parse::() - .unwrap() - .into_iter() - .map(|mut tok| { - tok.set_span(tt.span()); - tok - }), - ); - vec![tt] - } - TokenTree::Ident(i) if i.to_string() == "Self" => struct_name.clone(), - TokenTree::Literal(_) | TokenTree::Punct(_) | TokenTree::Ident(_) => vec![tt], - TokenTree::Group(g) => vec![TokenTree::Group(Group::new( - g.delimiter(), - g.stream() - .into_iter() - .flat_map(|tt| replace_self_and_deny_type_defs(struct_name, tt, errs)) - .collect(), - ))], + __phantom: ::core::marker::PhantomData< + fn(#ident #ty_generics) -> #ident #ty_generics + >, + } + + impl #impl_generics ::core::clone::Clone for __ThePinData #ty_generics + #whr + { + fn clone(&self) -> Self { *self } + } + + impl #impl_generics ::core::marker::Copy for __ThePinData #ty_generics + #whr + {} + + #[allow(dead_code)] // Some functions might never be used and private. + #[expect(clippy::missing_safety_doc)] + impl #impl_generics __ThePinData #ty_generics + #whr + { + #field_accessors + } + + // SAFETY: We have added the correct projection functions above to `__ThePinData` and + // we also use the least restrictive generics possible. + unsafe impl #impl_generics ::pin_init::__internal::HasPinData for #ident #ty_generics + #whr + { + type PinData = __ThePinData #ty_generics; + + unsafe fn __pin_data() -> Self::PinData { + __ThePinData { __phantom: ::core::marker::PhantomData } + } + } + + // SAFETY: TODO + unsafe impl #impl_generics ::pin_init::__internal::PinData for __ThePinData #ty_generics + #whr + { + type Datee = #ident #ty_generics; + } + } +} + +fn strip_pin_annotations(struct_: &mut syn::ItemStruct) { + for field in &mut struct_.fields { + field.attrs.retain(|a| !a.path().is_ident("pin")); + } +} + +struct SelfReplacer(PathSegment); + +impl VisitMut for SelfReplacer { + fn visit_path_mut(&mut self, i: &mut syn::Path) { + if i.is_ident("Self") { + let span = i.span(); + let seg = &self.0; + *i = parse_quote_spanned!(span=> #seg); + } else { + syn::visit_mut::visit_path_mut(self, i); + } + } + + fn visit_path_segment_mut(&mut self, seg: &mut PathSegment) { + if seg.ident == "Self" { + let span = seg.span(); + let this = &self.0; + *seg = parse_quote_spanned!(span=> #this); + } else { + syn::visit_mut::visit_path_segment_mut(self, seg); + } + } + + fn visit_item_mut(&mut self, _: &mut Item) { + // Do not descend into items, since items reset/change what `Self` refers to. + } +} + +// replace with `.collect()` once MSRV is above 1.79 +fn collect_tuple(iter: impl Iterator) -> (Vec, Vec) { + let mut res_a = vec![]; + let mut res_b = vec![]; + for (a, b) in iter { + res_a.push(a); + res_b.push(b); } + (res_a, res_b) } diff --git a/src/macros.rs b/src/macros.rs index b80c9561..eea8adc5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -503,580 +503,6 @@ pub use ::macros::paste; #[cfg(not(kernel))] pub use ::paste::paste; -/// This macro first parses the struct definition such that it separates pinned and not pinned -/// fields. Afterwards it declares the struct and implement the `PinData` trait safely. -#[doc(hidden)] -#[macro_export] -macro_rules! __pin_data { - // Proc-macro entry point, this is supplied by the proc-macro pre-parsing. - (parse_input: - @args($($pinned_drop:ident)?), - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis struct $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @body({ $($fields:tt)* }), - ) => { - // We now use token munching to iterate through all of the fields. While doing this we - // identify fields marked with `#[pin]`, these fields are the 'pinned fields'. The user - // wants these to be structurally pinned. The rest of the fields are the - // 'not pinned fields'. Additionally we collect all fields, since we need them in the right - // order to declare the struct. - // - // In this call we also put some explaining comments for the parameters. - $crate::__pin_data!(find_pinned_fields: - // Attributes on the struct itself, these will just be propagated to be put onto the - // struct definition. - @struct_attrs($(#[$($struct_attr)*])*), - // The visibility of the struct. - @vis($vis), - // The name of the struct. - @name($name), - // The 'impl generics', the generics that will need to be specified on the struct inside - // of an `impl<$ty_generics>` block. - @impl_generics($($impl_generics)*), - // The 'ty generics', the generics that will need to be specified on the impl blocks. - @ty_generics($($ty_generics)*), - // The 'decl generics', the generics that need to be specified on the struct - // definition. - @decl_generics($($decl_generics)*), - // The where clause of any impl block and the declaration. - @where($($($whr)*)?), - // The remaining fields tokens that need to be processed. - // We add a `,` at the end to ensure correct parsing. - @fields_munch($($fields)* ,), - // The pinned fields. - @pinned(), - // The not pinned fields. - @not_pinned(), - // All fields. - @fields(), - // The accumulator containing all attributes already parsed. - @accum(), - // Contains `yes` or `` to indicate if `#[pin]` was found on the current field. - @is_pinned(), - // The proc-macro argument, this should be `PinnedDrop` or ``. - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We found a PhantomPinned field, this should generally be pinned! - @fields_munch($field:ident : $($($(::)?core::)?marker::)?PhantomPinned, $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - // This field is not pinned. - @is_pinned(), - @pinned_drop($($pinned_drop:ident)?), - ) => { - ::core::compile_error!(concat!( - "The field `", - stringify!($field), - "` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute.", - )); - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - @pinned($($pinned)* $($accum)* $field: ::core::marker::PhantomPinned,), - @not_pinned($($not_pinned)*), - @fields($($fields)* $($accum)* $field: ::core::marker::PhantomPinned,), - @accum(), - @is_pinned(), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We reached the field declaration. - @fields_munch($field:ident : $type:ty, $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - // This field is pinned. - @is_pinned(yes), - @pinned_drop($($pinned_drop:ident)?), - ) => { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - @pinned($($pinned)* $($accum)* $field: $type,), - @not_pinned($($not_pinned)*), - @fields($($fields)* $($accum)* $field: $type,), - @accum(), - @is_pinned(), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We reached the field declaration. - @fields_munch($field:ident : $type:ty, $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - // This field is not pinned. - @is_pinned(), - @pinned_drop($($pinned_drop:ident)?), - ) => { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)* $($accum)* $field: $type,), - @fields($($fields)* $($accum)* $field: $type,), - @accum(), - @is_pinned(), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We found the `#[pin]` attr. - @fields_munch(#[pin] $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - @is_pinned($($is_pinned:ident)?), - @pinned_drop($($pinned_drop:ident)?), - ) => { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - // We do not include `#[pin]` in the list of attributes, since it is not actually an - // attribute that is defined somewhere. - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - @fields($($fields)*), - @accum($($accum)*), - // Set this to `yes`. - @is_pinned(yes), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We reached the field declaration with visibility, for simplicity we only munch the - // visibility and put it into `$accum`. - @fields_munch($fvis:vis $field:ident $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - @is_pinned($($is_pinned:ident)?), - @pinned_drop($($pinned_drop:ident)?), - ) => { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($field $($rest)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - @fields($($fields)*), - @accum($($accum)* $fvis), - @is_pinned($($is_pinned)?), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // Some other attribute, just put it into `$accum`. - @fields_munch(#[$($attr:tt)*] $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - @is_pinned($($is_pinned:ident)?), - @pinned_drop($($pinned_drop:ident)?), - ) => { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - @fields($($fields)*), - @accum($($accum)* #[$($attr)*]), - @is_pinned($($is_pinned)?), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We reached the end of the fields, plus an optional additional comma, since we added one - // before and the user is also allowed to put a trailing comma. - @fields_munch($(,)?), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum(), - @is_pinned(), - @pinned_drop($($pinned_drop:ident)?), - ) => { - // Declare the struct with all fields in the correct order. - $($struct_attrs)* - $vis struct $name <$($decl_generics)*> - where $($whr)* - { - $($fields)* - } - - $crate::__pin_data!(make_pin_projections: - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - ); - - // We put the rest into this const item, because it then will not be accessible to anything - // outside. - const _: () = { - // We declare this struct which will host all of the projection function for our type. - // it will be invariant over all generic parameters which are inherited from the - // struct. - $vis struct __ThePinData<$($impl_generics)*> - where $($whr)* - { - __phantom: ::core::marker::PhantomData< - fn($name<$($ty_generics)*>) -> $name<$($ty_generics)*> - >, - } - - impl<$($impl_generics)*> ::core::clone::Clone for __ThePinData<$($ty_generics)*> - where $($whr)* - { - fn clone(&self) -> Self { *self } - } - - impl<$($impl_generics)*> ::core::marker::Copy for __ThePinData<$($ty_generics)*> - where $($whr)* - {} - - // Make all projection functions. - $crate::__pin_data!(make_pin_data: - @pin_data(__ThePinData), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @where($($whr)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - ); - - // SAFETY: We have added the correct projection functions above to `__ThePinData` and - // we also use the least restrictive generics possible. - unsafe impl<$($impl_generics)*> - $crate::__internal::HasPinData for $name<$($ty_generics)*> - where $($whr)* - { - type PinData = __ThePinData<$($ty_generics)*>; - - unsafe fn __pin_data() -> Self::PinData { - __ThePinData { __phantom: ::core::marker::PhantomData } - } - } - - // SAFETY: TODO. - unsafe impl<$($impl_generics)*> - $crate::__internal::PinData for __ThePinData<$($ty_generics)*> - where $($whr)* - { - type Datee = $name<$($ty_generics)*>; - } - - // This struct will be used for the unpin analysis. Since only structurally pinned - // fields are relevant whether the struct should implement `Unpin`. - #[allow(dead_code)] - struct __Unpin <'__pin, $($impl_generics)*> - where $($whr)* - { - __phantom_pin: ::core::marker::PhantomData &'__pin ()>, - __phantom: ::core::marker::PhantomData< - fn($name<$($ty_generics)*>) -> $name<$($ty_generics)*> - >, - // Only the pinned fields. - $($pinned)* - } - - #[doc(hidden)] - impl<'__pin, $($impl_generics)*> ::core::marker::Unpin for $name<$($ty_generics)*> - where - __Unpin<'__pin, $($ty_generics)*>: ::core::marker::Unpin, - $($whr)* - {} - - // We need to disallow normal `Drop` implementation, the exact behavior depends on - // whether `PinnedDrop` was specified as the parameter. - $crate::__pin_data!(drop_prevention: - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @where($($whr)*), - @pinned_drop($($pinned_drop)?), - ); - }; - }; - // When no `PinnedDrop` was specified, then we have to prevent implementing drop. - (drop_prevention: - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @where($($whr:tt)*), - @pinned_drop(), - ) => { - // We prevent this by creating a trait that will be implemented for all types implementing - // `Drop`. Additionally we will implement this trait for the struct leading to a conflict, - // if it also implements `Drop` - trait MustNotImplDrop {} - #[expect(drop_bounds)] - impl MustNotImplDrop for T {} - impl<$($impl_generics)*> MustNotImplDrop for $name<$($ty_generics)*> - where $($whr)* {} - // We also take care to prevent users from writing a useless `PinnedDrop` implementation. - // They might implement `PinnedDrop` correctly for the struct, but forget to give - // `PinnedDrop` as the parameter to `#[pin_data]`. - #[expect(non_camel_case_types)] - trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} - impl - UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} - impl<$($impl_generics)*> - UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for $name<$($ty_generics)*> - where $($whr)* {} - }; - // When `PinnedDrop` was specified we just implement `Drop` and delegate. - (drop_prevention: - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @where($($whr:tt)*), - @pinned_drop(PinnedDrop), - ) => { - impl<$($impl_generics)*> ::core::ops::Drop for $name<$($ty_generics)*> - where $($whr)* - { - fn drop(&mut self) { - // SAFETY: Since this is a destructor, `self` will not move after this function - // terminates, since it is inaccessible. - let pinned = unsafe { ::core::pin::Pin::new_unchecked(self) }; - // SAFETY: Since this is a drop function, we can create this token to call the - // pinned destructor of this type. - let token = unsafe { $crate::__internal::OnlyCallFromDrop::new() }; - $crate::PinnedDrop::drop(pinned, token); - } - } - }; - // If some other parameter was specified, we emit a readable error. - (drop_prevention: - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @where($($whr:tt)*), - @pinned_drop($($rest:tt)*), - ) => { - compile_error!( - "Wrong parameters to `#[pin_data]`, expected nothing or `PinnedDrop`, got '{}'.", - stringify!($($rest)*), - ); - }; - (make_pin_projections: - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - @pinned($($(#[$($p_attr:tt)*])* $pvis:vis $p_field:ident : $p_type:ty),* $(,)?), - @not_pinned($($(#[$($attr:tt)*])* $fvis:vis $field:ident : $type:ty),* $(,)?), - ) => { - $crate::macros::paste! { - #[doc(hidden)] - $vis struct [< $name Projection >] <'__pin, $($decl_generics)*> { - $($(#[$($p_attr)*])* $pvis $p_field : ::core::pin::Pin<&'__pin mut $p_type>,)* - $($(#[$($attr)*])* $fvis $field : &'__pin mut $type,)* - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, - } - - impl<$($impl_generics)*> $name<$($ty_generics)*> - where $($whr)* - { - /// Pin-projects all fields of `Self`. - /// - /// These fields are structurally pinned: - $(#[doc = ::core::concat!(" - `", ::core::stringify!($p_field), "`")])* - /// - /// These fields are **not** structurally pinned: - $(#[doc = ::core::concat!(" - `", ::core::stringify!($field), "`")])* - #[inline] - $vis fn project<'__pin>( - self: ::core::pin::Pin<&'__pin mut Self>, - ) -> [< $name Projection >] <'__pin, $($ty_generics)*> { - // SAFETY: we only give access to `&mut` for fields not structurally pinned. - let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; - [< $name Projection >] { - $( - // SAFETY: `$p_field` is structurally pinned. - $(#[$($p_attr)*])* - $p_field : unsafe { ::core::pin::Pin::new_unchecked(&mut this.$p_field) }, - )* - $( - $(#[$($attr)*])* - $field : &mut this.$field, - )* - ___pin_phantom_data: ::core::marker::PhantomData, - } - } - } - } - }; - (make_pin_data: - @pin_data($pin_data:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @where($($whr:tt)*), - @pinned($($(#[$($p_attr:tt)*])* $pvis:vis $p_field:ident : $p_type:ty),* $(,)?), - @not_pinned($($(#[$($attr:tt)*])* $fvis:vis $field:ident : $type:ty),* $(,)?), - ) => { - $crate::macros::paste! { - // For every field, we create a projection function according to its projection type. If a - // field is structurally pinned, then it must be initialized via `PinInit`, if it is not - // structurally pinned, then it can be initialized via `Init`. - // - // The functions are `unsafe` to prevent accidentally calling them. - #[allow(dead_code)] - #[expect(clippy::missing_safety_doc)] - impl<$($impl_generics)*> $pin_data<$($ty_generics)*> - where $($whr)* - { - $( - $(#[$($p_attr)*])* - $pvis unsafe fn $p_field( - self, - slot: *mut $p_type, - init: impl $crate::PinInit<$p_type, E>, - ) -> ::core::result::Result<(), E> { - // SAFETY: TODO. - unsafe { $crate::PinInit::__pinned_init(init, slot) } - } - - $(#[$($p_attr)*])* - $pvis unsafe fn [<__project_ $p_field>]<'__slot>( - self, - slot: &'__slot mut $p_type, - ) -> ::core::pin::Pin<&'__slot mut $p_type> { - ::core::pin::Pin::new_unchecked(slot) - } - )* - $( - $(#[$($attr)*])* - $fvis unsafe fn $field( - self, - slot: *mut $type, - init: impl $crate::Init<$type, E>, - ) -> ::core::result::Result<(), E> { - // SAFETY: TODO. - unsafe { $crate::Init::__init(init, slot) } - } - - $(#[$($attr)*])* - $fvis unsafe fn [<__project_ $field>]<'__slot>( - self, - slot: &'__slot mut $type, - ) -> &'__slot mut $type { - slot - } - )* - } - } - }; -} - /// The internal init macro. Do not call manually! /// /// This is called by the `{try_}{pin_}init!` macros with various inputs. diff --git a/tests/many_generics.rs b/tests/many_generics.rs index 2cad6421..d45ac287 100644 --- a/tests/many_generics.rs +++ b/tests/many_generics.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(RUSTC_LINT_REASONS_IS_STABLE), feature(lint_reasons))] +#![allow(dead_code)] use core::{marker::PhantomPinned, pin::Pin}; use pin_init::*; diff --git a/tests/ui/compile-fail/init/wrong_generics2.stderr b/tests/ui/compile-fail/init/wrong_generics2.stderr index dbc9c342..8c57961e 100644 --- a/tests/ui/compile-fail/init/wrong_generics2.stderr +++ b/tests/ui/compile-fail/init/wrong_generics2.stderr @@ -12,7 +12,7 @@ help: you might have forgotten to add the struct literal inside the block --> src/macros.rs | ~ ::core::ptr::write($slot, $t { SomeStruct { - |4 $($acc)* + | $($acc)* ~ } }); | diff --git a/tests/ui/compile-fail/pin_data/missing_comma.stderr b/tests/ui/compile-fail/pin_data/missing_comma.stderr index 5e296742..1b50f815 100644 --- a/tests/ui/compile-fail/pin_data/missing_comma.stderr +++ b/tests/ui/compile-fail/pin_data/missing_comma.stderr @@ -4,11 +4,14 @@ error: expected `,`, or `}`, found `b` 5 | a: Box | ^ help: try adding a comma: `,` -error: recursion limit reached while expanding `$crate::__pin_data!` - --> tests/ui/compile-fail/pin_data/missing_comma.rs:3:1 +error: expected `,` + --> tests/ui/compile-fail/pin_data/missing_comma.rs:6:5 | -3 | #[pin_data] - | ^^^^^^^^^^^ +6 | b: Box + | ^ + +error[E0601]: `main` function not found in crate `$CRATE` + --> tests/ui/compile-fail/pin_data/missing_comma.rs:7:2 | - = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`) - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) +7 | } + | ^ consider adding a `main` function to `$DIR/tests/ui/compile-fail/pin_data/missing_comma.rs` diff --git a/tests/ui/compile-fail/pin_data/missing_pin.stderr b/tests/ui/compile-fail/pin_data/missing_pin.stderr index fdc96d73..ced6d246 100644 --- a/tests/ui/compile-fail/pin_data/missing_pin.stderr +++ b/tests/ui/compile-fail/pin_data/missing_pin.stderr @@ -18,4 +18,4 @@ note: required by a bound in `__ThePinData::a` 5 | struct Foo { 6 | a: usize, | - required by a bound in this associated function - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.stderr b/tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.stderr index 4c1b7ce3..e334cfef 100644 --- a/tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.stderr +++ b/tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.stderr @@ -1,31 +1,46 @@ -error: The field `pin1` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute. - --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:4:1 +error: The field `pin1` of type `PhantomData` only has an effect if it has the `#[pin]` attribute + --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:6:5 | -4 | #[pin_data] - | ^^^^^^^^^^^ +6 | pin1: PhantomPinned, + | ^^^^^^^^^^^^^^^^^^^ + +error: The field `pin2` of type `PhantomData` only has an effect if it has the `#[pin]` attribute + --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:7:5 | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) +7 | pin2: marker::PhantomPinned, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: The field `pin2` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute. - --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:4:1 +error: The field `pin3` of type `PhantomData` only has an effect if it has the `#[pin]` attribute + --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:8:5 | -4 | #[pin_data] - | ^^^^^^^^^^^ +8 | pin3: core::marker::PhantomPinned, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: The field `pin4` of type `PhantomData` only has an effect if it has the `#[pin]` attribute + --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:9:5 | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) +9 | pin4: ::core::marker::PhantomPinned, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: The field `pin3` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute. - --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:4:1 +error[E0412]: cannot find type `PhantomPinned` in this scope + --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:6:11 + | +6 | pin1: PhantomPinned, + | ^^^^^^^^^^^^^ not found in this scope | -4 | #[pin_data] - | ^^^^^^^^^^^ +help: consider importing this struct + | +1 + use std::marker::PhantomPinned; | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) -error: The field `pin4` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute. - --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:4:1 +error[E0433]: failed to resolve: use of unresolved module or unlinked crate `marker` + --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:7:11 + | +7 | pin2: marker::PhantomPinned, + | ^^^^^^ use of unresolved module or unlinked crate `marker` + | + = help: if you wanted to use a crate named `marker`, use `cargo add marker` to add it to your `Cargo.toml` +help: consider importing this module | -4 | #[pin_data] - | ^^^^^^^^^^^ +1 + use std::marker; | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/pin_data_but_drop.stderr b/tests/ui/compile-fail/pin_data/pin_data_but_drop.stderr index 6143d9af..b46a30a1 100644 --- a/tests/ui/compile-fail/pin_data/pin_data_but_drop.stderr +++ b/tests/ui/compile-fail/pin_data/pin_data_but_drop.stderr @@ -7,4 +7,4 @@ error[E0119]: conflicting implementations of trait `MustNotImplDrop` for type `F | first implementation here | conflicting implementation for `Foo` | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/twice.stderr b/tests/ui/compile-fail/pin_data/twice.stderr index c09a6ce7..274912e3 100644 --- a/tests/ui/compile-fail/pin_data/twice.stderr +++ b/tests/ui/compile-fail/pin_data/twice.stderr @@ -1,54 +1,50 @@ error[E0428]: the name `FooProjection` is defined multiple times - --> tests/ui/compile-fail/pin_data/twice.rs:3:1 + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 | 3 | #[pin_data] - | ^^^^^^^^^^^ - | | - | `FooProjection` redefined here - | previous definition of the type `FooProjection` here + | ----------- previous definition of the type `FooProjection` here +4 | #[pin_data] + | ^^^^^^^^^^^ `FooProjection` redefined here | = note: `FooProjection` must be defined only once in the type namespace of this module - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0119]: conflicting implementations of trait `HasPinData` for type `Foo` - --> tests/ui/compile-fail/pin_data/twice.rs:3:1 + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 | 3 | #[pin_data] - | ^^^^^^^^^^^ - | | - | first implementation here - | conflicting implementation for `Foo` + | ----------- first implementation here +4 | #[pin_data] + | ^^^^^^^^^^^ conflicting implementation for `Foo` | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0119]: conflicting implementations of trait `Unpin` for type `Foo` - --> tests/ui/compile-fail/pin_data/twice.rs:3:1 + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 | 3 | #[pin_data] - | ^^^^^^^^^^^ - | | - | first implementation here - | conflicting implementation for `Foo` + | ----------- first implementation here +4 | #[pin_data] + | ^^^^^^^^^^^ conflicting implementation for `Foo` | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0592]: duplicate definitions with name `project` - --> tests/ui/compile-fail/pin_data/twice.rs:3:1 + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 | 3 | #[pin_data] - | ^^^^^^^^^^^ - | | - | duplicate definitions for `project` - | other definition for `project` + | ----------- other definition for `project` +4 | #[pin_data] + | ^^^^^^^^^^^ duplicate definitions for `project` | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types - --> tests/ui/compile-fail/pin_data/twice.rs:3:1 + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 | -3 | #[pin_data] - | ^^^^^^^^^^^ expected `&mut usize`, found `Pin<&mut usize>` +4 | #[pin_data] + | ^^^^^^^^^^^ expected `Pin<&mut usize>`, found `&mut usize` | - = note: expected mutable reference `&mut usize` - found struct `Pin<&mut usize>` - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: expected struct `Pin<&mut usize>` + found mutable reference `&mut usize` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/unexpected_args.stderr b/tests/ui/compile-fail/pin_data/unexpected_args.stderr index 4744fc12..9ad20047 100644 --- a/tests/ui/compile-fail/pin_data/unexpected_args.stderr +++ b/tests/ui/compile-fail/pin_data/unexpected_args.stderr @@ -1,15 +1,5 @@ -error: compile_error! takes 1 argument - --> tests/ui/compile-fail/pin_data/unexpected_args.rs:3:1 +error: expected `PinnedDrop` + --> tests/ui/compile-fail/pin_data/unexpected_args.rs:3:12 | 3 | #[pin_data(Bar)] - | ^^^^^^^^^^^^^^^^ - | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: Wrong parameters to `#[pin_data]`, expected nothing or `PinnedDrop`, got '{}'. - --> tests/ui/compile-fail/pin_data/unexpected_args.rs:3:1 - | -3 | #[pin_data(Bar)] - | ^^^^^^^^^^^^^^^^ - | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^ diff --git a/tests/ui/compile-fail/pin_data/unexpected_item.stderr b/tests/ui/compile-fail/pin_data/unexpected_item.stderr index 1772f228..580792ac 100644 --- a/tests/ui/compile-fail/pin_data/unexpected_item.stderr +++ b/tests/ui/compile-fail/pin_data/unexpected_item.stderr @@ -1,16 +1,4 @@ -error: no rules expected keyword `fn` - --> tests/ui/compile-fail/pin_data/unexpected_item.rs:4:1 - | -4 | fn foo() {} - | ^^ no rules expected this token in macro call - | -note: while trying to match keyword `struct` - --> src/macros.rs - | - | $vis:vis struct $name:ident - | ^^^^^^ - -error: Could not locate type name. +error: `#[pin_data]` can only be applied to struct, enum and union defintions --> tests/ui/compile-fail/pin_data/unexpected_item.rs:3:1 | 3 | #[pin_data] diff --git a/tests/ui/compile-fail/pinned_drop/useless_pinned_drop.stderr b/tests/ui/compile-fail/pinned_drop/useless_pinned_drop.stderr index 95c4a757..d54aeb01 100644 --- a/tests/ui/compile-fail/pinned_drop/useless_pinned_drop.stderr +++ b/tests/ui/compile-fail/pinned_drop/useless_pinned_drop.stderr @@ -7,4 +7,4 @@ error[E0119]: conflicting implementations of trait `UselessPinnedDropImpl_you_ne | first implementation here | conflicting implementation for `Foo` | - = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index 007d4388..af78aad7 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -1,4 +1,5 @@ #![feature(lint_reasons)] +#![allow(dead_code)] use core::{marker::PhantomPinned, pin::Pin}; use pin_init::*; trait Bar<'a, const ID: usize = 0> { @@ -12,7 +13,8 @@ where r: &'b mut [&'a mut T; SIZE], _pin: PhantomPinned, } -#[doc(hidden)] +/// Pin-projections of [`Foo`] +#[allow(dead_code)] struct FooProjection< '__pin, 'a, @@ -20,9 +22,9 @@ struct FooProjection< T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0, > { - _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, array: &'__pin mut [u8; 1024 * 1024], r: &'__pin mut &'b mut [&'a mut T; SIZE], + _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> Foo<'a, 'b, T, SIZE> @@ -37,21 +39,21 @@ where /// These fields are **not** structurally pinned: /// - `array` /// - `r` - #[inline] fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, ) -> FooProjection<'__pin, 'a, 'b, T, SIZE> { let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; FooProjection { - _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, array: &mut this.array, r: &mut this.r, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, ___pin_phantom_data: ::core::marker::PhantomData, } } } const _: () = { - struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> + #[doc(hidden)] + struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> where T: Bar<'a, 1>, { @@ -84,19 +86,12 @@ const _: () = { where T: Bar<'a, 1>, { - unsafe fn _pin( - self, - slot: *mut PhantomPinned, - init: impl ::pin_init::PinInit, - ) -> ::core::result::Result<(), E> { - unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } - } - unsafe fn __project__pin<'__slot>( - self, - slot: &'__slot mut PhantomPinned, - ) -> ::core::pin::Pin<&'__slot mut PhantomPinned> { - ::core::pin::Pin::new_unchecked(slot) - } + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + /// unsafe fn array( self, slot: *mut [u8; 1024 * 1024], @@ -104,12 +99,21 @@ const _: () = { ) -> ::core::result::Result<(), E> { unsafe { ::pin_init::Init::__init(init, slot) } } + /// # Safety + /// + /// `slot` points at the field `array` inside of `Foo`, which is pinned. unsafe fn __project_array<'__slot>( self, slot: &'__slot mut [u8; 1024 * 1024], ) -> &'__slot mut [u8; 1024 * 1024] { slot } + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + /// unsafe fn r( self, slot: *mut &'b mut [&'a mut T; SIZE], @@ -117,12 +121,37 @@ const _: () = { ) -> ::core::result::Result<(), E> { unsafe { ::pin_init::Init::__init(init, slot) } } + /// # Safety + /// + /// `slot` points at the field `r` inside of `Foo`, which is pinned. unsafe fn __project_r<'__slot>( self, slot: &'__slot mut &'b mut [&'a mut T; SIZE], ) -> &'__slot mut &'b mut [&'a mut T; SIZE] { slot } + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + /// - `slot` will not move until it is dropped, i.e. it will be pinned. + unsafe fn _pin( + self, + slot: *mut PhantomPinned, + init: impl ::pin_init::PinInit, + ) -> ::core::result::Result<(), E> { + unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } + } + /// # Safety + /// + /// `slot` points at the field `_pin` inside of `Foo`, which is pinned. + unsafe fn __project__pin<'__slot>( + self, + slot: &'__slot mut PhantomPinned, + ) -> ::core::pin::Pin<&'__slot mut PhantomPinned> { + unsafe { ::core::pin::Pin::new_unchecked(slot) } + } } unsafe impl< 'a, @@ -152,7 +181,7 @@ const _: () = { type Datee = Foo<'a, 'b, T, SIZE>; } #[allow(dead_code)] - struct __Unpin<'__pin, 'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> + struct __Unpin<'__pin, 'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> where T: Bar<'a, 1>, { diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index 9d563f15..d8f1d178 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -4,10 +4,11 @@ struct Foo { array: [u8; 1024 * 1024], _pin: PhantomPinned, } -#[doc(hidden)] +/// Pin-projections of [`Foo`] +#[allow(dead_code)] struct FooProjection<'__pin> { - _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, array: &'__pin mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, } impl Foo { @@ -18,19 +19,19 @@ impl Foo { /// /// These fields are **not** structurally pinned: /// - `array` - #[inline] fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, ) -> FooProjection<'__pin> { let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; FooProjection { - _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, ___pin_phantom_data: ::core::marker::PhantomData, } } } const _: () = { + #[doc(hidden)] struct __ThePinData { __phantom: ::core::marker::PhantomData Foo>, } @@ -43,19 +44,12 @@ const _: () = { #[allow(dead_code)] #[expect(clippy::missing_safety_doc)] impl __ThePinData { - unsafe fn _pin( - self, - slot: *mut PhantomPinned, - init: impl ::pin_init::PinInit, - ) -> ::core::result::Result<(), E> { - unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } - } - unsafe fn __project__pin<'__slot>( - self, - slot: &'__slot mut PhantomPinned, - ) -> ::core::pin::Pin<&'__slot mut PhantomPinned> { - ::core::pin::Pin::new_unchecked(slot) - } + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + /// unsafe fn array( self, slot: *mut [u8; 1024 * 1024], @@ -63,12 +57,37 @@ const _: () = { ) -> ::core::result::Result<(), E> { unsafe { ::pin_init::Init::__init(init, slot) } } + /// # Safety + /// + /// `slot` points at the field `array` inside of `Foo`, which is pinned. unsafe fn __project_array<'__slot>( self, slot: &'__slot mut [u8; 1024 * 1024], ) -> &'__slot mut [u8; 1024 * 1024] { slot } + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + /// - `slot` will not move until it is dropped, i.e. it will be pinned. + unsafe fn _pin( + self, + slot: *mut PhantomPinned, + init: impl ::pin_init::PinInit, + ) -> ::core::result::Result<(), E> { + unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } + } + /// # Safety + /// + /// `slot` points at the field `_pin` inside of `Foo`, which is pinned. + unsafe fn __project__pin<'__slot>( + self, + slot: &'__slot mut PhantomPinned, + ) -> ::core::pin::Pin<&'__slot mut PhantomPinned> { + unsafe { ::core::pin::Pin::new_unchecked(slot) } + } } unsafe impl ::pin_init::__internal::HasPinData for Foo { type PinData = __ThePinData; diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 8aacd750..247c8d4a 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -4,10 +4,11 @@ struct Foo { array: [u8; 1024 * 1024], _pin: PhantomPinned, } -#[doc(hidden)] +/// Pin-projections of [`Foo`] +#[allow(dead_code)] struct FooProjection<'__pin> { - _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, array: &'__pin mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, } impl Foo { @@ -18,19 +19,19 @@ impl Foo { /// /// These fields are **not** structurally pinned: /// - `array` - #[inline] fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, ) -> FooProjection<'__pin> { let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; FooProjection { - _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, ___pin_phantom_data: ::core::marker::PhantomData, } } } const _: () = { + #[doc(hidden)] struct __ThePinData { __phantom: ::core::marker::PhantomData Foo>, } @@ -43,19 +44,12 @@ const _: () = { #[allow(dead_code)] #[expect(clippy::missing_safety_doc)] impl __ThePinData { - unsafe fn _pin( - self, - slot: *mut PhantomPinned, - init: impl ::pin_init::PinInit, - ) -> ::core::result::Result<(), E> { - unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } - } - unsafe fn __project__pin<'__slot>( - self, - slot: &'__slot mut PhantomPinned, - ) -> ::core::pin::Pin<&'__slot mut PhantomPinned> { - ::core::pin::Pin::new_unchecked(slot) - } + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + /// unsafe fn array( self, slot: *mut [u8; 1024 * 1024], @@ -63,12 +57,37 @@ const _: () = { ) -> ::core::result::Result<(), E> { unsafe { ::pin_init::Init::__init(init, slot) } } + /// # Safety + /// + /// `slot` points at the field `array` inside of `Foo`, which is pinned. unsafe fn __project_array<'__slot>( self, slot: &'__slot mut [u8; 1024 * 1024], ) -> &'__slot mut [u8; 1024 * 1024] { slot } + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + /// - `slot` will not move until it is dropped, i.e. it will be pinned. + unsafe fn _pin( + self, + slot: *mut PhantomPinned, + init: impl ::pin_init::PinInit, + ) -> ::core::result::Result<(), E> { + unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } + } + /// # Safety + /// + /// `slot` points at the field `_pin` inside of `Foo`, which is pinned. + unsafe fn __project__pin<'__slot>( + self, + slot: &'__slot mut PhantomPinned, + ) -> ::core::pin::Pin<&'__slot mut PhantomPinned> { + unsafe { ::core::pin::Pin::new_unchecked(slot) } + } } unsafe impl ::pin_init::__internal::HasPinData for Foo { type PinData = __ThePinData; From 7ac932645c049b5b12bf7c7cd80001a3fc2f80a9 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Thu, 16 Oct 2025 12:20:14 +0200 Subject: [PATCH 12/14] add `?Sized` bounds to traits in `#[pin_data]` macro The `#[pin_data]` macro uses some auxiliary traits to ensure that a user does not implement `Drop` for the annotated struct, as that is unsound and can lead to UB. However, if the struct that is annotated is `!Sized`, the current bounds do not work, because `Sized` is an implicit bound for generics. This is *not* a soundness hole of pin-init, as it currently is impossible to construct an unsized struct using pin-init. Signed-off-by: Benno Lossin --- internal/src/pin_data.rs | 4 ++-- tests/ui/expand/pin-data.expanded.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index e57b766d..8a955139 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -223,7 +223,7 @@ fn generate_drop_impl( // if it also implements `Drop` trait MustNotImplDrop {} #[expect(drop_bounds)] - impl MustNotImplDrop for T {} + impl MustNotImplDrop for T {} impl #impl_generics MustNotImplDrop for #ident #ty_generics #whr {} @@ -232,7 +232,7 @@ fn generate_drop_impl( // `PinnedDrop` as the parameter to `#[pin_data]`. #[expect(non_camel_case_types)] trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} - impl + impl UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} impl #impl_generics UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #ident #ty_generics diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index d8f1d178..58b5d908 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -113,11 +113,12 @@ const _: () = { {} trait MustNotImplDrop {} #[expect(drop_bounds)] - impl MustNotImplDrop for T {} + impl MustNotImplDrop for T {} impl MustNotImplDrop for Foo {} #[expect(non_camel_case_types)] trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} - impl UselessPinnedDropImpl_you_need_to_specify_PinnedDrop - for T {} + impl< + T: ::pin_init::PinnedDrop + ?::core::marker::Sized, + > UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} impl UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for Foo {} }; From 5f88f2c587ec829762cac416b9e364455f227734 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Fri, 17 Oct 2025 15:21:38 +0200 Subject: [PATCH 13/14] rewrite the initializer macros using `syn` Rewrite the initializer macros `[pin_]init!` using `syn`. No functional changes intended aside from improved error messages on syntactic and semantical errors. For example if one forgets to use `<-` with an initializer (and instead uses `:`): impl Bar { fn new() -> impl PinInit { ... } } impl Foo { fn new() -> impl PinInit { pin_init!(Self { bar: Bar::new() }) } } Then the declarative macro would report: error[E0308]: mismatched types --> tests/ui/compile-fail/init/colon_instead_of_arrow.rs:21:9 | 14 | fn new() -> impl PinInit { | ------------------ the found opaque type ... 21 | pin_init!(Self { bar: Bar::new() }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | expected `Bar`, found opaque type | arguments to this function are incorrect | = note: expected struct `Bar` found opaque type `impl pin_init::PinInit` note: function defined here --> $RUST/core/src/ptr/mod.rs | | pub const unsafe fn write(dst: *mut T, src: T) { | ^^^^^ = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) And the new error is: error[E0308]: mismatched types --> tests/ui/compile-fail/init/colon_instead_of_arrow.rs:21:31 | 14 | fn new() -> impl PinInit { | ------------------ the found opaque type ... 21 | pin_init!(Self { bar: Bar::new() }) | --- ^^^^^^^^^^ expected `Bar`, found opaque type | | | arguments to this function are incorrect | = note: expected struct `Bar` found opaque type `impl pin_init::PinInit` note: function defined here --> $RUST/core/src/ptr/mod.rs | | pub const unsafe fn write(dst: *mut T, src: T) { | ^^^^^ Importantly, this error gives much more accurate span locations, pointing to the offending field, rather than the entire macro invocation. Signed-off-by: Benno Lossin --- internal/src/init.rs | 437 ++++++++ internal/src/lib.rs | 21 + internal/src/pin_data.rs | 4 +- src/lib.rs | 56 +- src/macros.rs | 951 ------------------ .../init/colon_instead_of_arrow.stderr | 10 +- .../init/field_value_wrong_type.stderr | 10 +- .../ui/compile-fail/init/invalid_init.stderr | 11 +- .../ui/compile-fail/init/missing_comma.stderr | 22 +- .../init/missing_comma_with_zeroable.stderr | 15 +- .../init/missing_error_type.stderr | 12 +- .../compile-fail/init/missing_pin_data.stderr | 2 +- .../init/no_error_coercion.stderr | 8 +- .../compile-fail/init/shadowing_field.stderr | 14 +- .../compile-fail/init/wrong_generics.stderr | 20 +- .../compile-fail/init/wrong_generics2.stderr | 64 -- .../compile-fail/pin_data/missing_pin.stderr | 11 +- .../compile-fail/zeroable/with_comma.stderr | 36 +- tests/ui/expand/many_generics.expanded.rs | 4 +- tests/ui/expand/pin-data.expanded.rs | 3 +- tests/ui/expand/pinned_drop.expanded.rs | 3 +- tests/ui/expand/simple-init.expanded.rs | 10 +- 22 files changed, 531 insertions(+), 1193 deletions(-) create mode 100644 internal/src/init.rs delete mode 100644 src/macros.rs diff --git a/internal/src/init.rs b/internal/src/init.rs new file mode 100644 index 00000000..c02a9969 --- /dev/null +++ b/internal/src/init.rs @@ -0,0 +1,437 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned}; +use syn::{ + braced, + parse::{End, Parse}, + parse_quote, + punctuated::Punctuated, + spanned::Spanned, + token, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, +}; + +pub struct Initializer { + this: Option, + path: Path, + brace_token: token::Brace, + fields: Punctuated, + rest: Option<(Token![..], Expr)>, + error: Option<(Token![?], Type)>, +} + +struct This { + _and_token: Token![&], + ident: Ident, + _in_token: Token![in], +} + +enum InitializerField { + Value { + ident: Ident, + value: Option<(Token![:], Expr)>, + }, + Init { + ident: Ident, + _left_arrow_token: Token![<-], + value: Expr, + }, + Code { + _underscore_token: Token![_], + _colon_token: Token![:], + block: Block, + }, +} + +impl InitializerField { + fn ident(&self) -> Option<&Ident> { + match self { + Self::Value { ident, .. } | Self::Init { ident, .. } => Some(ident), + Self::Code { .. } => None, + } + } +} + +pub(crate) fn expand( + Initializer { + this, + path, + brace_token, + fields, + rest, + mut error, + }: Initializer, + default_error: Option<&'static str>, + pinned: bool, +) -> TokenStream { + let mut errors = TokenStream::new(); + if let Some(default_error) = default_error { + error.get_or_insert((Default::default(), syn::parse_str(default_error).unwrap())); + } + let error = error.map(|(_, err)| err).unwrap_or_else(|| { + errors.extend(quote_spanned!(brace_token.span.close()=> + ::core::compile_error!("expected `? ` after `}`"); + )); + parse_quote!(::core::convert::Infallible) + }); + let slot = format_ident!("slot"); + let (has_data_trait, data_trait, get_data, init_from_closure) = if pinned { + ( + format_ident!("HasPinData"), + format_ident!("PinData"), + format_ident!("__pin_data"), + format_ident!("pin_init_from_closure"), + ) + } else { + ( + format_ident!("HasInitData"), + format_ident!("InitData"), + format_ident!("__init_data"), + format_ident!("init_from_closure"), + ) + }; + let init_kind = get_init_kind(rest, &mut errors); + let zeroable_check = match init_kind { + InitKind::Normal => quote!(), + InitKind::Zeroing => quote! { + // The user specified `..Zeroable::zeroed()` at the end of the list of fields. + // Therefore we check if the struct implements `Zeroable` and then zero the memory. + // This allows us to also remove the check that all fields are present (since we + // already set the memory to zero and that is a valid bit pattern). + fn assert_zeroable(_: *mut T) + where T: ::pin_init::Zeroable + {} + // Ensure that the struct is indeed `Zeroable`. + assert_zeroable(#slot); + // SAFETY: The type implements `Zeroable` by the check above. + unsafe { ::core::ptr::write_bytes(#slot, 0, 1) }; + }, + }; + let this = match this { + None => quote!(), + Some(This { ident, .. }) => quote! { + // Create the `this` so it can be referenced by the user inside of the + // expressions creating the individual fields. + let #ident = unsafe { ::core::ptr::NonNull::new_unchecked(slot) }; + }, + }; + // `mixed_site` ensures that the data is not accessible to the user-controlled code. + let data = format_ident!("__data", span = Span::mixed_site()); + let init_fields = init_fields(&fields, pinned, &data, &slot); + let field_check = make_field_check(&fields, init_kind, &path); + quote! {{ + // We do not want to allow arbitrary returns, so we declare this type as the `Ok` return + // type and shadow it later when we insert the arbitrary user code. That way there will be + // no possibility of returning without `unsafe`. + struct __InitOk; + + // Get the data about fields from the supplied type. + // SAFETY: TODO + let #data = unsafe { + use ::pin_init::__internal::#has_data_trait; + // Can't use `<#path as #has_data_trait>::#get_data`, since the user is able to omit + // generics (which need to be present with that syntax). + #path::#get_data() + }; + // Ensure that `#data` really is of type `#data` and help with type inference: + let init = ::pin_init::__internal::#data_trait::make_closure::<_, __InitOk, #error>( + #data, + move |slot| { + { + // Shadow the structure so it cannot be used to return early. + struct __InitOk; + #zeroable_check + #this + #init_fields + #field_check + } + Ok(__InitOk) + } + ); + let init = move |slot| -> ::core::result::Result<(), #error> { + init(slot).map(|__InitOk| ()) + }; + // SAFETY: TODO + let init = unsafe { ::pin_init::#init_from_closure::<_, #error>(init) }; + init + }} +} + +enum InitKind { + Normal, + Zeroing, +} + +fn get_init_kind(rest: Option<(Token![..], Expr)>, errors: &mut TokenStream) -> InitKind { + let Some((dotdot, expr)) = rest else { + return InitKind::Normal; + }; + match &expr { + Expr::Call(ExprCall { func, args, .. }) if args.is_empty() => match &**func { + Expr::Path(ExprPath { + attrs, + qself: None, + path: + Path { + leading_colon: None, + segments, + }, + }) if attrs.is_empty() + && segments.len() == 2 + && segments[0].ident == "Zeroable" + && segments[0].arguments.is_none() + && segments[1].ident == "init_zeroed" + && segments[1].arguments.is_none() => + { + return InitKind::Zeroing; + } + _ => {} + }, + _ => {} + } + let span = quote!(#dotdot #expr).span(); + errors.extend(quote_spanned!(span=> + ::core::compile_error!("expected nothing or `..Zeroable::init_zeroed()`."); + )); + InitKind::Normal +} + +/// Generate the code that initializes the fields of the struct using the initializers in `field`. +fn init_fields( + fields: &Punctuated, + pinned: bool, + data: &Ident, + slot: &Ident, +) -> TokenStream { + let mut guards = vec![]; + let mut res = TokenStream::new(); + for field in fields { + let init = match field { + InitializerField::Value { ident, value } => { + let mut value_ident = ident.clone(); + let value_prep = value.as_ref().map(|value| &value.1).map(|value| { + // Setting the span of `value_ident` to `value`'s span improves error messages + // when the type of `value` is wrong. + value_ident.set_span(value.span()); + quote!(let #value_ident = #value;) + }); + // Again span for better diagnostics + let write = quote_spanned!(ident.span()=> ::core::ptr::write); + let accessor = if pinned { + let project_ident = format_ident!("__project_{ident}"); + quote! { + // SAFETY: TODO + unsafe { #data.#project_ident(&mut (*#slot).#ident) } + } + } else { + quote! { + // SAFETY: TODO + unsafe { &mut (*#slot).#ident } + } + }; + quote! { + { + #value_prep + // SAFETY: TODO + unsafe { #write(::core::ptr::addr_of_mut!((*#slot).#ident), #value_ident) }; + } + #[allow(unused_variables)] + let #ident = #accessor; + } + } + InitializerField::Init { ident, value, .. } => { + // Again span for better diagnostics + let init = format_ident!("init", span = value.span()); + if pinned { + let project_ident = format_ident!("__project_{ident}"); + quote! { + { + let #init = #value; + // SAFETY: + // - `slot` is valid, because we are inside of an initializer closure, we + // return when an error/panic occurs. + // - We also use `#data` to require the correct trait (`Init` or `PinInit`) + // for `#ident`. + unsafe { #data.#ident(::core::ptr::addr_of_mut!((*#slot).#ident), #init)? }; + } + // SAFETY: TODO + #[allow(unused_variables)] + let #ident = unsafe { #data.#project_ident(&mut (*#slot).#ident) }; + } + } else { + quote! { + { + let #init = #value; + // SAFETY: `slot` is valid, because we are inside of an initializer + // closure, we return when an error/panic occurs. + unsafe { + ::pin_init::Init::__init( + #init, + ::core::ptr::addr_of_mut!((*#slot).#ident), + )? + }; + } + // SAFETY: TODO + #[allow(unused_variables)] + let #ident = unsafe { &mut (*#slot).#ident }; + } + } + } + InitializerField::Code { block: value, .. } => quote!(#[allow(unused_braces)] #value), + }; + res.extend(init); + if let Some(ident) = field.ident() { + // `mixed_site` ensures that the guard is not accessible to the user-controlled code. + let guard = format_ident!("__{ident}_guard", span = Span::mixed_site()); + guards.push(guard.clone()); + res.extend(quote! { + // Create the drop guard: + // + // We rely on macro hygiene to make it impossible for users to access this local + // variable. + // SAFETY: We forget the guard later when initialization has succeeded. + let #guard = unsafe { + ::pin_init::__internal::DropGuard::new( + ::core::ptr::addr_of_mut!((*slot).#ident) + ) + }; + }); + } + } + quote! { + #res + // If execution reaches this point, all fields have been initialized. Therefore we can now + // dismiss the guards by forgetting them. + #(::core::mem::forget(#guards);)* + } +} + +/// Generate the check for ensuring that every field has been initialized. +fn make_field_check( + fields: &Punctuated, + init_kind: InitKind, + path: &Path, +) -> TokenStream { + let fields = fields.iter().filter_map(|f| f.ident()); + match init_kind { + InitKind::Normal => quote! { + // We use unreachable code to ensure that all fields have been mentioned exactly once, + // this struct initializer will still be type-checked and complain with a very natural + // error message if a field is forgotten/mentioned more than once. + #[allow(unreachable_code, clippy::diverging_sub_expression)] + // SAFETY: this code is never executed. + let _ = || unsafe { + ::core::ptr::write(slot, #path { + #( + #fields: ::core::panic!(), + )* + }) + }; + }, + InitKind::Zeroing => quote! { + // We use unreachable code to ensure that all fields have been mentioned at most once. + // Since the user specified `..Zeroable::zeroed()` at the end, all missing fields will + // be zeroed. This struct initializer will still be type-checked and complain with a + // very natural error message if a field is mentioned more than once, or doesn't exist. + #[allow(unreachable_code, clippy::diverging_sub_expression, unused_assignments)] + // SAFETY: this code is never executed. + let _ = || unsafe { + let mut zeroed = ::core::mem::zeroed(); + ::core::ptr::write(slot, zeroed); + zeroed = ::core::mem::zeroed(); + ::core::ptr::write(slot, #path { + #( + #fields: ::core::panic!(), + )* + ..zeroed + }) + }; + }, + } +} + +impl Parse for Initializer { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let this = input.peek(Token![&]).then(|| input.parse()).transpose()?; + let path = input.parse()?; + let content; + let brace_token = braced!(content in input); + let mut fields = Punctuated::new(); + loop { + let lh = content.lookahead1(); + if lh.peek(End) || lh.peek(Token![..]) { + break; + } else if lh.peek(Ident) || lh.peek(Token![_]) { + fields.push_value(content.parse()?); + let lh = content.lookahead1(); + if lh.peek(End) { + break; + } else if lh.peek(Token![,]) { + fields.push_punct(content.parse()?); + } else { + return Err(lh.error()); + } + } else { + return Err(lh.error()); + } + } + let rest = content + .peek(Token![..]) + .then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?))) + .transpose()?; + let error = input + .peek(Token![?]) + .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) + .transpose()?; + Ok(Self { + this, + path, + brace_token, + fields, + rest, + error, + }) + } +} + +impl Parse for This { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(Self { + _and_token: input.parse()?, + ident: input.parse()?, + _in_token: input.parse()?, + }) + } +} + +impl Parse for InitializerField { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lh = input.lookahead1(); + if lh.peek(Token![_]) { + Ok(Self::Code { + _underscore_token: input.parse()?, + _colon_token: input.parse()?, + block: input.parse()?, + }) + } else if lh.peek(Ident) { + let ident = input.parse()?; + let lh = input.lookahead1(); + if lh.peek(Token![<-]) { + Ok(Self::Init { + ident, + _left_arrow_token: input.parse()?, + value: input.parse()?, + }) + } else if lh.peek(Token![:]) { + Ok(Self::Value { + ident, + value: Some((input.parse()?, input.parse()?)), + }) + } else if lh.peek(Token![,]) || lh.peek(End) { + Ok(Self::Value { ident, value: None }) + } else { + Err(lh.error()) + } + } else { + Err(lh.error()) + } + } +} diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 828f18db..360dd0ea 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -13,6 +13,7 @@ use proc_macro::TokenStream; use syn::parse_macro_input; +mod init; mod pin_data; mod pinned_drop; mod zeroable; @@ -44,3 +45,23 @@ pub fn derive_zeroable(input: TokenStream) -> TokenStream { pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream { zeroable::maybe_derive(parse_macro_input!(input as _)).into() } + +#[proc_macro] +pub fn init(input: TokenStream) -> TokenStream { + init::expand( + parse_macro_input!(input as _), + Some("::core::convert::Infallible"), + false, + ) + .into() +} + +#[proc_macro] +pub fn pin_init(input: TokenStream) -> TokenStream { + init::expand( + parse_macro_input!(input as _), + Some("::core::convert::Infallible"), + true, + ) + .into() +} diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 8a955139..83785cb0 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -317,6 +317,7 @@ fn generate_projections( quote! { #[doc = #docs] #[allow(dead_code)] + #[doc(hidden)] #vis struct #projection #generics { #(#fields_decl)* ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, @@ -332,6 +333,7 @@ fn generate_projections( /// /// These fields are **not** structurally pinned: #(#not_structurally_pinned_fields_docs)* + #[inline] #vis fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, ) -> #projection #ty_gen { @@ -396,7 +398,7 @@ fn generate_the_pin_data( quote!(__init), quote!(&'__slot mut #ty), quote!(slot), - quote!(#[doc = ""]), + quote!(), ) }; let slot_safety = format!( diff --git a/src/lib.rs b/src/lib.rs index d96f1b5b..43868fa1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -297,8 +297,6 @@ extern crate self as pin_init; #[doc(hidden)] pub mod __internal; -#[doc(hidden)] -pub mod macros; #[cfg(any(feature = "std", feature = "alloc"))] mod alloc; @@ -781,32 +779,7 @@ macro_rules! stack_try_pin_init { /// ``` /// /// [`NonNull`]: core::ptr::NonNull -// For a detailed example of how this macro works, see the module documentation of the hidden -// module `macros` inside of `macros.rs`. -#[macro_export] -macro_rules! pin_init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }) => { - $crate::pin_init!($(&$this in)? $t $(::<$($generics),*>)? { - $($fields)* - }? ::core::convert::Infallible) - }; - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }? $err:ty) => { - $crate::__init_internal!( - @this($($this)?), - @typ($t $(::<$($generics),*>)? ), - @fields($($fields)*), - @error($err), - @data(PinData, use_data), - @has_data(HasPinData, __pin_data), - @construct_closure(pin_init_from_closure), - @munch_fields($($fields)*), - ) - } -} +pub use pin_init_internal::pin_init; /// Construct an in-place, fallible initializer for `struct`s. /// @@ -844,32 +817,7 @@ macro_rules! pin_init { /// } /// # let _ = Box::init(BigBuf::new()); /// ``` -// For a detailed example of how this macro works, see the module documentation of the hidden -// module `macros` inside of `macros.rs`. -#[macro_export] -macro_rules! init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }) => { - $crate::init!($(&$this in)? $t $(::<$($generics),*>)? { - $($fields)* - }? ::core::convert::Infallible) - }; - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }? $err:ty) => { - $crate::__init_internal!( - @this($($this)?), - @typ($t $(::<$($generics),*>)?), - @fields($($fields)*), - @error($err), - @data(InitData, /*no use_data*/), - @has_data(HasInitData, __init_data), - @construct_closure(init_from_closure), - @munch_fields($($fields)*), - ) - } -} +pub use pin_init_internal::init; /// Asserts that a field on a struct using `#[pin_data]` is marked with `#[pin]` ie. that it is /// structurally pinned. diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index eea8adc5..00000000 --- a/src/macros.rs +++ /dev/null @@ -1,951 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! This module provides the macros that actually implement the proc-macros `pin_data` and -//! `pinned_drop`. It also contains `__init_internal`, the implementation of the -//! `{try_}{pin_}init!` macros. -//! -//! These macros should never be called directly, since they expect their input to be -//! in a certain format which is internal. If used incorrectly, these macros can lead to UB even in -//! safe code! Use the public facing macros instead. -//! -//! This architecture has been chosen because the kernel does not yet have access to `syn` which -//! would make matters a lot easier for implementing these as proc-macros. -//! -//! Since this library and the kernel implementation should diverge as little as possible, the same -//! approach has been taken here. -//! -//! # Macro expansion example -//! -//! This section is intended for readers trying to understand the macros in this module and the -//! `[try_][pin_]init!` macros from `lib.rs`. -//! -//! We will look at the following example: -//! -//! ```rust,ignore -//! #[pin_data] -//! #[repr(C)] -//! struct Bar { -//! #[pin] -//! t: T, -//! pub x: usize, -//! } -//! -//! impl Bar { -//! fn new(t: T) -> impl PinInit { -//! pin_init!(Self { t, x: 0 }) -//! } -//! } -//! -//! #[pin_data(PinnedDrop)] -//! struct Foo { -//! a: usize, -//! #[pin] -//! b: Bar, -//! } -//! -//! #[pinned_drop] -//! impl PinnedDrop for Foo { -//! fn drop(self: Pin<&mut Self>) { -//! println!("{self:p} is getting dropped."); -//! } -//! } -//! -//! let a = 42; -//! let initializer = pin_init!(Foo { -//! a, -//! b <- Bar::new(36), -//! }); -//! ``` -//! -//! This example includes the most common and important features of the pin-init API. -//! -//! Below you can find individual section about the different macro invocations. Here are some -//! general things we need to take into account when designing macros: -//! - use global paths, similarly to file paths, these start with the separator: `::core::panic!()` -//! this ensures that the correct item is used, since users could define their own `mod core {}` -//! and then their own `panic!` inside to execute arbitrary code inside of our macro. -//! - macro `unsafe` hygiene: we need to ensure that we do not expand arbitrary, user-supplied -//! expressions inside of an `unsafe` block in the macro, because this would allow users to do -//! `unsafe` operations without an associated `unsafe` block. -//! -//! ## `#[pin_data]` on `Bar` -//! -//! This macro is used to specify which fields are structurally pinned and which fields are not. It -//! is placed on the struct definition and allows `#[pin]` to be placed on the fields. -//! -//! Here is the definition of `Bar` from our example: -//! -//! ```rust,ignore -//! #[pin_data] -//! #[repr(C)] -//! struct Bar { -//! #[pin] -//! t: T, -//! pub x: usize, -//! } -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! // Firstly the normal definition of the struct, attributes are preserved: -//! #[repr(C)] -//! struct Bar { -//! t: T, -//! pub x: usize, -//! } -//! // Then an anonymous constant is defined, this is because we do not want any code to access the -//! // types that we define inside: -//! const _: () = { -//! // We define the pin-data carrying struct, it is a ZST and needs to have the same generics, -//! // since we need to implement access functions for each field and thus need to know its -//! // type. -//! struct __ThePinData { -//! __phantom: ::core::marker::PhantomData) -> Bar>, -//! } -//! // We implement `Copy` for the pin-data struct, since all functions it defines will take -//! // `self` by value. -//! impl ::core::clone::Clone for __ThePinData { -//! fn clone(&self) -> Self { -//! *self -//! } -//! } -//! impl ::core::marker::Copy for __ThePinData {} -//! // For every field of `Bar`, the pin-data struct will define a function with the same name -//! // and accessor (`pub` or `pub(crate)` etc.). This function will take a pointer to the -//! // field (`slot`) and a `PinInit` or `Init` depending on the projection kind of the field -//! // (if pinning is structural for the field, then `PinInit` otherwise `Init`). -//! #[allow(dead_code)] -//! impl __ThePinData { -//! unsafe fn t( -//! self, -//! slot: *mut T, -//! // Since `t` is `#[pin]`, this is `PinInit`. -//! init: impl ::pin_init::PinInit, -//! ) -> ::core::result::Result<(), E> { -//! unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } -//! } -//! pub unsafe fn x( -//! self, -//! slot: *mut usize, -//! // Since `x` is not `#[pin]`, this is `Init`. -//! init: impl ::pin_init::Init, -//! ) -> ::core::result::Result<(), E> { -//! unsafe { ::pin_init::Init::__init(init, slot) } -//! } -//! } -//! // Implement the internal `HasPinData` trait that associates `Bar` with the pin-data struct -//! // that we constructed above. -//! unsafe impl ::pin_init::__internal::HasPinData for Bar { -//! type PinData = __ThePinData; -//! unsafe fn __pin_data() -> Self::PinData { -//! __ThePinData { -//! __phantom: ::core::marker::PhantomData, -//! } -//! } -//! } -//! // Implement the internal `PinData` trait that marks the pin-data struct as a pin-data -//! // struct. This is important to ensure that no user can implement a rogue `__pin_data` -//! // function without using `unsafe`. -//! unsafe impl ::pin_init::__internal::PinData for __ThePinData { -//! type Datee = Bar; -//! } -//! // Now we only want to implement `Unpin` for `Bar` when every structurally pinned field is -//! // `Unpin`. In other words, whether `Bar` is `Unpin` only depends on structurally pinned -//! // fields (those marked with `#[pin]`). These fields will be listed in this struct, in our -//! // case no such fields exist, hence this is almost empty. The two phantomdata fields exist -//! // for two reasons: -//! // - `__phantom`: every generic must be used, since we cannot really know which generics -//! // are used, we declare all and then use everything here once. -//! // - `__phantom_pin`: uses the `'__pin` lifetime and ensures that this struct is invariant -//! // over it. The lifetime is needed to work around the limitation that trait bounds must -//! // not be trivial, e.g. the user has a `#[pin] PhantomPinned` field -- this is -//! // unconditionally `!Unpin` and results in an error. The lifetime tricks the compiler -//! // into accepting these bounds regardless. -//! #[allow(dead_code)] -//! struct __Unpin<'__pin, T> { -//! __phantom_pin: ::core::marker::PhantomData &'__pin ()>, -//! __phantom: ::core::marker::PhantomData) -> Bar>, -//! // Our only `#[pin]` field is `t`. -//! t: T, -//! } -//! #[doc(hidden)] -//! impl<'__pin, T> ::core::marker::Unpin for Bar -//! where -//! __Unpin<'__pin, T>: ::core::marker::Unpin, -//! {} -//! // Now we need to ensure that `Bar` does not implement `Drop`, since that would give users -//! // access to `&mut self` inside of `drop` even if the struct was pinned. This could lead to -//! // UB with only safe code, so we disallow this by giving a trait implementation error using -//! // a direct impl and a blanket implementation. -//! trait MustNotImplDrop {} -//! // Normally `Drop` bounds do not have the correct semantics, but for this purpose they do -//! // (normally people want to know if a type has any kind of drop glue at all, here we want -//! // to know if it has any kind of custom drop glue, which is exactly what this bound does). -//! #[expect(drop_bounds)] -//! impl MustNotImplDrop for T {} -//! impl MustNotImplDrop for Bar {} -//! // Here comes a convenience check, if one implemented `PinnedDrop`, but forgot to add it to -//! // `#[pin_data]`, then this will error with the same mechanic as above, this is not needed -//! // for safety, but a good sanity check, since no normal code calls `PinnedDrop::drop`. -//! #[expect(non_camel_case_types)] -//! trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} -//! impl< -//! T: ::pin_init::PinnedDrop, -//! > UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} -//! impl UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for Bar {} -//! }; -//! ``` -//! -//! ## `pin_init!` in `impl Bar` -//! -//! This macro creates an pin-initializer for the given struct. It requires that the struct is -//! annotated by `#[pin_data]`. -//! -//! Here is the impl on `Bar` defining the new function: -//! -//! ```rust,ignore -//! impl Bar { -//! fn new(t: T) -> impl PinInit { -//! pin_init!(Self { t, x: 0 }) -//! } -//! } -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! impl Bar { -//! fn new(t: T) -> impl PinInit { -//! { -//! // We do not want to allow arbitrary returns, so we declare this type as the `Ok` -//! // return type and shadow it later when we insert the arbitrary user code. That way -//! // there will be no possibility of returning without `unsafe`. -//! struct __InitOk; -//! // Get the data about fields from the supplied type. -//! // - the function is unsafe, hence the unsafe block -//! // - we `use` the `HasPinData` trait in the block, it is only available in that -//! // scope. -//! let data = unsafe { -//! use ::pin_init::__internal::HasPinData; -//! Self::__pin_data() -//! }; -//! // Ensure that `data` really is of type `PinData` and help with type inference: -//! let init = ::pin_init::__internal::PinData::make_closure::< -//! _, -//! __InitOk, -//! ::core::convert::Infallible, -//! >(data, move |slot| { -//! { -//! // Shadow the structure so it cannot be used to return early. If a user -//! // tries to write `return Ok(__InitOk)`, then they get a type error, -//! // since that will refer to this struct instead of the one defined -//! // above. -//! struct __InitOk; -//! // This is the expansion of `t,`, which is syntactic sugar for `t: t,`. -//! { -//! unsafe { ::core::ptr::write(::core::addr_of_mut!((*slot).t), t) }; -//! } -//! // Since initialization could fail later (not in this case, since the -//! // error type is `Infallible`) we will need to drop this field if there -//! // is an error later. This `DropGuard` will drop the field when it gets -//! // dropped and has not yet been forgotten. -//! let __t_guard = unsafe { -//! ::pin_init::__internal::DropGuard::new(::core::addr_of_mut!((*slot).t)) -//! }; -//! // Expansion of `x: 0,`: -//! // Since this can be an arbitrary expression we cannot place it inside -//! // of the `unsafe` block, so we bind it here. -//! { -//! let x = 0; -//! unsafe { ::core::ptr::write(::core::addr_of_mut!((*slot).x), x) }; -//! } -//! // We again create a `DropGuard`. -//! let __x_guard = unsafe { -//! ::pin_init::__internal::DropGuard::new(::core::addr_of_mut!((*slot).x)) -//! }; -//! // Since initialization has successfully completed, we can now forget -//! // the guards. This is not `mem::forget`, since we only have -//! // `&DropGuard`. -//! ::core::mem::forget(__x_guard); -//! ::core::mem::forget(__t_guard); -//! // Here we use the type checker to ensure that every field has been -//! // initialized exactly once, since this is `if false` it will never get -//! // executed, but still type-checked. -//! // Additionally we abuse `slot` to automatically infer the correct type -//! // for the struct. This is also another check that every field is -//! // accessible from this scope. -//! #[allow(unreachable_code, clippy::diverging_sub_expression)] -//! let _ = || { -//! unsafe { -//! ::core::ptr::write( -//! slot, -//! Self { -//! // We only care about typecheck finding every field -//! // here, the expression does not matter, just conjure -//! // one using `panic!()`: -//! t: ::core::panic!(), -//! x: ::core::panic!(), -//! }, -//! ); -//! }; -//! }; -//! } -//! // We leave the scope above and gain access to the previously shadowed -//! // `__InitOk` that we need to return. -//! Ok(__InitOk) -//! }); -//! // Change the return type from `__InitOk` to `()`. -//! let init = move | -//! slot, -//! | -> ::core::result::Result<(), ::core::convert::Infallible> { -//! init(slot).map(|__InitOk| ()) -//! }; -//! // Construct the initializer. -//! let init = unsafe { -//! ::pin_init::pin_init_from_closure::< -//! _, -//! ::core::convert::Infallible, -//! >(init) -//! }; -//! init -//! } -//! } -//! } -//! ``` -//! -//! ## `#[pin_data]` on `Foo` -//! -//! Since we already took a look at `#[pin_data]` on `Bar`, this section will only explain the -//! differences/new things in the expansion of the `Foo` definition: -//! -//! ```rust,ignore -//! #[pin_data(PinnedDrop)] -//! struct Foo { -//! a: usize, -//! #[pin] -//! b: Bar, -//! } -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! struct Foo { -//! a: usize, -//! b: Bar, -//! } -//! const _: () = { -//! struct __ThePinData { -//! __phantom: ::core::marker::PhantomData Foo>, -//! } -//! impl ::core::clone::Clone for __ThePinData { -//! fn clone(&self) -> Self { -//! *self -//! } -//! } -//! impl ::core::marker::Copy for __ThePinData {} -//! #[allow(dead_code)] -//! impl __ThePinData { -//! unsafe fn b( -//! self, -//! slot: *mut Bar, -//! init: impl ::pin_init::PinInit, E>, -//! ) -> ::core::result::Result<(), E> { -//! unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } -//! } -//! unsafe fn a( -//! self, -//! slot: *mut usize, -//! init: impl ::pin_init::Init, -//! ) -> ::core::result::Result<(), E> { -//! unsafe { ::pin_init::Init::__init(init, slot) } -//! } -//! } -//! unsafe impl ::pin_init::__internal::HasPinData for Foo { -//! type PinData = __ThePinData; -//! unsafe fn __pin_data() -> Self::PinData { -//! __ThePinData { -//! __phantom: ::core::marker::PhantomData, -//! } -//! } -//! } -//! unsafe impl ::pin_init::__internal::PinData for __ThePinData { -//! type Datee = Foo; -//! } -//! #[allow(dead_code)] -//! struct __Unpin<'__pin> { -//! __phantom_pin: ::core::marker::PhantomData &'__pin ()>, -//! __phantom: ::core::marker::PhantomData Foo>, -//! b: Bar, -//! } -//! #[doc(hidden)] -//! impl<'__pin> ::core::marker::Unpin for Foo -//! where -//! __Unpin<'__pin>: ::core::marker::Unpin, -//! {} -//! // Since we specified `PinnedDrop` as the argument to `#[pin_data]`, we expect `Foo` to -//! // implement `PinnedDrop`. Thus we do not need to prevent `Drop` implementations like -//! // before, instead we implement `Drop` here and delegate to `PinnedDrop`. -//! impl ::core::ops::Drop for Foo { -//! fn drop(&mut self) { -//! // Since we are getting dropped, no one else has a reference to `self` and thus we -//! // can assume that we never move. -//! let pinned = unsafe { ::core::pin::Pin::new_unchecked(self) }; -//! // Create the unsafe token that proves that we are inside of a destructor, this -//! // type is only allowed to be created in a destructor. -//! let token = unsafe { ::pin_init::__internal::OnlyCallFromDrop::new() }; -//! ::pin_init::PinnedDrop::drop(pinned, token); -//! } -//! } -//! }; -//! ``` -//! -//! ## `#[pinned_drop]` on `impl PinnedDrop for Foo` -//! -//! This macro is used to implement the `PinnedDrop` trait, since that trait is `unsafe` and has an -//! extra parameter that should not be used at all. The macro hides that parameter. -//! -//! Here is the `PinnedDrop` impl for `Foo`: -//! -//! ```rust,ignore -//! #[pinned_drop] -//! impl PinnedDrop for Foo { -//! fn drop(self: Pin<&mut Self>) { -//! println!("{self:p} is getting dropped."); -//! } -//! } -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! // `unsafe`, full path and the token parameter are added, everything else stays the same. -//! unsafe impl ::pin_init::PinnedDrop for Foo { -//! fn drop(self: Pin<&mut Self>, _: ::pin_init::__internal::OnlyCallFromDrop) { -//! println!("{self:p} is getting dropped."); -//! } -//! } -//! ``` -//! -//! ## `pin_init!` on `Foo` -//! -//! Since we already took a look at `pin_init!` on `Bar`, this section will only show the expansion -//! of `pin_init!` on `Foo`: -//! -//! ```rust,ignore -//! let a = 42; -//! let initializer = pin_init!(Foo { -//! a, -//! b <- Bar::new(36), -//! }); -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! let a = 42; -//! let initializer = { -//! struct __InitOk; -//! let data = unsafe { -//! use ::pin_init::__internal::HasPinData; -//! Foo::__pin_data() -//! }; -//! let init = ::pin_init::__internal::PinData::make_closure::< -//! _, -//! __InitOk, -//! ::core::convert::Infallible, -//! >(data, move |slot| { -//! { -//! struct __InitOk; -//! { -//! unsafe { ::core::ptr::write(::core::addr_of_mut!((*slot).a), a) }; -//! } -//! let __a_guard = unsafe { -//! ::pin_init::__internal::DropGuard::new(::core::addr_of_mut!((*slot).a)) -//! }; -//! let init = Bar::new(36); -//! unsafe { data.b(::core::addr_of_mut!((*slot).b), b)? }; -//! let __b_guard = unsafe { -//! ::pin_init::__internal::DropGuard::new(::core::addr_of_mut!((*slot).b)) -//! }; -//! ::core::mem::forget(__b_guard); -//! ::core::mem::forget(__a_guard); -//! #[allow(unreachable_code, clippy::diverging_sub_expression)] -//! let _ = || { -//! unsafe { -//! ::core::ptr::write( -//! slot, -//! Foo { -//! a: ::core::panic!(), -//! b: ::core::panic!(), -//! }, -//! ); -//! }; -//! }; -//! } -//! Ok(__InitOk) -//! }); -//! let init = move | -//! slot, -//! | -> ::core::result::Result<(), ::core::convert::Infallible> { -//! init(slot).map(|__InitOk| ()) -//! }; -//! let init = unsafe { -//! ::pin_init::pin_init_from_closure::<_, ::core::convert::Infallible>(init) -//! }; -//! init -//! }; -//! ``` - -#[cfg(kernel)] -pub use ::macros::paste; -#[cfg(not(kernel))] -pub use ::paste::paste; - -/// The internal init macro. Do not call manually! -/// -/// This is called by the `{try_}{pin_}init!` macros with various inputs. -/// -/// This macro has multiple internal call configurations, these are always the very first ident: -/// - nothing: this is the base case and called by the `{try_}{pin_}init!` macros. -/// - `with_update_parsed`: when the `..Zeroable::init_zeroed()` syntax has been handled. -/// - `init_slot`: recursively creates the code that initializes all fields in `slot`. -/// - `make_initializer`: recursively create the struct initializer that guarantees that every -/// field has been initialized exactly once. -#[doc(hidden)] -#[macro_export] -macro_rules! __init_internal { - ( - @this($($this:ident)?), - @typ($t:path), - @fields($($fields:tt)*), - @error($err:ty), - // Either `PinData` or `InitData`, `$use_data` should only be present in the `PinData` - // case. - @data($data:ident, $($use_data:ident)?), - // `HasPinData` or `HasInitData`. - @has_data($has_data:ident, $get_data:ident), - // `pin_init_from_closure` or `init_from_closure`. - @construct_closure($construct_closure:ident), - @munch_fields(), - ) => { - $crate::__init_internal!(with_update_parsed: - @this($($this)?), - @typ($t), - @fields($($fields)*), - @error($err), - @data($data, $($use_data)?), - @has_data($has_data, $get_data), - @construct_closure($construct_closure), - @init_zeroed(), // Nothing means default behavior. - ) - }; - ( - @this($($this:ident)?), - @typ($t:path), - @fields($($fields:tt)*), - @error($err:ty), - // Either `PinData` or `InitData`, `$use_data` should only be present in the `PinData` - // case. - @data($data:ident, $($use_data:ident)?), - // `HasPinData` or `HasInitData`. - @has_data($has_data:ident, $get_data:ident), - // `pin_init_from_closure` or `init_from_closure`. - @construct_closure($construct_closure:ident), - @munch_fields(..Zeroable::init_zeroed()), - ) => { - $crate::__init_internal!(with_update_parsed: - @this($($this)?), - @typ($t), - @fields($($fields)*), - @error($err), - @data($data, $($use_data)?), - @has_data($has_data, $get_data), - @construct_closure($construct_closure), - @init_zeroed(()), // `()` means zero all fields not mentioned. - ) - }; - ( - @this($($this:ident)?), - @typ($t:path), - @fields($($fields:tt)*), - @error($err:ty), - // Either `PinData` or `InitData`, `$use_data` should only be present in the `PinData` - // case. - @data($data:ident, $($use_data:ident)?), - // `HasPinData` or `HasInitData`. - @has_data($has_data:ident, $get_data:ident), - // `pin_init_from_closure` or `init_from_closure`. - @construct_closure($construct_closure:ident), - @munch_fields($ignore:tt $($rest:tt)*), - ) => { - $crate::__init_internal!( - @this($($this)?), - @typ($t), - @fields($($fields)*), - @error($err), - @data($data, $($use_data)?), - @has_data($has_data, $get_data), - @construct_closure($construct_closure), - @munch_fields($($rest)*), - ) - }; - (with_update_parsed: - @this($($this:ident)?), - @typ($t:path), - @fields($($fields:tt)*), - @error($err:ty), - // Either `PinData` or `InitData`, `$use_data` should only be present in the `PinData` - // case. - @data($data:ident, $($use_data:ident)?), - // `HasPinData` or `HasInitData`. - @has_data($has_data:ident, $get_data:ident), - // `pin_init_from_closure` or `init_from_closure`. - @construct_closure($construct_closure:ident), - @init_zeroed($($init_zeroed:expr)?), - ) => {{ - // We do not want to allow arbitrary returns, so we declare this type as the `Ok` return - // type and shadow it later when we insert the arbitrary user code. That way there will be - // no possibility of returning without `unsafe`. - struct __InitOk; - // Get the data about fields from the supplied type. - // - // SAFETY: TODO. - let data = unsafe { - use $crate::__internal::$has_data; - // Here we abuse `paste!` to retokenize `$t`. Declarative macros have some internal - // information that is associated to already parsed fragments, so a path fragment - // cannot be used in this position. Doing the retokenization results in valid rust - // code. - $crate::macros::paste!($t::$get_data()) - }; - // Ensure that `data` really is of type `$data` and help with type inference: - let init = $crate::__internal::$data::make_closure::<_, __InitOk, $err>( - data, - move |slot| { - { - // Shadow the structure so it cannot be used to return early. - struct __InitOk; - // If `$init_zeroed` is present we should zero the slot now and not emit an - // error when fields are missing (since they will be zeroed). We also have to - // check that the type actually implements `Zeroable`. - $({ - fn assert_zeroable(_: *mut T) {} - // Ensure that the struct is indeed `Zeroable`. - assert_zeroable(slot); - // SAFETY: The type implements `Zeroable` by the check above. - unsafe { ::core::ptr::write_bytes(slot, 0, 1) }; - $init_zeroed // This will be `()` if set. - })? - // Create the `this` so it can be referenced by the user inside of the - // expressions creating the individual fields. - $(let $this = unsafe { ::core::ptr::NonNull::new_unchecked(slot) };)? - // Initialize every field. - $crate::__init_internal!(init_slot($($use_data)?): - @data(data), - @slot(slot), - @guards(), - @munch_fields($($fields)*,), - ); - // We use unreachable code to ensure that all fields have been mentioned exactly - // once, this struct initializer will still be type-checked and complain with a - // very natural error message if a field is forgotten/mentioned more than once. - #[allow(unreachable_code, clippy::diverging_sub_expression)] - let _ = || { - $crate::__init_internal!(make_initializer: - @slot(slot), - @type_name($t), - @munch_fields($($fields)*,), - @acc(), - ); - }; - } - Ok(__InitOk) - } - ); - let init = move |slot| -> ::core::result::Result<(), $err> { - init(slot).map(|__InitOk| ()) - }; - // SAFETY: TODO. - let init = unsafe { $crate::$construct_closure::<_, $err>(init) }; - init - }}; - (init_slot($($use_data:ident)?): - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - @munch_fields($(..Zeroable::init_zeroed())? $(,)?), - ) => { - // Endpoint of munching, no fields are left. If execution reaches this point, all fields - // have been initialized. Therefore we can now dismiss the guards by forgetting them. - $(::core::mem::forget($guards);)* - }; - (init_slot($($use_data:ident)?): - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // arbitrary code block - @munch_fields(_: { $($code:tt)* }, $($rest:tt)*), - ) => { - { $($code)* } - $crate::__init_internal!(init_slot($($use_data)?): - @data($data), - @slot($slot), - @guards($($guards,)*), - @munch_fields($($rest)*), - ); - }; - (init_slot($use_data:ident): // `use_data` is present, so we use the `data` to init fields. - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // In-place initialization syntax. - @munch_fields($field:ident <- $val:expr, $($rest:tt)*), - ) => { - let init = $val; - // Call the initializer. - // - // SAFETY: `slot` is valid, because we are inside of an initializer closure, we - // return when an error/panic occurs. - // We also use the `data` to require the correct trait (`Init` or `PinInit`) for `$field`. - unsafe { $data.$field(::core::ptr::addr_of_mut!((*$slot).$field), init)? }; - // SAFETY: - // - the project function does the correct field projection, - // - the field has been initialized, - // - the reference is only valid until the end of the initializer. - #[allow(unused_variables)] - let $field = $crate::macros::paste!(unsafe { $data.[< __project_ $field >](&mut (*$slot).$field) }); - - // Create the drop guard: - // - // We rely on macro hygiene to make it impossible for users to access this local variable. - // We use `paste!` to create new hygiene for `$field`. - $crate::macros::paste! { - // SAFETY: We forget the guard later when initialization has succeeded. - let [< __ $field _guard >] = unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) - }; - - $crate::__init_internal!(init_slot($use_data): - @data($data), - @slot($slot), - @guards([< __ $field _guard >], $($guards,)*), - @munch_fields($($rest)*), - ); - } - }; - (init_slot(): // No `use_data`, so we use `Init::__init` directly. - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // In-place initialization syntax. - @munch_fields($field:ident <- $val:expr, $($rest:tt)*), - ) => { - let init = $val; - // Call the initializer. - // - // SAFETY: `slot` is valid, because we are inside of an initializer closure, we - // return when an error/panic occurs. - unsafe { $crate::Init::__init(init, ::core::ptr::addr_of_mut!((*$slot).$field))? }; - - // SAFETY: - // - the field is not structurally pinned, since the line above must compile, - // - the field has been initialized, - // - the reference is only valid until the end of the initializer. - #[allow(unused_variables)] - let $field = unsafe { &mut (*$slot).$field }; - - // Create the drop guard: - // - // We rely on macro hygiene to make it impossible for users to access this local variable. - // We use `paste!` to create new hygiene for `$field`. - $crate::macros::paste! { - // SAFETY: We forget the guard later when initialization has succeeded. - let [< __ $field _guard >] = unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) - }; - - $crate::__init_internal!(init_slot(): - @data($data), - @slot($slot), - @guards([< __ $field _guard >], $($guards,)*), - @munch_fields($($rest)*), - ); - } - }; - (init_slot(): // No `use_data`, so all fields are not structurally pinned - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // Init by-value. - @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), - ) => { - { - $(let $field = $val;)? - // Initialize the field. - // - // SAFETY: The memory at `slot` is uninitialized. - unsafe { ::core::ptr::write(::core::ptr::addr_of_mut!((*$slot).$field), $field) }; - } - - #[allow(unused_variables)] - // SAFETY: - // - the field is not structurally pinned, since no `use_data` was required to create this - // initializer, - // - the field has been initialized, - // - the reference is only valid until the end of the initializer. - let $field = unsafe { &mut (*$slot).$field }; - - // Create the drop guard: - // - // We rely on macro hygiene to make it impossible for users to access this local variable. - // We use `paste!` to create new hygiene for `$field`. - $crate::macros::paste! { - // SAFETY: We forget the guard later when initialization has succeeded. - let [< __ $field _guard >] = unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) - }; - - $crate::__init_internal!(init_slot(): - @data($data), - @slot($slot), - @guards([< __ $field _guard >], $($guards,)*), - @munch_fields($($rest)*), - ); - } - }; - (init_slot($use_data:ident): - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // Init by-value. - @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), - ) => { - { - $(let $field = $val;)? - // Initialize the field. - // - // SAFETY: The memory at `slot` is uninitialized. - unsafe { ::core::ptr::write(::core::ptr::addr_of_mut!((*$slot).$field), $field) }; - } - // SAFETY: - // - the project function does the correct field projection, - // - the field has been initialized, - // - the reference is only valid until the end of the initializer. - #[allow(unused_variables)] - let $field = $crate::macros::paste!(unsafe { $data.[< __project_ $field >](&mut (*$slot).$field) }); - - // Create the drop guard: - // - // We rely on macro hygiene to make it impossible for users to access this local variable. - // We use `paste!` to create new hygiene for `$field`. - $crate::macros::paste! { - // SAFETY: We forget the guard later when initialization has succeeded. - let [< __ $field _guard >] = unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) - }; - - $crate::__init_internal!(init_slot($use_data): - @data($data), - @slot($slot), - @guards([< __ $field _guard >], $($guards,)*), - @munch_fields($($rest)*), - ); - } - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields(_: { $($code:tt)* }, $($rest:tt)*), - @acc($($acc:tt)*), - ) => { - // code blocks are ignored for the initializer check - $crate::__init_internal!(make_initializer: - @slot($slot), - @type_name($t), - @munch_fields($($rest)*), - @acc($($acc)*), - ); - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields(..Zeroable::init_zeroed() $(,)?), - @acc($($acc:tt)*), - ) => { - // Endpoint, nothing more to munch, create the initializer. Since the users specified - // `..Zeroable::init_zeroed()`, the slot will already have been zeroed and all field that have - // not been overwritten are thus zero and initialized. We still check that all fields are - // actually accessible by using the struct update syntax ourselves. - // We are inside of a closure that is never executed and thus we can abuse `slot` to - // get the correct type inference here: - #[allow(unused_assignments)] - unsafe { - let mut zeroed = ::core::mem::zeroed(); - // We have to use type inference here to make zeroed have the correct type. This does - // not get executed, so it has no effect. - ::core::ptr::write($slot, zeroed); - zeroed = ::core::mem::zeroed(); - // Here we abuse `paste!` to retokenize `$t`. Declarative macros have some internal - // information that is associated to already parsed fragments, so a path fragment - // cannot be used in this position. Doing the retokenization results in valid rust - // code. - $crate::macros::paste!( - ::core::ptr::write($slot, $t { - $($acc)* - ..zeroed - }); - ); - } - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields($(,)?), - @acc($($acc:tt)*), - ) => { - // Endpoint, nothing more to munch, create the initializer. - // Since we are in the closure that is never called, this will never get executed. - // We abuse `slot` to get the correct type inference here: - // - // SAFETY: TODO. - unsafe { - // Here we abuse `paste!` to retokenize `$t`. Declarative macros have some internal - // information that is associated to already parsed fragments, so a path fragment - // cannot be used in this position. Doing the retokenization results in valid rust - // code. - $crate::macros::paste!( - ::core::ptr::write($slot, $t { - $($acc)* - }); - ); - } - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields($field:ident <- $val:expr, $($rest:tt)*), - @acc($($acc:tt)*), - ) => { - $crate::__init_internal!(make_initializer: - @slot($slot), - @type_name($t), - @munch_fields($($rest)*), - @acc($($acc)* $field: ::core::panic!(),), - ); - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), - @acc($($acc:tt)*), - ) => { - $crate::__init_internal!(make_initializer: - @slot($slot), - @type_name($t), - @munch_fields($($rest)*), - @acc($($acc)* $field: ::core::panic!(),), - ); - }; -} diff --git a/tests/ui/compile-fail/init/colon_instead_of_arrow.stderr b/tests/ui/compile-fail/init/colon_instead_of_arrow.stderr index f7be4a2f..f5d9ee7b 100644 --- a/tests/ui/compile-fail/init/colon_instead_of_arrow.stderr +++ b/tests/ui/compile-fail/init/colon_instead_of_arrow.stderr @@ -1,14 +1,13 @@ error[E0308]: mismatched types - --> tests/ui/compile-fail/init/colon_instead_of_arrow.rs:21:9 + --> tests/ui/compile-fail/init/colon_instead_of_arrow.rs:21:31 | 14 | fn new() -> impl PinInit { | ------------------ the found opaque type ... 21 | pin_init!(Self { bar: Bar::new() }) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | expected `Bar`, found opaque type - | arguments to this function are incorrect + | --- ^^^^^^^^^^ expected `Bar`, found opaque type + | | + | arguments to this function are incorrect | = note: expected struct `Bar` found opaque type `impl pin_init::PinInit` @@ -17,4 +16,3 @@ note: function defined here | | pub const unsafe fn write(dst: *mut T, src: T) { | ^^^^^ - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/field_value_wrong_type.stderr b/tests/ui/compile-fail/init/field_value_wrong_type.stderr index 5b91b5c7..f6470767 100644 --- a/tests/ui/compile-fail/init/field_value_wrong_type.stderr +++ b/tests/ui/compile-fail/init/field_value_wrong_type.stderr @@ -1,15 +1,13 @@ error[E0308]: mismatched types - --> tests/ui/compile-fail/init/field_value_wrong_type.rs:8:13 + --> tests/ui/compile-fail/init/field_value_wrong_type.rs:8:28 | 8 | let _ = init!(Foo { a: () }); - | ^^^^^^^^^^^^^^^^^^^^ - | | - | expected `usize`, found `()` - | arguments to this function are incorrect + | - ^^ expected `usize`, found `()` + | | + | arguments to this function are incorrect | note: function defined here --> $RUST/core/src/ptr/mod.rs | | pub const unsafe fn write(dst: *mut T, src: T) { | ^^^^^ - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/invalid_init.stderr b/tests/ui/compile-fail/init/invalid_init.stderr index b80864de..c38e6be2 100644 --- a/tests/ui/compile-fail/init/invalid_init.stderr +++ b/tests/ui/compile-fail/init/invalid_init.stderr @@ -1,16 +1,13 @@ error[E0277]: the trait bound `impl pin_init::PinInit: Init` is not satisfied - --> tests/ui/compile-fail/init/invalid_init.rs:18:13 + --> tests/ui/compile-fail/init/invalid_init.rs:19:16 | 18 | let _ = init!(Foo { - | _____________^ + | _____________- 19 | | bar <- Bar::new(), + | | ^^^^^^^^^^ the trait `Init` is not implemented for `impl pin_init::PinInit` 20 | | }); - | | ^ - | | | - | |______the trait `Init` is not implemented for `impl pin_init::PinInit` - | required by a bound introduced by this call + | |______- required by a bound introduced by this call | = help: the following other types implement trait `Init`: ChainInit Result - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/missing_comma.stderr b/tests/ui/compile-fail/init/missing_comma.stderr index 9e4c25db..dd16d94b 100644 --- a/tests/ui/compile-fail/init/missing_comma.stderr +++ b/tests/ui/compile-fail/init/missing_comma.stderr @@ -1,23 +1,5 @@ -error: no rules expected `c` +error: expected `}` or `,` --> tests/ui/compile-fail/init/missing_comma.rs:16:9 | 16 | c: Bar, - | ^ no rules expected this token in macro call - | -note: while trying to match `,` - --> src/macros.rs - | - | @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), - | ^ - -error: no rules expected `c` - --> tests/ui/compile-fail/init/missing_comma.rs:16:9 - | -16 | c: Bar, - | ^ no rules expected this token in macro call - | -note: while trying to match `,` - --> src/macros.rs - | - | @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), - | ^ + | ^ diff --git a/tests/ui/compile-fail/init/missing_comma_with_zeroable.stderr b/tests/ui/compile-fail/init/missing_comma_with_zeroable.stderr index 51ce37b1..865615d2 100644 --- a/tests/ui/compile-fail/init/missing_comma_with_zeroable.stderr +++ b/tests/ui/compile-fail/init/missing_comma_with_zeroable.stderr @@ -1,14 +1,10 @@ error[E0308]: mismatched types - --> tests/ui/compile-fail/init/missing_comma_with_zeroable.rs:11:13 + --> tests/ui/compile-fail/init/missing_comma_with_zeroable.rs:12:12 | -11 | let _ = init!(Foo { - | _____________^ -12 | | a: 0..Zeroable::init_zeroed() -13 | | }); - | | ^ - | | | - | |______expected `usize`, found `Range<{integer}>` - | arguments to this function are incorrect +12 | a: 0..Zeroable::init_zeroed() + | - ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `Range<{integer}>` + | | + | arguments to this function are incorrect | = note: expected type `usize` found struct `std::ops::Range<{integer}>` @@ -17,7 +13,6 @@ note: function defined here | | pub const unsafe fn write(dst: *mut T, src: T) { | ^^^^^ - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0063]: missing field `b` in initializer of `Foo` --> tests/ui/compile-fail/init/missing_comma_with_zeroable.rs:11:19 diff --git a/tests/ui/compile-fail/init/missing_error_type.stderr b/tests/ui/compile-fail/init/missing_error_type.stderr index 38a114ee..9e9a1478 100644 --- a/tests/ui/compile-fail/init/missing_error_type.stderr +++ b/tests/ui/compile-fail/init/missing_error_type.stderr @@ -1,11 +1,7 @@ -error: unexpected end of macro invocation - --> tests/ui/compile-fail/init/missing_error_type.rs:8:43 +error: unexpected end of input, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime + --> tests/ui/compile-fail/init/missing_error_type.rs:8:13 | 8 | let _ = init!(Foo { x: Box::new(0)? }?); - | ^ missing tokens in macro arguments + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -note: while trying to match meta-variable `$err:ty` - --> src/lib.rs - | - | }? $err:ty) => { - | ^^^^^^^ + = note: this error originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/missing_pin_data.stderr b/tests/ui/compile-fail/init/missing_pin_data.stderr index c228da5a..1215692b 100644 --- a/tests/ui/compile-fail/init/missing_pin_data.stderr +++ b/tests/ui/compile-fail/init/missing_pin_data.stderr @@ -10,4 +10,4 @@ error[E0599]: no associated item named `__pin_data` found for struct `Foo` in th = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `__pin_data`, perhaps you need to implement it: candidate #1: `HasPinData` - = note: this error originates in the macro `$crate::pin_init` which comes from the expansion of the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/no_error_coercion.stderr b/tests/ui/compile-fail/init/no_error_coercion.stderr index 37ba6916..974c3c15 100644 --- a/tests/ui/compile-fail/init/no_error_coercion.stderr +++ b/tests/ui/compile-fail/init/no_error_coercion.stderr @@ -1,5 +1,5 @@ error[E0277]: `?` couldn't convert the error to `std::alloc::AllocError` - --> tests/ui/compile-fail/init/no_error_coercion.rs:16:9 + --> tests/ui/compile-fail/init/no_error_coercion.rs:19:22 | 16 | / init!(Self { 17 | | a: Box::new(42), @@ -7,8 +7,8 @@ error[E0277]: `?` couldn't convert the error to `std::alloc::AllocError` 19 | | }? AllocError) | | ^ | | | - | |______________________this can't be annotated with `?` because it has type `Result<_, Infallible>` - | the trait `From` is not implemented for `std::alloc::AllocError` + | |______________________the trait `From` is not implemented for `std::alloc::AllocError` + | this can't be annotated with `?` because it has type `Result<_, Infallible>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/shadowing_field.stderr b/tests/ui/compile-fail/init/shadowing_field.stderr index 4846adb0..d9ba3f59 100644 --- a/tests/ui/compile-fail/init/shadowing_field.stderr +++ b/tests/ui/compile-fail/init/shadowing_field.stderr @@ -1,15 +1,17 @@ error[E0308]: mismatched types - --> tests/ui/compile-fail/init/shadowing_field.rs:10:13 + --> tests/ui/compile-fail/init/shadowing_field.rs:10:31 | 10 | let _ = init!(Foo { x, y: x }); - | ^^^^^^^^^^^^^^^^^^^^^^ - | | - | expected `usize`, found `&mut usize` - | arguments to this function are incorrect + | - ^ expected `usize`, found `&mut usize` + | | + | arguments to this function are incorrect | note: function defined here --> $RUST/core/src/ptr/mod.rs | | pub const unsafe fn write(dst: *mut T, src: T) { | ^^^^^ - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider dereferencing the borrow + | +10 | let _ = init!(Foo { x, y: *x }); + | + diff --git a/tests/ui/compile-fail/init/wrong_generics.stderr b/tests/ui/compile-fail/init/wrong_generics.stderr index cb1b1ef5..03960aa1 100644 --- a/tests/ui/compile-fail/init/wrong_generics.stderr +++ b/tests/ui/compile-fail/init/wrong_generics.stderr @@ -1,11 +1,19 @@ -error: no rules expected `<` +error: comparison operators cannot be chained --> tests/ui/compile-fail/init/wrong_generics.rs:7:22 | 7 | let _ = init!(Foo<()> { - | ^ no rules expected this token in macro call + | ^ ^ | -note: while trying to match `{` - --> src/lib.rs +help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments | - | ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - | ^ +7 | let _ = init!(Foo::<()> { + | ++ + +error: comparison operators cannot be chained + --> tests/ui/compile-fail/init/wrong_generics.rs:7:22 + | +7 | let _ = init!(Foo<()> { + | ^ ^ + | + = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + = help: or use `(...)` if you meant to specify fn arguments diff --git a/tests/ui/compile-fail/init/wrong_generics2.stderr b/tests/ui/compile-fail/init/wrong_generics2.stderr index 8c57961e..66edc9c3 100644 --- a/tests/ui/compile-fail/init/wrong_generics2.stderr +++ b/tests/ui/compile-fail/init/wrong_generics2.stderr @@ -1,51 +1,3 @@ -error: struct literal body without path - --> tests/ui/compile-fail/init/wrong_generics2.rs:7:13 - | -7 | let _ = init!(Foo::<(), ()> { - | _____________^ -8 | | value <- (), -9 | | }); - | |______^ - | - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) -help: you might have forgotten to add the struct literal inside the block - --> src/macros.rs - | - ~ ::core::ptr::write($slot, $t { SomeStruct { - | $($acc)* - ~ } }); - | - -error: expected one of `)`, `,`, `.`, `?`, or an operator, found `{` - --> tests/ui/compile-fail/init/wrong_generics2.rs:7:13 - | -7 | let _ = init!(Foo::<(), ()> { - | _____________^ -8 | | value <- (), -9 | | }); - | | ^ - | | | - | |______expected one of `)`, `,`, `.`, `?`, or an operator - | help: missing `,` - | - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0423]: expected value, found struct `Foo` - --> tests/ui/compile-fail/init/wrong_generics2.rs:7:13 - | -3 | / struct Foo { -4 | | value: T, -5 | | } - | |_- `Foo` defined here -6 | fn main() { -7 | let _ = init!(Foo::<(), ()> { - | _____________^ -8 | | value <- (), -9 | | }); - | |______^ help: use struct literal syntax instead: `Foo { value: val }` - | - = note: this error originates in the macro `$crate::init` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0107]: struct takes 1 generic argument but 2 generic arguments were supplied --> tests/ui/compile-fail/init/wrong_generics2.rs:7:19 | @@ -59,19 +11,3 @@ note: struct defined here, with 1 generic parameter: `T` | 3 | struct Foo { | ^^^ - - -error[E0061]: this function takes 2 arguments but 3 arguments were supplied - --> tests/ui/compile-fail/init/wrong_generics2.rs:7:13 - | -7 | let _ = init!(Foo::<(), ()> { - | _____________^ -8 | | value <- (), -9 | | }); - | |______^ unexpected argument #3 - | -note: function defined here - --> $RUST/core/src/ptr/mod.rs - | - | pub const unsafe fn write(dst: *mut T, src: T) { - | ^^^^^ - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/missing_pin.stderr b/tests/ui/compile-fail/pin_data/missing_pin.stderr index ced6d246..4bd17fbb 100644 --- a/tests/ui/compile-fail/pin_data/missing_pin.stderr +++ b/tests/ui/compile-fail/pin_data/missing_pin.stderr @@ -1,11 +1,10 @@ error[E0277]: the trait bound `impl PinInit: Init` is not satisfied - --> tests/ui/compile-fail/pin_data/missing_pin.rs:11:9 + --> tests/ui/compile-fail/pin_data/missing_pin.rs:12:18 | -11 | / pin_init!(Self { -12 | | a <- a, - | | - required by a bound introduced by this call -13 | | }) - | |__________^ the trait `Init` is not implemented for `impl PinInit` +12 | a <- a, + | - ^ the trait `Init` is not implemented for `impl PinInit` + | | + | required by a bound introduced by this call | = help: the trait `Init` is not implemented for `impl PinInit` but trait `Init, Infallible>` is implemented for it diff --git a/tests/ui/compile-fail/zeroable/with_comma.stderr b/tests/ui/compile-fail/zeroable/with_comma.stderr index ea217242..f172f31f 100644 --- a/tests/ui/compile-fail/zeroable/with_comma.stderr +++ b/tests/ui/compile-fail/zeroable/with_comma.stderr @@ -1,33 +1,5 @@ -error: no rules expected `,` - --> tests/ui/compile-fail/zeroable/with_comma.rs:11:13 +error: unexpected token, expected `}` + --> tests/ui/compile-fail/zeroable/with_comma.rs:13:34 | -11 | let _ = init!(Foo { - | _____________^ -12 | | a: 0, -13 | | ..Zeroable::init_zeroed(), -14 | | }); - | |______^ no rules expected this token in macro call - | -note: while trying to match `)` - --> src/macros.rs - | - | @munch_fields($(..Zeroable::init_zeroed())? $(,)?), - | ^ - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: no rules expected `,` - --> tests/ui/compile-fail/zeroable/with_comma.rs:11:13 - | -11 | let _ = init!(Foo { - | _____________^ -12 | | a: 0, -13 | | ..Zeroable::init_zeroed(), -14 | | }); - | |______^ no rules expected this token in macro call - | -note: while trying to match `)` - --> src/macros.rs - | - | @munch_fields(..Zeroable::init_zeroed() $(,)?), - | ^ - = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) +13 | ..Zeroable::init_zeroed(), + | ^ diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index af78aad7..44a4efa1 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -15,6 +15,7 @@ where } /// Pin-projections of [`Foo`] #[allow(dead_code)] +#[doc(hidden)] struct FooProjection< '__pin, 'a, @@ -39,6 +40,7 @@ where /// These fields are **not** structurally pinned: /// - `array` /// - `r` + #[inline] fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, ) -> FooProjection<'__pin, 'a, 'b, T, SIZE> { @@ -91,7 +93,6 @@ const _: () = { /// - `slot` is a valid pointer to uninitialized memory. /// - the caller does not touch `slot` when `Err` is returned, they are only permitted /// to deallocate. - /// unsafe fn array( self, slot: *mut [u8; 1024 * 1024], @@ -113,7 +114,6 @@ const _: () = { /// - `slot` is a valid pointer to uninitialized memory. /// - the caller does not touch `slot` when `Err` is returned, they are only permitted /// to deallocate. - /// unsafe fn r( self, slot: *mut &'b mut [&'a mut T; SIZE], diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index 58b5d908..fb59d866 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -6,6 +6,7 @@ struct Foo { } /// Pin-projections of [`Foo`] #[allow(dead_code)] +#[doc(hidden)] struct FooProjection<'__pin> { array: &'__pin mut [u8; 1024 * 1024], _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, @@ -19,6 +20,7 @@ impl Foo { /// /// These fields are **not** structurally pinned: /// - `array` + #[inline] fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, ) -> FooProjection<'__pin> { @@ -49,7 +51,6 @@ const _: () = { /// - `slot` is a valid pointer to uninitialized memory. /// - the caller does not touch `slot` when `Err` is returned, they are only permitted /// to deallocate. - /// unsafe fn array( self, slot: *mut [u8; 1024 * 1024], diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 247c8d4a..5e0f1250 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -6,6 +6,7 @@ struct Foo { } /// Pin-projections of [`Foo`] #[allow(dead_code)] +#[doc(hidden)] struct FooProjection<'__pin> { array: &'__pin mut [u8; 1024 * 1024], _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, @@ -19,6 +20,7 @@ impl Foo { /// /// These fields are **not** structurally pinned: /// - `array` + #[inline] fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, ) -> FooProjection<'__pin> { @@ -49,7 +51,6 @@ const _: () = { /// - `slot` is a valid pointer to uninitialized memory. /// - the caller does not touch `slot` when `Err` is returned, they are only permitted /// to deallocate. - /// unsafe fn array( self, slot: *mut [u8; 1024 * 1024], diff --git a/tests/ui/expand/simple-init.expanded.rs b/tests/ui/expand/simple-init.expanded.rs index 320efc95..c050a449 100644 --- a/tests/ui/expand/simple-init.expanded.rs +++ b/tests/ui/expand/simple-init.expanded.rs @@ -3,7 +3,7 @@ struct Foo {} fn main() { let _ = { struct __InitOk; - let data = unsafe { + let __data = unsafe { use ::pin_init::__internal::HasInitData; Foo::__init_data() }; @@ -12,16 +12,12 @@ fn main() { __InitOk, ::core::convert::Infallible, >( - data, + __data, move |slot| { { struct __InitOk; #[allow(unreachable_code, clippy::diverging_sub_expression)] - let _ = || { - unsafe { - ::core::ptr::write(slot, Foo {}); - }; - }; + let _ = || unsafe { ::core::ptr::write(slot, Foo {}) }; } Ok(__InitOk) }, From 53ff1a80d02cc1cad4cff14091a6d993faf932cb Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Wed, 22 Oct 2025 22:34:27 +0200 Subject: [PATCH 14/14] add `#[default_error()]` attribute to initializer macros The `#[default_error()]` attribute macro can be used to supply a default type as the error used for the `[pin_]init!` macros. This way one can easily define custom `try_[pin_]init!` variants that default to your project specific error type. Just write the following declarative macro: macro_rules! try_init { ($($args:tt)*) => { ::pin_init::init!( #[default_error(YourCustomErrorType)] $($args)* ) } } Signed-off-by: Benno Lossin --- internal/src/init.rs | 53 ++++++++++++++++++++++++++++++++++++++---- tests/default_error.rs | 16 +++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 tests/default_error.rs diff --git a/internal/src/init.rs b/internal/src/init.rs index c02a9969..e14bacc8 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -6,10 +6,11 @@ use syn::{ parse_quote, punctuated::Punctuated, spanned::Spanned, - token, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, + token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, }; pub struct Initializer { + attrs: Vec, this: Option, path: Path, brace_token: token::Brace, @@ -50,23 +51,44 @@ impl InitializerField { } } +enum InitializerAttribute { + DefaultError(DefaultErrorAttribute), +} + +struct DefaultErrorAttribute { + ty: Type, +} + pub(crate) fn expand( Initializer { + attrs, this, path, brace_token, fields, rest, - mut error, + error, }: Initializer, default_error: Option<&'static str>, pinned: bool, ) -> TokenStream { let mut errors = TokenStream::new(); + let mut error = error.map(|(_, err)| err); + if let Some(default_error) = attrs.iter().fold(None, |acc, attr| { + #[expect(irrefutable_let_patterns)] + if let InitializerAttribute::DefaultError(DefaultErrorAttribute { ty }) = attr { + Some(ty.clone()) + } else { + acc + } + }) { + error.get_or_insert(default_error); + } if let Some(default_error) = default_error { - error.get_or_insert((Default::default(), syn::parse_str(default_error).unwrap())); + error.get_or_insert(syn::parse_str(default_error).unwrap()); } - let error = error.map(|(_, err)| err).unwrap_or_else(|| { + + let error = error.unwrap_or_else(|| { errors.extend(quote_spanned!(brace_token.span.close()=> ::core::compile_error!("expected `? ` after `}`"); )); @@ -350,6 +372,7 @@ fn make_field_check( impl Parse for Initializer { fn parse(input: syn::parse::ParseStream) -> syn::Result { + let attrs = input.call(Attribute::parse_outer)?; let this = input.peek(Token![&]).then(|| input.parse()).transpose()?; let path = input.parse()?; let content; @@ -381,7 +404,19 @@ impl Parse for Initializer { .peek(Token![?]) .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) .transpose()?; + let attrs = attrs + .into_iter() + .map(|a| { + if a.path().is_ident("default_error") { + a.parse_args::() + .map(InitializerAttribute::DefaultError) + } else { + Err(syn::Error::new_spanned(a, "unknown initializer attribute")) + } + }) + .collect::, _>>()?; Ok(Self { + attrs, this, path, brace_token, @@ -392,6 +427,16 @@ impl Parse for Initializer { } } +impl Parse for DefaultErrorAttribute { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ty = input.parse()?; + if !input.peek(End) { + return Err(input.error("expected end of input")); + } + Ok(Self { ty }) + } +} + impl Parse for This { fn parse(input: syn::parse::ParseStream) -> syn::Result { Ok(Self { diff --git a/tests/default_error.rs b/tests/default_error.rs new file mode 100644 index 00000000..dc357626 --- /dev/null +++ b/tests/default_error.rs @@ -0,0 +1,16 @@ +#![allow(dead_code)] + +use pin_init::{init, Init}; + +struct Foo {} + +struct Error; + +impl Foo { + fn new() -> impl Init { + init!( + #[default_error(Error)] + Foo {} + ) + } +}