Skip to content

Commit

Permalink
internal: add syn version of [try_][pin_]init! macros
Browse files Browse the repository at this point in the history
Implement the `[try_][pin_]init!` derive macro using syn to simplify
parsing by not going through an additional declarative macro.
This not only simplifies the code by a lot, increasing maintainability
and making it easier to implement new features. But also improves the
user experience by improving the error messages one gets when giving
incorrect inputs to the macro.
For example, placing a `,` after `..Zeroable::zeroed()` is not allowed:

    use pin_init::*;

    #[derive(Zeroable)]
    struct Foo {
        a: usize,
        b: usize,
    }

    fn main() {
        let _ = init!(Foo {
            a: 0,
            ..Zeroable::zeroed(),
        });
    }

The declarative macro produces this error:

    error: no rules expected `,`
       |
    11 |       let _ = init!(Foo {
       |  _____________^
    12 | |         a: 0,
    13 | |         ..Zeroable::zeroed(),
    14 | |     });
       | |______^ no rules expected this token in macro call
       |
    note: while trying to match `)`
      --> src/macros.rs
       |
       |         @munch_fields($(..Zeroable::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 `,`
       |
    11 |       let _ = init!(Foo {
       |  _____________^
    12 | |         a: 0,
    13 | |         ..Zeroable::zeroed(),
    14 | |     });
       | |______^ no rules expected this token in macro call
       |
    note: while trying to match `)`
      --> src/macros.rs
       |
       |         @munch_fields(..Zeroable::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)

The syn version reduces this error to the much more manageable:

    error: unexpected token, expected `}`
       |
    12 |         ..Zeroable::zeroed(),
       |                             ^

This reimplementation is benefiting the most from syn, as can be seen in
this example. It declares a struct with a single generic, but then
supplies two type arguments in the initializer:

    use pin_init::*;

    struct Foo<T> {
        value: T,
    }
    fn main() {
        let _ = init!(Foo::<(), ()> {
            value <- (),
        });
    }

The declarative version emits the following unreadable mess of an error
(shortened for brevity of the commit message):

    error: struct literal body without path
      |
    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 {
      |9                    $($acc)*
      ~                 } });
      |

<...40 lines skipped...>

    error[E0061]: this function takes 2 arguments but 3 arguments were supplied
      |
    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<T>(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)

