From 3ac34322a9b541ca6b4b6deef06729c309b92e9c Mon Sep 17 00:00:00 2001 From: Martin Kletzander Date: Mon, 24 Nov 2025 12:01:41 +0100 Subject: [PATCH] Allow other attributes in #[pin_data] structs When the `__pin_data!(find_pinned_fields:` part of the macro encounters an unknown attribute (anything apart from `$[pin]`) before a field, it is put into the accumulator and the macro proceeds further. Whatever is in the accumulator is later saved into `$fields` and then also into `$pinned` or `$not_pinned` depending on whether that field is pinned. Pinned fields, with all their unknown attributes are also used in the internal `__Unpin` struct. A field can have multiple different attributes, mostly belonging into two different categories. Built-in (defined by the language itself) and arbitrary (any other attribute that is used by another proc-macro crate). Out of the built-in ones [1] the only things that make sense to be usable for struct fields are most probably just "cfg", "doc", lint levels ("allow", "expect", "warn", "deny", "forbid"), and "deprecated". Out of these the only one that makes sense to keep around for the pin_data macro is "cfg" since that one does a conditional compilation and we only want the members to be included if they are included in the original struct. From the arbitrary (basically unknown) ones there is no reason for them to be used, mainly because they will likely be part of a derive which will not be included for the `__Unpin` struct, therefore making the code fail to compile if included. Since those need to be kept for the original struct, move them to the `$fields` instead of `$accum` in order not to pollute the `struct __Unpin` with unknown attributes. To put this all together, add a test with a custom attribute that fails without this fix. Unfortunately, another crate needs to be added for the test. [1] https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index Signed-off-by: Martin Kletzander --- Cargo.lock | 5 +++++ Cargo.toml | 1 + src/macros.rs | 42 +++++++++++++++++++++++++++++++++++--- test_dummy_only/Cargo.lock | 7 +++++++ test_dummy_only/Cargo.toml | 13 ++++++++++++ test_dummy_only/src/lib.rs | 4 ++++ tests/extra_attrs.rs | 23 +++++++++++++++++++++ 7 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 test_dummy_only/Cargo.lock create mode 100644 test_dummy_only/Cargo.toml create mode 100644 test_dummy_only/src/lib.rs create mode 100644 tests/extra_attrs.rs diff --git a/Cargo.lock b/Cargo.lock index 1119162d..e4e64fa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,7 @@ dependencies = [ "pinned-init-macro", "prettyplease", "rustc_version", + "test_dummy_only", "trybuild", ] @@ -220,6 +221,10 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test_dummy_only" +version = "0.0.1" + [[package]] name = "toml" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index cfe8c414..c3c1f25c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ trybuild = { version = "1.0", features = ["diff"] } macrotest = "1.0" # needed for macrotest, have to enable verbatim feature to be able to format `&raw` expressions. prettyplease = { version = "0.2", features = ["verbatim"] } +test_dummy_only = { path = "./test_dummy_only" } [lints.rust] non_ascii_idents = "deny" diff --git a/src/macros.rs b/src/macros.rs index a3eda1e4..210f6c30 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -779,8 +779,9 @@ macro_rules! __pin_data { @ty_generics($($ty_generics:tt)*), @decl_generics($($decl_generics:tt)*), @where($($whr:tt)*), - // Some other attribute, just put it into `$accum`. - @fields_munch(#[$($attr:tt)*] $($rest:tt)*), + // The #[cfg] attribute needs to be kept for our fields to make sense, + // so put it into `$accum`. + @fields_munch(#[cfg $($attr:tt)*] $($rest:tt)*), @pinned($($pinned:tt)*), @not_pinned($($not_pinned:tt)*), @fields($($fields:tt)*), @@ -800,7 +801,42 @@ macro_rules! __pin_data { @pinned($($pinned)*), @not_pinned($($not_pinned)*), @fields($($fields)*), - @accum($($accum)* #[$($attr)*]), + @accum($($accum)* #[cfg $($attr)*]), + @is_pinned($($is_pinned)?), + @pinned_drop($($pinned_drop)?), + ); + }; + (find_pinned_fields: + @struct_attrs($($struct_attrs:tt)*), + @vis($vis:vis), + @name($name:ident), + @impl_generics($($impl_generics:tt)*), + @ty_generics($($ty_generics:tt)*), + @decl_generics($($decl_generics:tt)*), + @where($($whr:tt)*), + // Some other attribute, we only need to keep it for the original + // struct, just put it into `$fields`. + @fields_munch(#[$($attr:tt)*] $($rest:tt)*), + @pinned($($pinned:tt)*), + @not_pinned($($not_pinned:tt)*), + @fields($($fields:tt)*), + @accum($($accum:tt)*), + @is_pinned($($is_pinned:ident)?), + @pinned_drop($($pinned_drop:ident)?), + ) => { + $crate::__pin_data!(find_pinned_fields: + @struct_attrs($($struct_attrs)*), + @vis($vis), + @name($name), + @impl_generics($($impl_generics)*), + @ty_generics($($ty_generics)*), + @decl_generics($($decl_generics)*), + @where($($whr)*), + @fields_munch($($rest)*), + @pinned($($pinned)*), + @not_pinned($($not_pinned)*), + @fields($($fields)* #[$($attr)*]), + @accum($($accum)*), @is_pinned($($is_pinned)?), @pinned_drop($($pinned_drop)?), ); diff --git a/test_dummy_only/Cargo.lock b/test_dummy_only/Cargo.lock new file mode 100644 index 00000000..fa62fcd5 --- /dev/null +++ b/test_dummy_only/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "test_only" +version = "0.0.1" diff --git a/test_dummy_only/Cargo.toml b/test_dummy_only/Cargo.toml new file mode 100644 index 00000000..ada847fb --- /dev/null +++ b/test_dummy_only/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "test_dummy_only" +version = "0.0.1" +edition = "2021" + +authors = ["y86-dev"] +license = "MIT OR Apache-2.0" +description = "Proc macro only for test reproduction." + +publish = false + +[lib] +proc-macro = true diff --git a/test_dummy_only/src/lib.rs b/test_dummy_only/src/lib.rs new file mode 100644 index 00000000..0ea09257 --- /dev/null +++ b/test_dummy_only/src/lib.rs @@ -0,0 +1,4 @@ +#[proc_macro_derive(Dummy, attributes(dummy_attr))] +pub fn derive_device(_: proc_macro::TokenStream) -> proc_macro::TokenStream { + proc_macro::TokenStream::new() +} diff --git a/tests/extra_attrs.rs b/tests/extra_attrs.rs new file mode 100644 index 00000000..46c88373 --- /dev/null +++ b/tests/extra_attrs.rs @@ -0,0 +1,23 @@ +#![cfg_attr(not(RUSTC_LINT_REASONS_IS_STABLE), feature(lint_reasons))] + +use pinned_init::*; +use test_dummy_only::Dummy; + +#[pin_data] +#[derive(Dummy)] +struct Pointless { + #[pin] + #[dummy_attr] + #[cfg(test)] + member: i8, + #[pin] + #[dummy_attr] + #[cfg(not(test))] + member: u8, +} + +#[test] +fn multiple_attributes() { + stack_pin_init!(let p = init!(Pointless { member: 0 })); + println!("{}", p.member); +}