Introduce a procedural macro `Into` to automatically implement the `Into` trait for unit-only enums.
This reduces boilerplate in cases where enum variants need to be interpreted as relevant numeric values. A concrete example can be found in nova-core, where the `register!()` macro requires enum types used within it to be convertible via `u32::from()` [1]. Note that the macro actually generates `From<E> for T` implementations, where `E` is an enum identifier and `T` is an arbitrary integer type. This automatically provides the corresponding `Into<T> for E` implementations through the blanket implementation. Tested-by: Alexandre Courbot <[email protected]> Link: https://lore.kernel.org/rust-for-linux/[email protected]/ [1] Signed-off-by: Jesung Yang <[email protected]> --- rust/macros/convert.rs | 36 ++++++++++--- rust/macros/lib.rs | 115 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 6 deletions(-) diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs index 0084bc4308c1..a6ef67ba27c7 100644 --- a/rust/macros/convert.rs +++ b/rust/macros/convert.rs @@ -3,6 +3,12 @@ use proc_macro::{token_stream, Delimiter, Ident, Span, TokenStream, TokenTree}; use std::iter::Peekable; +#[derive(Debug)] +enum DeriveTarget { + TryFrom, + Into, +} + #[derive(Debug)] struct TypeArgs { helper: Vec<Ident>, @@ -13,13 +19,20 @@ struct TypeArgs { "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize", ]; +pub(crate) fn derive_into(input: TokenStream) -> TokenStream { + derive(input, DeriveTarget::Into) +} + pub(crate) fn derive_try_from(input: TokenStream) -> TokenStream { - derive(input) + derive(input, DeriveTarget::TryFrom) } -fn derive(input: TokenStream) -> TokenStream { - let derive_target = "TryFrom"; - let derive_helper = "try_from"; +fn derive(input: TokenStream, target: DeriveTarget) -> TokenStream { + type ImplFn = fn(&Ident, &Ident, &[Ident]) -> TokenStream; + let (derive_target, derive_helper, impl_trait) = match target { + DeriveTarget::TryFrom => ("TryFrom", "try_from", impl_try_from as ImplFn), + DeriveTarget::Into => ("Into", "into", impl_into as ImplFn), + }; let mut tokens = input.into_iter().peekable(); @@ -85,12 +98,12 @@ fn derive(input: TokenStream) -> TokenStream { let ty = type_args .repr .unwrap_or_else(|| Ident::new("isize", Span::mixed_site())); - impl_try_from(&ty, &enum_ident, &variants) + impl_trait(&ty, &enum_ident, &variants) } else { let impls = type_args .helper .iter() - .map(|ty| impl_try_from(ty, &enum_ident, &variants)); + .map(|ty| impl_trait(ty, &enum_ident, &variants)); quote! { #(#impls)* } } } @@ -335,3 +348,14 @@ fn try_from(#param: #ty) -> Result<Self, Self::Error> { } } } + +fn impl_into(ty: &Ident, enum_ident: &Ident, _: &[Ident]) -> TokenStream { + quote! { + #[automatically_derived] + impl ::core::convert::From<#enum_ident> for #ty { + fn from(value: #enum_ident) -> #ty { + value as #ty + } + } + } +} diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 569198f188f7..374c1bdb696a 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -427,6 +427,121 @@ pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream { kunit::kunit_tests(attr, ts) } +/// A derive macro for providing an impl of the [`Into`] trait. +/// +/// This macro automatically derives [`Into`] trait for a given enum by generating +/// the relevant [`From`] implementation. Currently, it only supports [unit-only enum]s +/// without generic parameters. +/// +/// [unit-only enum]: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.unit-only +/// +/// # Notes +/// +/// Unlike its name suggests, the macro actually generates [`From`] implementations +/// which automatically provide corresponding [`Into`] implementations. +/// +/// The macro uses the `into` custom attribute or `repr` attribute to generate [`From`] +/// implementations. `into` always takes precedence over `repr`. +/// +/// # Caveats +/// +/// Ensure that every integer type specified in `#[into(...)]` is large enough to cover +/// all enum discriminants. Otherwise, the internal `as` casts may overflow. +/// +/// # Examples +/// +/// ## Without Attributes +/// +/// Since [the default `Rust` representation uses `isize` for the discriminant type][repr-rs], +/// the macro implements `From<Foo>` for `isize`: +/// +/// [repr-rs]: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust +/// +/// ```rust +/// use kernel::macros::Into; +/// use kernel::prelude::*; +/// +/// #[derive(Debug, Default, Into)] +/// enum Foo { +/// #[default] +/// A, +/// B = 0x17, +/// } +/// +/// assert_eq!(0isize, Foo::A.into()); +/// assert_eq!(0x17isize, Foo::B.into()); +/// ``` +/// +/// ## With `#[repr(T)]` +/// +/// The macro implements `From<Foo>` for `T`: +/// +/// ```rust +/// use kernel::macros::Into; +/// use kernel::prelude::*; +/// +/// #[derive(Debug, Default, Into)] +/// #[repr(u8)] +/// enum Foo { +/// #[default] +/// A, +/// B = 0x17, +/// } +/// +/// assert_eq!(0u8, Foo::A.into()); +/// assert_eq!(0x17u8, Foo::B.into()); +/// ``` +/// +/// ## With `#[into(...)]` +/// +/// The macro implements `From<Foo>` for each `T` specified in `#[into(...)]`, +/// which always overrides `#[repr(...)]`: +/// +/// ```rust +/// use kernel::macros::Into; +/// use kernel::prelude::*; +/// +/// #[derive(Debug, Default, Into)] +/// #[into(u8, u16)] +/// #[repr(u8)] +/// enum Foo { +/// #[default] +/// A, +/// B = 0x17, +/// } +/// +/// assert_eq!(0u16, Foo::A.into()); +/// assert_eq!(0x17u16, Foo::B.into()); +/// ``` +/// +/// ## Unsupported Cases +/// +/// The following examples do not compile: +/// +/// ```compile_fail +/// # use kernel::macros::Into; +/// // Generic parameters are not allowed. +/// #[derive(Into)] +/// enum Foo<T> { +/// A, +/// } +/// +/// // Tuple-like enums or struct-like enums are not allowed. +/// #[derive(Into)] +/// enum Bar { +/// A(u8), +/// B { inner: u8 }, +/// } +/// +/// // Structs are not allowed. +/// #[derive(Into)] +/// struct Baz(u8); +/// ``` +#[proc_macro_derive(Into, attributes(into))] +pub fn derive_into(input: TokenStream) -> TokenStream { + convert::derive_into(input) +} + /// A derive macro for generating an impl of the [`TryFrom`] trait. /// /// This macro automatically derives [`TryFrom`] trait for a given enum. Currently, -- 2.39.5
