Skip to content

Commit 47d64a7

Browse files
committed
rewrite derive(Zeroable) and derive(MaybeZeroable) using syn
Rewrite the two derive macros for `Zeroable` using `syn`. One positive side effect of this change is that tuple structs are now supported by them. Additionally, syntax errors and the error emitted when trying to use one of the derive macros on an `enum` are improved. Otherwise no functional changes intended. For example: #[derive(Zeroable)] enum Num { A(u32), B(i32), } Produced this error before this commit: error: no rules expected keyword `enum` --> tests/ui/compile-fail/zeroable/enum.rs:5:1 | 5 | enum Num { | ^^^^ no rules expected this token in macro call | note: while trying to match keyword `struct` --> src/macros.rs | | $vis:vis struct $name:ident | ^^^^^^ Now the error is: error: cannot derive `Zeroable` for an enum --> tests/ui/compile-fail/zeroable/enum.rs:5:1 | 5 | enum Num { | ^^^^ error: cannot derive `Zeroable` for an enum Signed-off-by: Benno Lossin <[email protected]>
1 parent 2335f45 commit 47d64a7

File tree

7 files changed

+70
-232
lines changed

7 files changed

+70
-232
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
code at that point.
1616
- Make the `[try_][pin_]init!` macros expose initialized fields via a `let`
1717
binding as `&mut T` or `Pin<&mut T>` for later fields.
18+
- `derive([Maybe]Zeroable)` now support tuple structs
1819

1920
## [0.0.10] - 2025-08-19
2021

internal/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#![allow(missing_docs)]
1212

1313
use proc_macro::TokenStream;
14+
use syn::parse_macro_input;
1415

1516
mod helpers;
1617
mod pin_data;
@@ -29,10 +30,10 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
2930

3031
#[proc_macro_derive(Zeroable)]
3132
pub fn derive_zeroable(input: TokenStream) -> TokenStream {
32-
zeroable::derive(input.into()).into()
33+
zeroable::derive(parse_macro_input!(input as _)).into()
3334
}
3435

3536
#[proc_macro_derive(MaybeZeroable)]
3637
pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream {
37-
zeroable::maybe_derive(input.into()).into()
38+
zeroable::maybe_derive(parse_macro_input!(input as _)).into()
3839
}

internal/src/zeroable.rs

Lines changed: 59 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,71 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use crate::helpers::{parse_generics, Generics};
4-
use proc_macro2::{TokenStream, TokenTree};
5-
use quote::quote;
3+
use proc_macro2::TokenStream;
4+
use quote::{quote, quote_spanned};
5+
use syn::{parse_quote, Data, DeriveInput, Field, Fields};
66