This error delightfully reduces to the simple and clear message:

    error[E0107]: struct takes 1 generic argument but 2 generic arguments were supplied
      |
    7 |     let _ = init!(Foo::<(), ()> {
      |                   ^^^     ---- help: remove the unnecessary generic argument
      |                   |
      |                   expected 1 generic argument
      |
    note: struct defined here, with 1 generic parameter: `T`
     --> tests/ui/compile-fail/init/wrong_generics2.rs:3:8
      |
    3 | struct Foo<T> {
      |        ^^^ -

The syn version is only enabled in the user-space version and disabled
in the kernel until syn becomes available there.

Signed-off-by: Benno Lossin <[email protected]>
  • Loading branch information
y86-dev committed Mar 4, 2025
1 parent cb44178 commit 21b549d
Show file tree
Hide file tree
Showing 17 changed files with 595 additions and 233 deletions.
404 changes: 404 additions & 0 deletions internal/src/init.rs

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ mod pinned_drop;
#[cfg(kernel)]
mod zeroable;

#[cfg(not(kernel))]
mod init;
#[cfg(not(kernel))]
#[path = "syn_pin_data.rs"]
mod pin_data;
Expand All @@ -56,3 +58,79 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
pub fn derive_zeroable(input: TokenStream) -> TokenStream {
zeroable::derive(input.into()).into()
}

#[cfg(kernel)]
#[proc_macro]
pub fn pin_init(input: TokenStream) -> TokenStream {
quote!(::pin_init::__internal_pin_init!(#input))
}

#[cfg(not(kernel))]
#[proc_macro]
pub fn pin_init(input: TokenStream) -> TokenStream {
use syn::parse_macro_input;
init::init(
parse_macro_input!(input as init::InPlaceInitializer),
true,
true,
)
.unwrap_or_else(|e| e.into_compile_error())
.into()
}

#[cfg(kernel)]
#[proc_macro]
pub fn init(input: TokenStream) -> TokenStream {
quote!(::pin_init::__internal_init!(#input))
}

#[cfg(not(kernel))]
#[proc_macro]
pub fn init(input: TokenStream) -> TokenStream {
use syn::parse_macro_input;
init::init(
parse_macro_input!(input as init::InPlaceInitializer),
true,
false,
)
.unwrap_or_else(|e| e.into_compile_error())
.into()
}

#[cfg(kernel)]
#[proc_macro]
pub fn try_pin_init(input: TokenStream) -> TokenStream {
quote!(::pin_init::__internal_try_pin_init!(#input))
}

#[cfg(not(kernel))]
#[proc_macro]
pub fn try_pin_init(input: TokenStream) -> TokenStream {
use syn::parse_macro_input;
init::init(
parse_macro_input!(input as init::InPlaceInitializer),
false,
true,
)
.unwrap_or_else(|e| e.into_compile_error())
.into()
}

#[cfg(kernel)]
#[proc_macro]
pub fn try_init(input: TokenStream) -> TokenStream {
quote!(::pin_init::__internal_try_init!(#input))
}

#[cfg(not(kernel))]
#[proc_macro]
pub fn try_init(input: TokenStream) -> TokenStream {
use syn::parse_macro_input;
init::init(
parse_macro_input!(input as init::InPlaceInitializer),
false,
false,
)
.unwrap_or_else(|e| e.into_compile_error())
.into()
}
62 changes: 62 additions & 0 deletions src/__internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,65 @@ unsafe impl<T: ?Sized> PinInit<T, ()> for AlwaysFail<T> {
Err(())
}
}

#[cfg(kernel)]
#[macro_export]
macro_rules! __internal_init {
($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? {
$($fields:tt)*
}) => {
$crate::try_init!($(&$this in)? $t $(::<$($generics),*>)? {
$($fields)*
}? ::core::convert::Infallible)
};
}

#[cfg(kernel)]
#[macro_export]
macro_rules! __internal_pin_init {
($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? {
$($fields:tt)*
}) => {
$crate::try_pin_init!($(&$this in)? $t $(::<$($generics),*>)? {
$($fields)*
}? ::core::convert::Infallible)
};
}

#[cfg(kernel)]
#[macro_export]
macro_rules! __internal_try_init {
($(&$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)*),
)
};
}

#[cfg(kernel)]
#[macro_export]
macro_rules! __internal_try_pin_init {
($(&$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)*),
)
};
}
66 changes: 4 additions & 62 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -717,18 +717,7 @@ macro_rules! stack_try_pin_init {
/// ```
///
/// [`NonNull<Self>`]: 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::try_pin_init!($(&$this in)? $t $(::<$($generics),*>)? {
$($fields)*
}? ::core::convert::Infallible)
};
}
pub use pin_init_internal::pin_init;

/// Construct an in-place, fallible pinned initializer for `struct`s.
///
Expand Down Expand Up @@ -768,25 +757,7 @@ macro_rules! pin_init {
/// }
/// # 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) => {
$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::try_pin_init;

/// Construct an in-place initializer for `struct`s.
///
Expand Down Expand Up @@ -824,18 +795,7 @@ macro_rules! try_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::try_init!($(&$this in)? $t $(::<$($generics),*>)? {
$($fields)*
}? ::core::convert::Infallible)
}
}
pub use pin_init_internal::init;

/// Construct an in-place fallible initializer for `struct`s.
///
Expand Down Expand Up @@ -873,25 +833,7 @@ macro_rules! 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! try_init {
($(&$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::try_init;

/// Asserts that a field on a struct using `#[pin_data]` is marked with `#[pin]` ie. that it is
/// structurally pinned.
Expand Down
10 changes: 4 additions & 6 deletions tests/ui/compile-fail/init/colon_instead_of_arrow.stderr
Original file line number Diff line number Diff line change
@@ -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<Self> {
| ------------------ 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<Bar>`
Expand All @@ -17,4 +16,3 @@ note: function defined here
|
| pub const unsafe fn write<T>(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)
10 changes: 4 additions & 6 deletions tests/ui/compile-fail/init/field_value_wrong_type.stderr
Original file line number Diff line number Diff line change
@@ -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<T>(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)
7 changes: 2 additions & 5 deletions tests/ui/compile-fail/init/invalid_init.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ error[E0277]: the trait bound `impl pin_init::PinInit<Bar>: Init<Bar>` is not sa
| _____________^
19 | | bar <- Bar::new(),
20 | | });
| | ^
| | |
| |______the trait `Init<Bar>` is not implemented for `impl pin_init::PinInit<Bar>`
| required by a bound introduced by this call
| |______^ the trait `Init<Bar>` is not implemented for `impl pin_init::PinInit<Bar>`
|
= help: the trait `Init<T, E>` is implemented for `ChainInit<I, F, T, E>`
= 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)
22 changes: 2 additions & 20 deletions tests/ui/compile-fail/init/missing_comma.stderr
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
error: no rules expected `c`
error: expected `,`
--> 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)*),
| ^
| ^
15 changes: 5 additions & 10 deletions tests/ui/compile-fail/init/missing_comma_with_zeroable.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@ help: you can add the `dyn` keyword if you want a trait object
| ++++ +

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::zeroed()
13 | | });
| | ^
| | |
| |______expected `usize`, found `Range<{integer}>`
| arguments to this function are incorrect
12 | a: 0..Zeroable::zeroed()
| - ^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `Range<{integer}>`
| |
| arguments to this function are incorrect
|
= note: expected type `usize`
found struct `std::ops::Range<{integer}>`
Expand All @@ -28,7 +24,6 @@ note: function defined here
|
| pub const unsafe fn write<T>(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
Expand Down
12 changes: 4 additions & 8 deletions tests/ui/compile-fail/init/missing_error_type.stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
error: unexpected end of macro invocation
--> tests/ui/compile-fail/init/missing_error_type.rs:8:47
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 _ = try_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 `try_init` (in Nightly builds, run with -Z macro-backtrace for more info)
7 changes: 3 additions & 4 deletions tests/ui/compile-fail/init/missing_pin_data.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ 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 `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info)
help: there is an associated function `__init_data` with a similar name
--> src/lib.rs
|
- @has_data(HasPinData, __pin_data),
+ @has_data(HasPinData, __init_data),
9 - pin_init!(Self { a: 42 })
9 + __init_data
|
Loading

0 comments on commit 21b549d

Please sign in to comment.