From 6aed5c880589256c74eb127691a9ff915c40c0e7 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Wed, 12 Feb 2025 20:37:02 +0100 Subject: [PATCH 1/7] internal: add `syn` dependency Adds the `syn` crate as a dependency in order to rewrite the proc macros using it. Signed-off-by: Benno Lossin --- Cargo.lock | 9 +++++---- internal/Cargo.lock | 20 ++++++++++++++++---- internal/Cargo.toml | 1 + 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45fa73ab..58b49e1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", + "syn", ] [[package]] @@ -131,9 +132,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -210,9 +211,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", diff --git a/internal/Cargo.lock b/internal/Cargo.lock index d0a730fe..42b1c239 100644 --- a/internal/Cargo.lock +++ b/internal/Cargo.lock @@ -9,22 +9,23 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 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.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +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 747c0b21..48c37160 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.98", features = ["full", "visit", "visit-mut"] } [build-dependencies] rustc_version = "0.4" From 02f41ad38da6678e9e58342573a4c9a8eacadcaa Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Wed, 12 Feb 2025 20:46:36 +0100 Subject: [PATCH 2/7] internal: add syn version of the `Zeroable` derive macro Implement the `Zeroable` derive macro using syn to simplify parsing by not going through an additional declarative macro. 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 --- internal/src/lib.rs | 5 +++ internal/src/syn_zeroable.rs | 63 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 internal/src/syn_zeroable.rs diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 56aa9ecc..14fdf804 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -31,6 +31,11 @@ extern crate quote; mod helpers; mod pin_data; mod pinned_drop; +#[cfg(kernel)] +mod zeroable; + +#[cfg(not(kernel))] +#[path = "syn_zeroable.rs"] mod zeroable; #[proc_macro_attribute] diff --git a/internal/src/syn_zeroable.rs b/internal/src/syn_zeroable.rs new file mode 100644 index 00000000..90ea8fa6 --- /dev/null +++ b/internal/src/syn_zeroable.rs @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parse_macro_input, parse_quote, Data, DataStruct, DeriveInput, Error, GenericParam, Result, + TypeParam, WherePredicate, +}; + +pub(crate) fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let raw = input.clone().into(); + do_derive(parse_macro_input!(input as DeriveInput), raw) + .unwrap_or_else(|e| e.into_compile_error()) + .into() +} + +fn do_derive( + DeriveInput { + ident, + mut generics, + data, + .. + }: DeriveInput, + raw_input: TokenStream, +) -> Result { + let Data::Struct(DataStruct { fields, .. }) = data else { + return Err(Error::new_spanned( + raw_input, + "`Zeroable` can only be derived for structs.", + )); + }; + let field_ty = fields.iter().map(|f| &f.ty); + let zeroable_bounds = generics + .params + .iter() + .filter_map(|p| match p { + GenericParam::Type(TypeParam { ident, .. }) => { + Some(parse_quote!(#ident: ::pin_init::Zeroable)) + } + _ => None, + }) + .collect::>(); + generics + .make_where_clause() + .predicates + .extend(zeroable_bounds); + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + Ok(quote! { + // SAFETY: Every field type implements `Zeroable` and padding bytes may be zero. + #[automatically_derived] + unsafe impl #impl_generics ::pin_init::Zeroable for #ident #ty_generics + #whr + {} + const _: () = { + fn assert_zeroable() {} + fn ensure_zeroable #impl_generics () + #whr + { + #(assert_zeroable::<#field_ty>();)* + } + }; + }) +} From 0fb3e7ff74f241baea8ee5b4c7303ca4fd9af739 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Wed, 12 Feb 2025 21:15:55 +0100 Subject: [PATCH 3/7] internal: add syn version of `pinned_drop` proc macro Implement the `pinned_drop` attribute 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 in this piece of code, there is a `drop` function missing: use pin_init::*; #[pin_data(PinnedDrop)] struct Foo {} #[pinned_drop] impl PinnedDrop for Foo {} But this error is communicated very poorly in the declarative macro version: error: no rules expected `)` | 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) error[E0277]: the trait bound `Foo: PinnedDrop` is not satisfied | 3 | #[pin_data(PinnedDrop)] | ^^^^^^^^^^^^^^^^^^^^^^^ | | | the trait `PinnedDrop` is not implemented for `Foo` | required by a bound introduced by this call | = 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 syn version is much more concise and right to the point: error[E0046]: not all trait items implemented, missing: `drop` | 7 | impl PinnedDrop for Foo {} | ^^^^^^^^^^^^^^^^^^^^^^^ missing `drop` in implementation | = help: implement the missing item: `fn drop(self: Pin<&mut Self>, _: OnlyCallFromDrop) { todo!() }` Another example is the following: use pin_init::*; use std::pin::Pin; #[pin_data(PinnedDrop)] struct Foo {} #[pinned_drop] impl PinnedDrop for Foo { fn drop(self: Pin<&mut Self>) {} const BAZ: usize = 0; } It produces this error in the declarative macro version: error: no rules expected keyword `const` | 10 | const BAZ: usize = 0; | ^^^^^ no rules expected this token in macro call | note: while trying to match `)` --> src/macros.rs | | ), | ^ error[E0277]: the trait bound `Foo: PinnedDrop` is not satisfied | 3 | #[pin_data(PinnedDrop)] | ^^^^^^^^^^^^^^^^^^^^^^^ | | | the trait `PinnedDrop` is not implemented for `Foo` | required by a bound introduced by this call | = 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) In the syn version, we get instead: error[E0438]: const `BAZ` is not a member of trait `pinned_init::PinnedDrop` | 11 | const BAZ: usize = 0; | ^^^^^^^^^^^^^^^^^^^^^ not a member of trait `pinned_init::PinnedDrop` 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 --- internal/src/lib.rs | 4 + internal/src/syn_pinned_drop.rs | 77 +++++++++++++++++++ .../ui/compile-fail/pinned_drop/no_fn.stderr | 26 ++----- .../unexpected_additional_item.stderr | 26 +++---- .../pinned_drop/unexpected_item.stderr | 23 ++---- 5 files changed, 103 insertions(+), 53 deletions(-) create mode 100644 internal/src/syn_pinned_drop.rs diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 14fdf804..b11545b1 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -30,10 +30,14 @@ extern crate quote; mod helpers; mod pin_data; +#[cfg(kernel)] mod pinned_drop; #[cfg(kernel)] mod zeroable; +#[cfg(not(kernel))] +#[path = "syn_pinned_drop.rs"] +mod pinned_drop; #[cfg(not(kernel))] #[path = "syn_zeroable.rs"] mod zeroable; diff --git a/internal/src/syn_pinned_drop.rs b/internal/src/syn_pinned_drop.rs new file mode 100644 index 00000000..1e70ee29 --- /dev/null +++ b/internal/src/syn_pinned_drop.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parse_macro_input, parse_quote, spanned::Spanned, Error, ImplItem, ImplItemFn, ItemImpl, + Result, Token, +}; + +pub(crate) fn pinned_drop( + args: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + parse_macro_input!(args as syn::parse::Nothing); + do_impl(parse_macro_input!(input as ItemImpl)) + .unwrap_or_else(|e| e.into_compile_error()) + .into() +} + +fn do_impl(mut input: ItemImpl) -> Result { + let Some((_, path, _)) = &mut input.trait_ else { + return Err(Error::new_spanned( + input, + "expected an `impl` block implementing `PinnedDrop`", + )); + }; + if !is_pinned_drop(path) { + return Err(Error::new_spanned( + input, + "expected an `impl` block implementing `PinnedDrop`", + )); + } + let mut error = None; + if let Some(unsafety) = input.unsafety.take() { + error = Some( + Error::new_spanned( + unsafety, + "implementing the trait `PinnedDrop` via `#[pinned_drop]` is not unsafe", + ) + .into_compile_error(), + ); + } + input.unsafety = Some(Token![unsafe](input.impl_token.span())); + if path.segments.len() != 2 { + path.segments.insert(0, parse_quote!(pin_init)); + } + path.leading_colon.get_or_insert(Token![::](path.span())); + for item in &mut input.items { + match item { + ImplItem::Fn(ImplItemFn { sig, .. }) if sig.ident == "drop" => { + sig.inputs + .push(parse_quote!(_: ::pin_init::__internal::OnlyCallFromDrop)); + } + _ => {} + } + } + Ok(quote! { + #error + #input + }) +} + +fn is_pinned_drop(path: &syn::Path) -> bool { + if path.segments.len() > 2 { + return false; + } + // If there is a `::`, then the path needs to be `::pin_init::PinnedDrop`. + if path.leading_colon.is_some() && path.segments.len() != 2 { + return false; + } + for (actual, expected) in path.segments.iter().rev().zip(["PinnedDrop", "pin_init"]) { + if actual.ident != expected { + return false; + } + } + true +} diff --git a/tests/ui/compile-fail/pinned_drop/no_fn.stderr b/tests/ui/compile-fail/pinned_drop/no_fn.stderr index f2520adf..f82b3006 100644 --- a/tests/ui/compile-fail/pinned_drop/no_fn.stderr +++ b/tests/ui/compile-fail/pinned_drop/no_fn.stderr @@ -1,23 +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) - -error[E0277]: the trait bound `Foo: PinnedDrop` is not satisfied - --> tests/ui/compile-fail/pinned_drop/no_fn.rs:3:1 - | -3 | #[pin_data(PinnedDrop)] - | ^^^^^^^^^^^^^^^^^^^^^^^ - | | - | the trait `PinnedDrop` is not implemented for `Foo` - | required by a bound introduced by this call - | - = 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) + = 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.stderr b/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.stderr index 5a1ad504..2e4d4aec 100644 --- a/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.stderr +++ b/tests/ui/compile-fail/pinned_drop/unexpected_additional_item.stderr @@ -1,22 +1,16 @@ -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_additional_item.rs:10:5 | 10 | const BAZ: usize = 0; - | ^^^^^ no rules expected this token in macro call - | -note: while trying to match `)` - --> src/macros.rs - | - | ), - | ^ + | ^^^^^^^^^^^^^^^^^^^^^ not a member of trait `pin_init::PinnedDrop` -error[E0277]: the trait bound `Foo: PinnedDrop` is not satisfied - --> tests/ui/compile-fail/pinned_drop/unexpected_additional_item.rs:3:1 +error[E0412]: cannot find type `Pin` in this scope + --> tests/ui/compile-fail/pinned_drop/unexpected_additional_item.rs:8:19 + | +8 | fn drop(self: Pin<&mut Self>) {} + | ^^^ not found in this scope + | +help: consider importing this struct | -3 | #[pin_data(PinnedDrop)] - | ^^^^^^^^^^^^^^^^^^^^^^^ - | | - | the trait `PinnedDrop` is not implemented for `Foo` - | required by a bound introduced by this call +1 + use std::pin::Pin; | - = 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/pinned_drop/unexpected_item.stderr b/tests/ui/compile-fail/pinned_drop/unexpected_item.stderr index 5b8fbb70..6f564a2b 100644 --- a/tests/ui/compile-fail/pinned_drop/unexpected_item.stderr +++ b/tests/ui/compile-fail/pinned_drop/unexpected_item.stderr @@ -1,22 +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 - | -note: while trying to match keyword `fn` - --> src/macros.rs - | - | fn drop($($sig:tt)*) { - | ^^ + | ^^^^^^^^^^^^^^^^^^^^^ not a member of trait `pin_init::PinnedDrop` -error[E0277]: the trait bound `Foo: PinnedDrop` is not satisfied - --> tests/ui/compile-fail/pinned_drop/unexpected_item.rs:3:1 +error[E0046]: not all trait items implemented, missing: `drop` + --> tests/ui/compile-fail/pinned_drop/unexpected_item.rs:7:1 | -3 | #[pin_data(PinnedDrop)] - | ^^^^^^^^^^^^^^^^^^^^^^^ - | | - | the trait `PinnedDrop` is not implemented for `Foo` - | required by a bound introduced by this call +7 | impl PinnedDrop for Foo { + | ^^^^^^^^^^^^^^^^^^^^^^^ missing `drop` in implementation | - = 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) + = help: implement the missing item: `fn drop(self: Pin<&mut Self>, _: OnlyCallFromDrop) { todo!() }` From b407406abeba123fba43c000999ef1fda472dba8 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Wed, 12 Feb 2025 21:24:00 +0100 Subject: [PATCH 4/7] internal: add syn version of the `pin_data` proc macro Implement the `pin_data` attribute 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, annotating a function with `pin_data` is not allowed: use pin_init::*; #[pin_data] fn foo() {} This results in the following rather unwieldy error with the declarative version: error: no rules expected keyword `fn` | 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. | 3 | #[pin_data] | ^^^^^^^^^^^ | = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) The syn version gives this very concise error: error: expected `struct` | 4 | fn foo() {} | ^^ 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 --- internal/src/lib.rs | 9 +- internal/src/syn_pin_data.rs | 313 ++++++++++++++++++ tests/many_generics.rs | 2 +- .../ui/compile-fail/pin_data/missing_comma.rs | 2 + .../pin_data/missing_comma.stderr | 11 +- .../compile-fail/pin_data/missing_pin.stderr | 2 +- .../pin_data/no_pin_on_phantompinned.stderr | 24 +- .../pin_data/pin_data_but_drop.stderr | 2 +- tests/ui/compile-fail/pin_data/twice.stderr | 22 +- .../pin_data/unexpected_args.stderr | 16 +- .../pin_data/unexpected_item.stderr | 18 +- .../pinned_drop/useless_pinned_drop.stderr | 2 +- tests/ui/expand/many_generics.expanded.rs | 8 +- tests/zeroing.rs | 2 +- 14 files changed, 356 insertions(+), 77 deletions(-) create mode 100644 internal/src/syn_pin_data.rs diff --git a/internal/src/lib.rs b/internal/src/lib.rs index b11545b1..2c458715 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -24,17 +24,18 @@ use proc_macro::TokenStream; #[macro_use] #[cfg_attr(not(kernel), rustfmt::skip)] mod quote; -#[cfg(not(kernel))] -#[macro_use] -extern crate quote; - +#[cfg(kernel)] mod helpers; +#[cfg(kernel)] mod pin_data; #[cfg(kernel)] mod pinned_drop; #[cfg(kernel)] mod zeroable; +#[cfg(not(kernel))] +#[path = "syn_pin_data.rs"] +mod pin_data; #[cfg(not(kernel))] #[path = "syn_pinned_drop.rs"] mod pinned_drop; diff --git a/internal/src/syn_pin_data.rs b/internal/src/syn_pin_data.rs new file mode 100644 index 00000000..d9517638 --- /dev/null +++ b/internal/src/syn_pin_data.rs @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parse_macro_input, parse_quote, + visit_mut::{visit_path_segment_mut, VisitMut}, + Error, Field, ItemStruct, PathSegment, Result, Type, TypePath, WhereClause, +}; + +pub(crate) fn pin_data( + inner: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + do_impl(inner.into(), parse_macro_input!(item as ItemStruct)) + .unwrap_or_else(|e| e.into_compile_error()) + .into() +} + +fn do_impl(args: TokenStream, mut struct_: ItemStruct) -> Result { + // 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); + + let the_pin_data = generate_the_pin_data(&struct_); + let unpin_impl = unpin_impl(&struct_); + let drop_impl = drop_impl(&struct_, args)?; + + let mut errors = TokenStream::new(); + for field in &mut struct_.fields { + if !is_pinned(field) && is_phantom_pinned(&field.ty) { + let message = format!("The field `{}` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute", field.ident.as_ref().unwrap() ); + errors.extend(quote!(::core::compile_error!(#message);)); + } + field.attrs.retain(|a| !a.path().is_ident("pin")); + } + Ok(quote! { + #struct_ + #errors + const _: () = { + #the_pin_data + #unpin_impl + #drop_impl + }; + }) +} + +struct SelfReplacer(PathSegment); + +impl VisitMut for SelfReplacer { + fn visit_path_segment_mut(&mut self, seg: &mut PathSegment) { + if seg.ident == "Self" { + *seg = self.0.clone(); + } else { + visit_path_segment_mut(self, seg); + } + } + + fn visit_item_mut(&mut self, _: &mut syn::Item) { + // Do not descend into items, since items reset/change what `Self` refers to. + } +} + +fn is_pinned(field: &Field) -> bool { + field.attrs.iter().any(|a| a.path().is_ident("pin")) +} + +fn is_phantom_pinned(ty: &Type) -> bool { + match ty { + Type::Path(TypePath { qself: None, path }) => { + // Cannot possibly refer to `PhantomPinned`. + 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 generate_the_pin_data( + ItemStruct { + vis, + ident, + generics, + fields, + .. + }: &ItemStruct, +) -> TokenStream { + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + + // 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 must be initialized via `Init`. + let pinned_field_accessors = fields + .iter() + .filter(|f| is_pinned(f)) + .map(|Field { vis, ident, ty, .. }| { + quote! { + #vis unsafe fn #ident( + self, + slot: *mut #ty, + init: impl ::pin_init::PinInit<#ty, E>, + ) -> ::core::result::Result<(), E> { + unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } + } + } + }) + .collect::(); + let not_pinned_field_accessors = fields + .iter() + .filter(|f| !is_pinned(f)) + .map(|Field { vis, ident, ty, .. }| { + quote! { + #vis unsafe fn #ident( + self, + slot: *mut #ty, + init: impl ::pin_init::Init<#ty, E>, + ) -> ::core::result::Result<(), E> { + unsafe { ::pin_init::Init::__init(init, slot) } + } + } + }) + .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. + #vis struct __ThePinData #generics + #whr + { + __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 + { + #pinned_field_accessors + #not_pinned_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 } + } + } + + unsafe impl #impl_generics + ::pin_init::__internal::PinData for __ThePinData #ty_generics + #whr + { + type Datee = #ident #ty_generics; + } + } +} + +fn 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 ( + 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() + .filter(|f| is_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 drop_impl( + ItemStruct { + ident, generics, .. + }: &ItemStruct, + args: TokenStream, +) -> Result { + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + let has_pinned_drop = match syn::parse2::>(args.clone()) { + Ok(None) => false, + Ok(Some(ident)) if ident == "PinnedDrop" => true, + _ => { + return Err(Error::new_spanned( + args, + "Expected nothing or `PinnedDrop` as arguments to `#[pin_data]`.", + )) + } + }; + // We need to disallow normal `Drop` implementation, the exact behavior depends on whether + // `PinnedDrop` was specified in `args`. + Ok(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); + } + } + } + } 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 + {} + } + }) +} diff --git a/tests/many_generics.rs b/tests/many_generics.rs index 2cad6421..9fdc3eb0 100644 --- a/tests/many_generics.rs +++ b/tests/many_generics.rs @@ -12,7 +12,7 @@ struct Foo<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> where T: Bar<'a, 1>, { - array: [u8; 1024 * 1024], + _array: [u8; 1024 * 1024], r: &'b mut [&'a mut T; SIZE], #[pin] _pin: PhantomPinned, diff --git a/tests/ui/compile-fail/pin_data/missing_comma.rs b/tests/ui/compile-fail/pin_data/missing_comma.rs index b7d11f54..9b068dd7 100644 --- a/tests/ui/compile-fail/pin_data/missing_comma.rs +++ b/tests/ui/compile-fail/pin_data/missing_comma.rs @@ -5,3 +5,5 @@ struct Foo { a: Box b: Box } + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/missing_comma.stderr b/tests/ui/compile-fail/pin_data/missing_comma.stderr index 5e296742..49a749ed 100644 --- a/tests/ui/compile-fail/pin_data/missing_comma.stderr +++ b/tests/ui/compile-fail/pin_data/missing_comma.stderr @@ -4,11 +4,8 @@ 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] - | ^^^^^^^^^^^ - | - = 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) +6 | b: Box + | ^ diff --git a/tests/ui/compile-fail/pin_data/missing_pin.stderr b/tests/ui/compile-fail/pin_data/missing_pin.stderr index 5d98a04c..cc146200 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 a99db630..3a9496dc 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,39 +1,31 @@ -error: The field `pin1` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute. +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 | 4 | #[pin_data] | ^^^^^^^^^^^ | - = 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: The field `pin2` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute. +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 | 4 | #[pin_data] | ^^^^^^^^^^^ | - = 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: The field `pin3` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute. +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 | 4 | #[pin_data] | ^^^^^^^^^^^ | - = 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: The field `pin4` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute. +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 | 4 | #[pin_data] | ^^^^^^^^^^^ | - = 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) - -warning: unused imports: `PhantomPinned` and `self` - --> tests/ui/compile-fail/pin_data/no_pin_on_phantompinned.rs:2:19 - | -2 | use std::marker::{self, PhantomPinned}; - | ^^^^ ^^^^^^^^^^^^^ - | - = note: `#[warn(unused_imports)]` on by default + = 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/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 2a89d952..d2a5d75d 100644 --- a/tests/ui/compile-fail/pin_data/twice.stderr +++ b/tests/ui/compile-fail/pin_data/twice.stderr @@ -1,21 +1,19 @@ 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) diff --git a/tests/ui/compile-fail/pin_data/unexpected_args.stderr b/tests/ui/compile-fail/pin_data/unexpected_args.stderr index 4744fc12..67cf5bcf 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 nothing or `PinnedDrop` as arguments to `#[pin_data]`. + --> 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..70a35e15 100644 --- a/tests/ui/compile-fail/pin_data/unexpected_item.stderr +++ b/tests/ui/compile-fail/pin_data/unexpected_item.stderr @@ -1,19 +1,5 @@ -error: no rules expected keyword `fn` +error: expected `struct` --> 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. - --> tests/ui/compile-fail/pin_data/unexpected_item.rs:3:1 - | -3 | #[pin_data] - | ^^^^^^^^^^^ - | - = 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/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 f22ef125..84db2561 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -8,12 +8,12 @@ struct Foo<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> where T: Bar<'a, 1>, { - array: [u8; 1024 * 1024], + _array: [u8; 1024 * 1024], r: &'b mut [&'a mut T; SIZE], _pin: PhantomPinned, } const _: () = { - struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> + struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> where T: Bar<'a, 1>, { @@ -53,7 +53,7 @@ const _: () = { ) -> ::core::result::Result<(), E> { unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } } - unsafe fn array( + unsafe fn _array( self, slot: *mut [u8; 1024 * 1024], init: impl ::pin_init::Init<[u8; 1024 * 1024], E>, @@ -96,7 +96,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/zeroing.rs b/tests/zeroing.rs index d722d728..4cc776f3 100644 --- a/tests/zeroing.rs +++ b/tests/zeroing.rs @@ -11,7 +11,7 @@ const MARKS: usize = 64; pub struct Foo { buf: [u8; 1024 * 1024], marks: [*mut u8; MARKS], - pos: usize, + _pos: usize, #[pin] _pin: PhantomPinned, } From 6a90e52c65dcc5f03bc5c4438a18ca003d7780f8 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Thu, 13 Feb 2025 00:47:51 +0100 Subject: [PATCH 5/7] add `?Sized` bounds to traits in `#[pin_data]` macro `#[pin_data]` 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 value using pin-init. Signed-off-by: Benno Lossin --- internal/src/syn_pin_data.rs | 6 +++--- src/macros.rs | 4 ++-- tests/ui/expand/pin-data.expanded.rs | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/internal/src/syn_pin_data.rs b/internal/src/syn_pin_data.rs index d9517638..30cd4bc3 100644 --- a/internal/src/syn_pin_data.rs +++ b/internal/src/syn_pin_data.rs @@ -293,7 +293,7 @@ fn 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 {} @@ -302,8 +302,8 @@ fn drop_impl( // `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 + UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} impl #impl_generics UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #ident #ty_generics #whr diff --git a/src/macros.rs b/src/macros.rs index 36162332..03e25884 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -931,7 +931,7 @@ macro_rules! __pin_data { // if it also implements `Drop` trait MustNotImplDrop {} #[expect(drop_bounds)] - impl MustNotImplDrop for T {} + 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. @@ -939,7 +939,7 @@ macro_rules! __pin_data { // `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 $name<$($ty_generics)*> diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index 58b8f532..5f3946b3 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -56,11 +56,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 100ab0811cccf4bb323697bd8a3611fbacffce6e Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Thu, 13 Feb 2025 01:03:00 +0100 Subject: [PATCH 6/7] allow doctests to refer to the pin-init crate The next commit will name the pin-init crate from proc macros via `::pin_init`. For this to work within tests of the pin-init crate itself, it needs to be able to refer to itself via that name. Thus add the required code for the name to be available. Signed-off-by: Benno Lossin --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0806c689..579d09d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -290,6 +290,9 @@ mod alloc; #[cfg(any(feature = "std", feature = "alloc"))] pub use alloc::InPlaceInit; +#[allow(unused_extern_crates)] +extern crate self as pin_init; + /// Used to specify the pinning information of the fields of a struct. /// /// This is somewhat similar in purpose as From ea9fc0640ae683ede94ff7163d5c4f262d31086a Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Wed, 12 Feb 2025 21:32:39 +0100 Subject: [PATCH 7/7] internal: add syn version of `[try_][pin_]init!` macros 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 { 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(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 { | ^^^ - 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 --- internal/src/init.rs | 404 ++++++++++++++++++ internal/src/lib.rs | 78 ++++ src/__internal.rs | 62 +++ src/lib.rs | 66 +-- .../init/colon_instead_of_arrow.stderr | 10 +- .../init/field_value_wrong_type.stderr | 10 +- .../ui/compile-fail/init/invalid_init.stderr | 7 +- .../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 | 7 +- .../init/no_error_coercion.stderr | 8 +- .../compile-fail/init/wrong_generics.stderr | 20 +- .../compile-fail/init/wrong_generics2.stderr | 64 --- tests/ui/compile-fail/zeroable/with_comma.rs | 1 - .../compile-fail/zeroable/with_comma.stderr | 36 +- tests/ui/expand/simple-init.expanded.rs | 6 +- 17 files changed, 595 insertions(+), 233 deletions(-) create mode 100644 internal/src/init.rs diff --git a/internal/src/init.rs b/internal/src/init.rs new file mode 100644 index 00000000..4d325cd3 --- /dev/null +++ b/internal/src/init.rs @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned}; +use syn::{ + braced, + parse::{Parse, ParseStream}, + parse_quote, + punctuated::Punctuated, + spanned::Spanned, + token, Error, Expr, ExprCall, ExprPath, Path, Result, Token, Type, +}; + +pub(crate) struct InPlaceInitializer { + 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 FieldInitializer { + Value { + ident: Ident, + value: Option<(Token![:], Expr)>, + }, + Init { + ident: Ident, + _larrow: Token![<-], + value: Expr, + }, +} + +impl FieldInitializer { + fn ident(&self) -> &Ident { + match self { + FieldInitializer::Value { ident, .. } | FieldInitializer::Init { ident, .. } => ident, + } + } +} + +enum InitKind { + Normal, + Zeroing, +} + +pub(crate) fn init( + InPlaceInitializer { + this, + path, + fields, + rest, + mut error, + brace_token, + .. + }: InPlaceInitializer, + default_error: bool, + pin: bool, +) -> Result { + if default_error { + error.get_or_insert(( + Default::default(), + parse_quote!(::core::convert::Infallible), + )); + } + let Some((_, error)) = error else { + return Err(Error::new( + brace_token.span.close(), + "expected `? ` after `}`", + )); + }; + let use_data = pin; + let (has_data_trait, data_trait, get_data, from_closure) = match pin { + true => ( + format_ident!("HasPinData"), + format_ident!("PinData"), + format_ident!("__pin_data"), + format_ident!("pin_init_from_closure"), + ), + false => ( + format_ident!("HasInitData"), + format_ident!("InitData"), + format_ident!("__init_data"), + format_ident!("init_from_closure"), + ), + }; + + let init_kind = get_init_kind(rest)?; + 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) }; + }, + }; + let data = format_ident!("data", span = Span::mixed_site()); + let init_fields = init_fields(&fields, use_data, &data); + let field_check = make_field_check(&fields, init_kind, &path); + Ok(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. + 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| ()) + }; + let init = unsafe { ::pin_init::#from_closure::<_, #error>(init) }; + init + }}) +} + +fn get_init_kind(rest: Option<(Token![..], Expr)>) -> Result { + let Some((dotdot, expr)) = rest else { + return Ok(InitKind::Normal); + }; + let error = Error::new_spanned( + quote!(#dotdot #expr), + "Expected one of the following:\n- Nothing.\n- `..Zeroable::zeroed()`.", + ); + let Expr::Call(ExprCall { + func, args, attrs, .. + }) = expr + else { + return Err(error); + }; + if !args.is_empty() || !attrs.is_empty() { + return Err(error); + } + 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 == "zeroed" + && segments[1].arguments.is_none() => + { + Ok(InitKind::Zeroing) + } + _ => Err(error), + } +} + +/// Generate the code that initializes the fields of the struct using the initializers in `field`. +fn init_fields( + fields: &Punctuated, + use_data: bool, + data: &Ident, +) -> TokenStream { + let mut guards = vec![]; + let mut res = TokenStream::new(); + for field in fields { + let ident = field.ident(); + let guard = format_ident!("__{ident}_guard", span = Span::mixed_site()); + guards.push(guard.clone()); + let init = match field { + FieldInitializer::Value { ident, value } => { + let mut value_ident = ident.clone(); + let value = 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 + }; + quote! { + { + #value + // Initialize the field. + // + // SAFETY: The memory at `slot` is uninitialized. + unsafe { #write(::core::ptr::addr_of_mut!((*slot).#ident), #value_ident) }; + } + } + } + FieldInitializer::Init { ident, value, .. } => { + if use_data { + quote! { + let init = #value; + // 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 `#ident`. + unsafe { #data.#ident(::core::ptr::addr_of_mut!((*slot).#ident), init)? }; + } + } else { + quote! { + let init = #value; + // Call the initializer. + // + // 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), + )? + }; + } + } + } + }; + res.extend(init); + 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().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 InPlaceInitializer { + fn parse(input: ParseStream) -> Result { + let content; + Ok(Self { + this: input.peek(Token![&]).then(|| input.parse()).transpose()?, + path: input.parse()?, + brace_token: braced!(content in input), + fields: { + let mut fields = Punctuated::new(); + loop { + let lookahead = content.lookahead1(); + if content.is_empty() || lookahead.peek(Token![..]) { + break; + } else if lookahead.peek(syn::Ident) { + fields.push_value(content.parse()?); + let lookahead = content.lookahead1(); + if lookahead.peek(Token![,]) { + fields.push_punct(content.parse()?); + } else if content.is_empty() { + break; + } else { + return Err(lookahead.error()); + } + } else { + return Err(lookahead.error()); + } + } + fields + }, + rest: content + .peek(Token![..]) + .then(|| -> Result<_> { Ok((content.parse()?, content.parse()?)) }) + .transpose()?, + error: input + .peek(Token![?]) + .then(|| -> Result<_> { Ok((input.parse()?, input.parse()?)) }) + .transpose()?, + }) + } +} + +impl Parse for This { + fn parse(input: ParseStream) -> Result { + Ok(Self { + _and_token: input.parse()?, + ident: input.parse()?, + _in_token: input.parse()?, + }) + } +} + +impl Parse for FieldInitializer { + fn parse(input: ParseStream) -> Result { + let ident = input.parse()?; + let lookahead = input.lookahead1(); + Ok(if lookahead.peek(Token![<-]) { + Self::Init { + ident, + _larrow: input.parse()?, + value: input.parse()?, + } + } else if lookahead.peek(Token![:]) { + Self::Value { + ident, + value: Some((input.parse()?, input.parse()?)), + } + } else if lookahead.peek(Token![,]) || input.is_empty() { + Self::Value { ident, value: None } + } else { + return Err(lookahead.error()); + }) + } +} diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 2c458715..e4918405 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -33,6 +33,8 @@ mod pinned_drop; #[cfg(kernel)] mod zeroable; +#[cfg(not(kernel))] +mod init; #[cfg(not(kernel))] #[path = "syn_pin_data.rs"] mod pin_data; @@ -57,3 +59,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() +} diff --git a/src/__internal.rs b/src/__internal.rs index 557b5948..76989e82 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -290,3 +290,65 @@ unsafe impl PinInit for AlwaysFail { 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)*), + ) + }; +} diff --git a/src/lib.rs b/src/lib.rs index 579d09d4..78d4f4dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -725,18 +725,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::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. /// @@ -776,25 +765,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. /// @@ -832,18 +803,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. /// @@ -881,25 +841,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. 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 7731bc31..f8581d49 100644 --- a/tests/ui/compile-fail/init/invalid_init.stderr +++ b/tests/ui/compile-fail/init/invalid_init.stderr @@ -5,10 +5,7 @@ error[E0277]: the trait bound `impl pin_init::PinInit: Init` is not sa | _____________^ 19 | | bar <- Bar::new(), 20 | | }); - | | ^ - | | | - | |______the trait `Init` is not implemented for `impl pin_init::PinInit` - | required by a bound introduced by this call + | |______^ the trait `Init` is not implemented for `impl pin_init::PinInit` | = help: the trait `Init` is implemented for `ChainInit` - = 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/missing_comma.stderr b/tests/ui/compile-fail/init/missing_comma.stderr index 9e4c25db..6681634f 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 `,` --> 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 4ffb89c6..53cf5b85 100644 --- a/tests/ui/compile-fail/init/missing_comma_with_zeroable.stderr +++ b/tests/ui/compile-fail/init/missing_comma_with_zeroable.stderr @@ -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}>` @@ -28,7 +24,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 8f9f3ef3..5669cc4b 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: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) diff --git a/tests/ui/compile-fail/init/missing_pin_data.stderr b/tests/ui/compile-fail/init/missing_pin_data.stderr index e9806dd5..58f0013e 100644 --- a/tests/ui/compile-fail/init/missing_pin_data.stderr +++ b/tests/ui/compile-fail/init/missing_pin_data.stderr @@ -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 | diff --git a/tests/ui/compile-fail/init/no_error_coercion.stderr b/tests/ui/compile-fail/init/no_error_coercion.stderr index dae2783e..275e2cb4 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 | / try_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 `try_init` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `try_init` (in Nightly builds, run with -Z macro-backtrace for more info) 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 d1b1e7a2..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 { - |9 $($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::try_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/zeroable/with_comma.rs b/tests/ui/compile-fail/zeroable/with_comma.rs index e490b891..6ecd1d56 100644 --- a/tests/ui/compile-fail/zeroable/with_comma.rs +++ b/tests/ui/compile-fail/zeroable/with_comma.rs @@ -1,4 +1,3 @@ -extern crate pin_init; use pin_init::*; #[derive(Zeroable)] diff --git a/tests/ui/compile-fail/zeroable/with_comma.stderr b/tests/ui/compile-fail/zeroable/with_comma.stderr index df6263a4..09800cf9 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:12:29 | -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 `,` - --> tests/ui/compile-fail/zeroable/with_comma.rs:11:13 - | -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) +12 | ..Zeroable::zeroed(), + | ^ diff --git a/tests/ui/expand/simple-init.expanded.rs b/tests/ui/expand/simple-init.expanded.rs index 320efc95..0689a65d 100644 --- a/tests/ui/expand/simple-init.expanded.rs +++ b/tests/ui/expand/simple-init.expanded.rs @@ -17,11 +17,7 @@ fn main() { { 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) },