7-
pub(crate) fn parse_zeroable_derive_input(
8-
input: TokenStream,
9-
) -> (
10-
Vec<TokenTree>,
11-
Vec<TokenTree>,
12-
Vec<TokenTree>,
13-
Option<TokenTree>,
14-
) {
15-
let (
16-
Generics {
17-
impl_generics,
18-
decl_generics: _,
19-
ty_generics,
20-
},
21-
mut rest,
22-
) = parse_generics(input);
23-
// This should be the body of the struct `{...}`.
24-
let last = rest.pop();
25-
// Now we insert `Zeroable` as a bound for every generic parameter in `impl_generics`.
26-
let mut new_impl_generics = Vec::with_capacity(impl_generics.len());
27-
// Are we inside of a generic where we want to add `Zeroable`?
28-
let mut in_generic = !impl_generics.is_empty();
29-
// Have we already inserted `Zeroable`?
30-
let mut inserted = false;
31-
// Level of `<>` nestings.
32-
let mut nested = 0;
33-
for tt in impl_generics {
34-
match &tt {
35-
// If we find a `,`, then we have finished a generic/constant/lifetime parameter.
36-
TokenTree::Punct(p) if nested == 0 && p.as_char() == ',' => {
37-
if in_generic && !inserted {
38-
new_impl_generics.extend(quote! { : ::pin_init::Zeroable });
39-
}
40-
in_generic = true;
41-
inserted = false;
42-
new_impl_generics.push(tt);
43-
}
44-
// If we find `'`, then we are entering a lifetime.
45-
TokenTree::Punct(p) if nested == 0 && p.as_char() == '\'' => {
46-
in_generic = false;
47-
new_impl_generics.push(tt);
48-
}
49-
TokenTree::Punct(p) if nested == 0 && p.as_char() == ':' => {
50-
new_impl_generics.push(tt);
51-
if in_generic {
52-
new_impl_generics.extend(quote! { ::pin_init::Zeroable + });
53-
inserted = true;
54-
}
55-
}
56-
TokenTree::Punct(p) if p.as_char() == '<' => {
57-
nested += 1;
58-
new_impl_generics.push(tt);
59-
}
60-
TokenTree::Punct(p) if p.as_char() == '>' => {
61-
assert!(nested > 0);
62-
nested -= 1;
63-
new_impl_generics.push(tt);
64-
}
65-
_ => new_impl_generics.push(tt),
7+
pub(crate) fn derive(input: DeriveInput) -> TokenStream {
8+
let fields = match input.data {
9+
Data::Struct(data_struct) => data_struct.fields,
10+
Data::Union(data_union) => Fields::Named(data_union.fields),
11+
Data::Enum(data_enum) => {
12+
return quote_spanned! {data_enum.enum_token.span=>
13+
::core::compile_error!("cannot derive `Zeroable` for an enum");
14+
};
6615
}
16+
};
17+
let name = input.ident;
18+
let mut generics = input.generics;
19+
for param in generics.type_params_mut() {
20+
param.bounds.push(parse_quote!(::pin_init::Zeroable));
6721
}
68-
assert_eq!(nested, 0);
69-
if in_generic && !inserted {
70-
new_impl_generics.extend(quote! { : ::pin_init::Zeroable });
71-
}
72-
(rest, new_impl_generics, ty_generics, last)
73-
}
74-
75-
pub(crate) fn derive(input: TokenStream) -> TokenStream {
76-
let (rest, new_impl_generics, ty_generics, last) = parse_zeroable_derive_input(input);
22+
let (impl_gen, ty_gen, whr) = generics.split_for_impl();
23+
let field_type = fields.iter().map(|field| &field.ty);
7724
quote! {
78-
::pin_init::__derive_zeroable!(
79-
parse_input:
80-
@sig(#(#rest)*),
81-
@impl_generics(#(#new_impl_generics)*),
82-
@ty_generics(#(#ty_generics)*),
83-
@body(#last),
84-
);
25+
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
26+
#[automatically_derived]
27+
unsafe impl #impl_gen ::pin_init::Zeroable for #name #ty_gen
28+
#whr
29+
{}
30+
const _: () = {
31+
fn assert_zeroable<T: ?::core::marker::Sized + ::pin_init::Zeroable>() {}
32+
fn ensure_zeroable #impl_gen ()
33+
#whr
34+
{
35+
#(
36+
assert_zeroable::<#field_type>();
37+
)*
38+
}
39+
};
8540
}
8641
}
8742

88-
pub(crate) fn maybe_derive(input: TokenStream) -> TokenStream {
89-
let (rest, new_impl_generics, ty_generics, last) = parse_zeroable_derive_input(input);
43+
pub(crate) fn maybe_derive(input: DeriveInput) -> TokenStream {
44+
let fields = match input.data {
45+
Data::Struct(data_struct) => data_struct.fields,
46+
Data::Union(data_union) => Fields::Named(data_union.fields),
47+
Data::Enum(data_enum) => {
48+
return quote_spanned! {data_enum.enum_token.span=>
49+
compile_error!("cannot derive `Zeroable` for an enum");
50+
};
51+
}
52+
};
53+
let name = input.ident;
54+
let mut generics = input.generics;
55+
for Field { ty, .. } in fields {
56+
generics
57+
.make_where_clause()
58+
.predicates
59+
// the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds`
60+
// feature <https://github.com/rust-lang/rust/issues/48214#issuecomment-2557829956>.
61+
.push(parse_quote!(#ty: for<'__dummy> ::pin_init::Zeroable));
62+
}
63+
let (impl_gen, ty_gen, whr) = generics.split_for_impl();
9064
quote! {
91-
::pin_init::__maybe_derive_zeroable!(
92-
parse_input:
93-
@sig(#(#rest)*),
94-
@impl_generics(#(#new_impl_generics)*),
95-
@ty_generics(#(#ty_generics)*),
96-
@body(#last),
97-
);
65+
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
66+
#[automatically_derived]
67+
unsafe impl #impl_gen ::pin_init::Zeroable for #name #ty_gen
68+
#whr
69+
{}
9870
}
9971
}

src/macros.rs

Lines changed: 0 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,127 +1551,3 @@ macro_rules! __init_internal {
15511551
);
15521552
};
15531553
}
1554-
1555-
#[doc(hidden)]
1556-
#[macro_export]
1557-
macro_rules! __derive_zeroable {
1558-
(parse_input:
1559-
@sig(
1560-
$(#[$($struct_attr:tt)*])*
1561-
$vis:vis struct $name:ident
1562-
$(where $($whr:tt)*)?
1563-
),
1564-
@impl_generics($($impl_generics:tt)*),
1565-
@ty_generics($($ty_generics:tt)*),
1566-
@body({
1567-
$(
1568-
$(#[$($field_attr:tt)*])*
1569-
$field_vis:vis $field:ident : $field_ty:ty
1570-
),* $(,)?
1571-
}),
1572-
) => {
1573-
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
1574-
#[automatically_derived]
1575-
unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*>
1576-
where
1577-
$($($whr)*)?
1578-
{}
1579-
const _: () = {
1580-
fn assert_zeroable<T: ?::core::marker::Sized + $crate::Zeroable>() {}
1581-
fn ensure_zeroable<$($impl_generics)*>()
1582-
where $($($whr)*)?
1583-
{
1584-
$(assert_zeroable::<$field_ty>();)*
1585-
}
1586-
};
1587-
};
1588-
(parse_input:
1589-
@sig(
1590-
$(#[$($struct_attr:tt)*])*
1591-
$vis:vis union $name:ident
1592-
$(where $($whr:tt)*)?
1593-
),
1594-
@impl_generics($($impl_generics:tt)*),
1595-
@ty_generics($($ty_generics:tt)*),
1596-
@body({
1597-
$(
1598-
$(#[$($field_attr:tt)*])*
1599-
$field_vis:vis $field:ident : $field_ty:ty
1600-
),* $(,)?
1601-
}),
1602-
) => {
1603-
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
1604-
#[automatically_derived]
1605-
unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*>
1606-
where
1607-
$($($whr)*)?
1608-
{}
1609-
const _: () = {
1610-
fn assert_zeroable<T: ?::core::marker::Sized + $crate::Zeroable>() {}
1611-
fn ensure_zeroable<$($impl_generics)*>()
1612-
where $($($whr)*)?
1613-
{
1614-
$(assert_zeroable::<$field_ty>();)*
1615-
}
1616-
};
1617-
};
1618-
}
1619-
1620-
#[doc(hidden)]
1621-
#[macro_export]
1622-
macro_rules! __maybe_derive_zeroable {
1623-
(parse_input:
1624-
@sig(
1625-
$(#[$($struct_attr:tt)*])*
1626-
$vis:vis struct $name:ident
1627-
$(where $($whr:tt)*)?
1628-
),
1629-
@impl_generics($($impl_generics:tt)*),
1630-
@ty_generics($($ty_generics:tt)*),
1631-
@body({
1632-
$(
1633-
$(#[$($field_attr:tt)*])*
1634-
$field_vis:vis $field:ident : $field_ty:ty
1635-
),* $(,)?
1636-
}),
1637-
) => {
1638-
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
1639-
#[automatically_derived]
1640-
unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*>
1641-
where
1642-
$(
1643-
// the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds`
1644-
// feature <https://github.com/rust-lang/rust/issues/48214#issuecomment-2557829956>.
1645-
$field_ty: for<'__dummy> $crate::Zeroable,
1646-
)*
1647-
$($($whr)*)?
1648-
{}
1649-
};
1650-
(parse_input:
1651-
@sig(
1652-
$(#[$($struct_attr:tt)*])*
1653-
$vis:vis union $name:ident
1654-
$(where $($whr:tt)*)?
1655-
),
1656-
@impl_generics($($impl_generics:tt)*),
1657-
@ty_generics($($ty_generics:tt)*),
1658-
@body({
1659-
$(
1660-
$(#[$($field_attr:tt)*])*
1661-
$field_vis:vis $field:ident : $field_ty:ty
1662-
),* $(,)?
1663-
}),
1664-
) => {
1665-
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
1666-
#[automatically_derived]
1667-
unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*>
1668-
where
1669-
$(
1670-
// the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds`
1671-
// feature <https://github.com/rust-lang/rust/issues/48214#issuecomment-2557829956>.
1672-
$field_ty: for<'__dummy> $crate::Zeroable,
1673-
)*
1674-
$($($whr)*)?
1675-
{}
1676-
};
1677-
}
Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,11 @@
1-
error: no rules expected keyword `enum`
1+
error: cannot derive `Zeroable` for an enum
22
--> tests/ui/compile-fail/zeroable/enum.rs:5:1
33
|
44
5 | enum Num {
5-
| ^^^^ no rules expected this token in macro call
6-
|
7-
note: while trying to match keyword `struct`
8-
--> src/macros.rs
9-
|
10-
| $vis:vis struct $name:ident
11-
| ^^^^^^
5+
| ^^^^
126

13-
error: no rules expected keyword `enum`
7+
error: cannot derive `Zeroable` for an enum
148
--> tests/ui/compile-fail/zeroable/enum.rs:11:1
159
|
1610
11 | enum Num2 {
17-
| ^^^^ no rules expected this token in macro call
18-
|
19-
note: while trying to match keyword `struct`
20-
--> src/macros.rs
21-
|
22-
| $vis:vis struct $name:ident
23-
| ^^^^^^
11+
| ^^^^

tests/ui/compile-fail/zeroable/not_all_fields_zeroable.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ note: required by a bound in `assert_zeroable`
99
|
1010
4 | #[derive(Zeroable)]
1111
| ^^^^^^^^ required by this bound in `assert_zeroable`
12-
= note: this error originates in the macro `::pin_init::__derive_zeroable` which comes from the expansion of the derive macro `Zeroable` (in Nightly builds, run with -Z macro-backtrace for more info)
12+
= note: this error originates in the derive macro `Zeroable` (in Nightly builds, run with -Z macro-backtrace for more info)
1313
help: consider removing the leading `&`-reference
1414
|
1515
7 - b: &'static Foo,

tests/ui/expand/zeroable.expanded.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ struct WithGenerics<'a, T, U: Trait> {
3131
unsafe impl<
3232
'a,
3333
T: ::pin_init::Zeroable,
34-
U: ::pin_init::Zeroable + Trait,
34+
U: Trait + ::pin_init::Zeroable,
3535
> ::pin_init::Zeroable for WithGenerics<'a, T, U> {}
3636
const _: () = {
3737
fn assert_zeroable<T: ?::core::marker::Sized + ::pin_init::Zeroable>() {}
38-
fn ensure_zeroable<'a, T: ::pin_init::Zeroable, U: ::pin_init::Zeroable + Trait>() {
38+
fn ensure_zeroable<'a, T: ::pin_init::Zeroable, U: Trait + ::pin_init::Zeroable>() {
3939
assert_zeroable::<T>();
4040
assert_zeroable::<&'a U>();
4141
}

0 commit comments

Comments
 (0)