Jesung Yang
2025-Aug-15 05:32 UTC
[PATCH v2 0/5] rust: add `TryFrom` and `Into` derive macros
This patch series introduces derive macros for `TryFrom` and `Into`
traits.
A few enhancements were made to the custom `quote!()` macro to write
the derive macro. These include support for additional punctuation
tokens and a fix for an unused variable warning when quoting simple
forms. Detailed information about these enhancements is provided in the
relevant patches.
This series builds on the previous work [1], where the `FromPrimitive`
trait was considered too heavy for the current use cases. In response
to the emerging need for functionality similar to `ToPrimitive`, this
series also implements the `Into` derive macro.
The original discussion can be found on Zulip [2].
[1] https://lore.kernel.org/rust-for-linux/cover.1750689857.git.y.j3ms.n at
gmail.com/
[2]
https://rust-for-linux.zulipchat.com/#narrow/channel/288089/topic/x/near/524335626
Changes in v2 (no functional changes):
- Split the patch "rust: macros: extend custom `quote!()` macro"
into two separate patches.
- Remove unnecessary spaces between tags.
- Use a consistent commit subject prefix: "rust: macros:".
- Add Tested-by tags.
Jesung Yang (5):
rust: macros: allow conversion from `&T` to `TokenStream`
rust: macros: extend custom `quote!()` macro
rust: macros: prefix variable `span` with underscore
rust: macros: add derive macro for `TryFrom`
rust: macros: add derive macro for `Into`
rust/macros/convert.rs | 361 +++++++++++++++++++++++++++++++++++++++++
rust/macros/lib.rs | 239 +++++++++++++++++++++++++++
rust/macros/quote.rs | 40 ++++-
3 files changed, 638 insertions(+), 2 deletions(-)
create mode 100644 rust/macros/convert.rs
base-commit: dff64b072708ffef23c117fa1ee1ea59eb417807
--
2.39.5
Jesung Yang
2025-Aug-15 05:32 UTC
[PATCH v2 1/5] rust: macros: allow conversion from `&T` to `TokenStream`
Implement the `ToTokens` trait for `&T` where `T` implements `ToTokens`.
This allows users to use the `quote!()` macro with references directly,
avoiding the need to clone values.
Tested-by: Alexandre Courbot <acourbot at nvidia.com>
Signed-off-by: Jesung Yang <y.j3ms.n at gmail.com>
---
rust/macros/quote.rs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs
index 92cacc4067c9..8a89f0b1e785 100644
--- a/rust/macros/quote.rs
+++ b/rust/macros/quote.rs
@@ -7,6 +7,12 @@ pub(crate) trait ToTokens {
fn to_tokens(&self, tokens: &mut TokenStream);
}
+impl<T: ToTokens> ToTokens for &T {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ (*self).to_tokens(tokens);
+ }
+}
+
impl<T: ToTokens> ToTokens for Option<T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if let Some(v) = self {
--
2.39.5
Jesung Yang
2025-Aug-15 05:32 UTC
[PATCH v2 2/5] rust: macros: extend custom `quote!()` macro
Extend the `quote_spanned!()` macro to support additional punctuation
tokens: `->`, `<`, `>`, and `==`. This symbols are commonly needed when
dealing with functions, generic bounds, and equality comparisons.
Tested-by: Alexandre Courbot <acourbot at nvidia.com>
Signed-off-by: Jesung Yang <y.j3ms.n at gmail.com>
---
rust/macros/quote.rs | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs
index 8a89f0b1e785..24764b04a07d 100644
--- a/rust/macros/quote.rs
+++ b/rust/macros/quote.rs
@@ -150,6 +150,36 @@ macro_rules! quote_spanned {
));
quote_spanned!(@proc $v $span $($tt)*);
};
+ (@proc $v:ident $span:ident -> $($tt:tt)*) => {
+ $v.push(::proc_macro::TokenTree::Punct(
+ ::proc_macro::Punct::new('-',
::proc_macro::Spacing::Joint)
+ ));
+ $v.push(::proc_macro::TokenTree::Punct(
+ ::proc_macro::Punct::new('>',
::proc_macro::Spacing::Alone)
+ ));
+ quote_spanned!(@proc $v $span $($tt)*);
+ };
+ (@proc $v:ident $span:ident < $($tt:tt)*) => {
+ $v.push(::proc_macro::TokenTree::Punct(
+ ::proc_macro::Punct::new('<',
::proc_macro::Spacing::Alone)
+ ));
+ quote_spanned!(@proc $v $span $($tt)*);
+ };
+ (@proc $v:ident $span:ident > $($tt:tt)*) => {
+ $v.push(::proc_macro::TokenTree::Punct(
+ ::proc_macro::Punct::new('>',
::proc_macro::Spacing::Alone)
+ ));
+ quote_spanned!(@proc $v $span $($tt)*);
+ };
+ (@proc $v:ident $span:ident == $($tt:tt)*) => {
+ $v.push(::proc_macro::TokenTree::Punct(
+ ::proc_macro::Punct::new('=',
::proc_macro::Spacing::Joint)
+ ));
+ $v.push(::proc_macro::TokenTree::Punct(
+ ::proc_macro::Punct::new('=',
::proc_macro::Spacing::Alone)
+ ));
+ quote_spanned!(@proc $v $span $($tt)*);
+ };
(@proc $v:ident $span:ident = $($tt:tt)*) => {
$v.push(::proc_macro::TokenTree::Punct(
::proc_macro::Punct::new('=',
::proc_macro::Spacing::Alone)
--
2.39.5
Jesung Yang
2025-Aug-15 05:32 UTC
[PATCH v2 3/5] rust: macros: prefix variable `span` with underscore
Prefix the variable `span` in `quote_spanned!()` macro with an
underscore to silence unused variable warnings.
The warning occurs when the macro is used without any uninterpolated
identifiers. For example:
// Triggers a warning: "unused variable: `span`"
quote! { #foo }
// This is fine
quote! { Some(#foo) }
There is no good reason to disallow such quoting patterns, so fix the
warning instead.
Tested-by: Alexandre Courbot <acourbot at nvidia.com>
Signed-off-by: Jesung Yang <y.j3ms.n at gmail.com>
---
rust/macros/quote.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs
index 24764b04a07d..75367a93e0d3 100644
--- a/rust/macros/quote.rs
+++ b/rust/macros/quote.rs
@@ -57,8 +57,8 @@ macro_rules! quote_spanned {
#[allow(clippy::vec_init_then_push)]
{
tokens = ::std::vec::Vec::new();
- let span = $span;
- quote_spanned!(@proc tokens span $($tt)*);
+ let _span = $span;
+ quote_spanned!(@proc tokens _span $($tt)*);
}
::proc_macro::TokenStream::from_iter(tokens)
}};
--
2.39.5
Jesung Yang
2025-Aug-15 05:32 UTC
[PATCH v2 4/5] rust: macros: add derive macro for `TryFrom`
Introduce a procedural macro `TryFrom` to automatically implement the
`TryFrom` trait for unit-only enums.
This reduces boilerplate in cases where numeric values need to be
interpreted as relevant enum variants. This situation often arise when
working with low-level data sources. A typical example is the `Chipset`
enum in nova-core, where the value read from a GPU register should be
mapped to a corresponding variant.
Since a pending RFC [1] proposes adding the `syn` crate [2] as a
dependency, keep the parsing logic minimal.
Tested-by: Alexandre Courbot <acourbot at nvidia.com>
Link:
https://lore.kernel.org/rust-for-linux/20250304225536.2033853-1-benno.lossin at
proton.me [1]
Link: https://docs.rs/syn/latest/syn [2]
Signed-off-by: Jesung Yang <y.j3ms.n at gmail.com>
---
rust/macros/convert.rs | 337 +++++++++++++++++++++++++++++++++++++++++
rust/macros/lib.rs | 124 +++++++++++++++
2 files changed, 461 insertions(+)
create mode 100644 rust/macros/convert.rs
diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
new file mode 100644
index 000000000000..0084bc4308c1
--- /dev/null
+++ b/rust/macros/convert.rs
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use proc_macro::{token_stream, Delimiter, Ident, Span, TokenStream, TokenTree};
+use std::iter::Peekable;
+
+#[derive(Debug)]
+struct TypeArgs {
+ helper: Vec<Ident>,
+ repr: Option<Ident>,
+}
+
+const VALID_TYPES: [&str; 12] = [
+ "u8", "u16", "u32", "u64",
"u128", "usize", "i8", "i16",
"i32", "i64", "i128", "isize",
+];
+
+pub(crate) fn derive_try_from(input: TokenStream) -> TokenStream {
+ derive(input)
+}
+
+fn derive(input: TokenStream) -> TokenStream {
+ let derive_target = "TryFrom";
+ let derive_helper = "try_from";
+
+ let mut tokens = input.into_iter().peekable();
+
+ let type_args = match parse_types(&mut tokens, derive_helper) {
+ Ok(type_args) => type_args,
+ Err(errs) => return errs,
+ };
+
+ // Skip until the `enum` keyword, including the `enum` itself.
+ for tt in tokens.by_ref() {
+ if matches!(tt, TokenTree::Ident(ident) if ident.to_string() ==
"enum") {
+ break;
+ }
+ }
+
+ let Some(TokenTree::Ident(enum_ident)) = tokens.next() else {
+ return format!(
+ "::core::compile_error!(\"`#[derive({derive_target})]`
can only \
+ be applied to an enum\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap();
+ };
+
+ let mut errs = TokenStream::new();
+
+ if matches!(tokens.peek(), Some(TokenTree::Punct(p)) if p.as_char() ==
'<') {
+ errs.extend(
+ format!(
+
"::core::compile_error!(\"`#[derive({derive_target})]` \
+ does not support enums with generic parameters\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap(),
+ );
+ }
+
+ let Some(variants_group) = tokens.find_map(|tt| match tt {
+ TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => Some(g),
+ _ => None,
+ }) else {
+ unreachable!("Enums have its corresponding body")
+ };
+
+ let enum_body_tokens = variants_group.stream().into_iter().peekable();
+ let variants = match parse_enum_variants(enum_body_tokens, &enum_ident,
derive_target) {
+ Ok(variants) => variants,
+ Err(new_errs) => {
+ errs.extend(new_errs);
+ return errs;
+ }
+ };
+
+ if !errs.is_empty() {
+ return errs;
+ }
+
+ if type_args.helper.is_empty() {
+ // Extract the representation passed by `#[repr(...)]` if present.
+ // If nothing is specified, the default is `Rust` representation,
+ // which uses `isize` for the discriminant type.
+ // See:
https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
+ let ty = type_args
+ .repr
+ .unwrap_or_else(|| Ident::new("isize",
Span::mixed_site()));
+ impl_try_from(&ty, &enum_ident, &variants)
+ } else {
+ let impls = type_args
+ .helper
+ .iter()
+ .map(|ty| impl_try_from(ty, &enum_ident, &variants));
+ quote! { #(#impls)* }
+ }
+}
+
+fn parse_types(
+ attrs: &mut Peekable<token_stream::IntoIter>,
+ helper: &str,
+) -> Result<TypeArgs, TokenStream> {
+ let mut helper_args = vec![];
+ let mut repr_arg = None;
+
+ // Scan only the attributes. As soon as we see a token that is
+ // not `#`, we know we have consumed all attributes.
+ while let Some(TokenTree::Punct(p)) = attrs.peek() {
+ if p.as_char() != '#' {
+ unreachable!("Outer attributes start with `#`");
+ }
+ attrs.next();
+
+ // The next token should be a `Group` delimited by brackets.
+ // (e.g., #[try_from(u8, u16)])
+ // ^^^^^^^^^^^^^^^^^^^
+ let Some(TokenTree::Group(attr_group)) = attrs.next() else {
+ unreachable!("Outer attributes are surrounded by `[` and
`]`");
+ };
+
+ let mut inner = attr_group.stream().into_iter();
+
+ // Extract the attribute identifier.
+ // (e.g., #[try_from(u8, u16)])
+ // ^^^^^^^^
+ let attr_name = match inner.next() {
+ Some(TokenTree::Ident(ident)) => ident.to_string(),
+ _ => unreachable!("Attributes have identifiers"),
+ };
+
+ if attr_name == helper {
+ match parse_helper_args(inner, helper) {
+ Ok(v) => helper_args.extend_from_slice(&v),
+ Err(errs) => return Err(errs),
+ }
+ } else if attr_name == "repr" {
+ repr_arg = parse_repr_args(inner);
+ }
+ }
+
+ Ok(TypeArgs {
+ helper: helper_args,
+ repr: repr_arg,
+ })
+}
+
+fn parse_repr_args(mut tt_group: impl Iterator<Item = TokenTree>) ->
Option<Ident> {
+ // The next token should be a `Group` delimited by parentheses.
+ // (e.g., #[repr(C, u8)])
+ // ^^^^^^^
+ let Some(TokenTree::Group(args_group)) = tt_group.next() else {
+ unreachable!("`repr` attribute has at least one argument")
+ };
+
+ for arg in args_group.stream() {
+ if let TokenTree::Ident(type_ident) = arg {
+ if VALID_TYPES.contains(&type_ident.to_string().as_str()) {
+ return Some(type_ident);
+ }
+ }
+ }
+
+ None
+}
+
+fn parse_helper_args(
+ mut tt_group: impl Iterator<Item = TokenTree>,
+ helper: &str,
+) -> Result<Vec<Ident>, TokenStream> {
+ let mut errs = TokenStream::new();
+ let mut args = vec![];
+
+ // The next token should be a `Group`.
+ // (e.g., #[try_from(u8, u16)])
+ // ^^^^^^^^^
+ let Some(TokenTree::Group(args_group)) = tt_group.next() else {
+ return Err(format!(
+ "::core::compile_error!(\"`{helper}` attribute expects at
\
+ least one integer type argument (e.g.,
`#[{helper}(u8)]`)\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap());
+ };
+
+ let raw_args = args_group.stream();
+ if raw_args.is_empty() {
+ return Err(format!(
+ "::core::compile_error!(\"`{helper}` attribute expects at
\
+ least one integer type argument (e.g.,
`#[{helper}(u8)]`)\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap());
+ }
+
+ // Iterate over the attribute argument tokens to collect valid integer
+ // type identifiers.
+ let mut raw_args = raw_args.into_iter();
+ while let Some(tt) = raw_args.next() {
+ let TokenTree::Ident(type_ident) = tt else {
+ errs.extend(
+ format!(
+ "::core::compile_error!(\"`{helper}` attribute
expects \
+ comma-separated integer type arguments \
+ (e.g., `#[{helper}(u8, u16)]`)\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap(),
+ );
+ return Err(errs);
+ };
+
+ if VALID_TYPES.contains(&type_ident.to_string().as_str()) {
+ args.push(type_ident);
+ } else {
+ errs.extend(
+ format!(
+ "::core::compile_error!(\"`{type_ident}` in
`{helper}` \
+ attribute is not an integer type\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap(),
+ );
+ }
+
+ match raw_args.next() {
+ Some(TokenTree::Punct(p)) if p.as_char() == ',' =>
continue,
+ None => break,
+ Some(_) => {
+ errs.extend(
+ format!(
+ "::core::compile_error!(\"`{helper}`
attribute expects \
+ comma-separated integer type arguments \
+ (e.g., `#[{helper}(u8, u16)]`)\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap(),
+ );
+ return Err(errs);
+ }
+ }
+ }
+
+ if !errs.is_empty() {
+ return Err(errs);
+ }
+
+ Ok(args)
+}
+
+fn parse_enum_variants(
+ mut tokens: Peekable<token_stream::IntoIter>,
+ enum_ident: &Ident,
+ derive_target: &str,
+) -> Result<Vec<Ident>, TokenStream> {
+ let mut errs = TokenStream::new();
+
+ let mut variants = vec![];
+
+ if tokens.peek().is_none() {
+ errs.extend(
+ format!(
+
"::core::compile_error!(\"`#[derive({derive_target})]` \
+ does not support zero-variant enums\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap(),
+ );
+ }
+
+ while let Some(tt) = tokens.next() {
+ // Skip attributes like `#[...]` if present.
+ if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '#')
{
+ tokens.next();
+ continue;
+ }
+
+ let TokenTree::Ident(ident) = tt else {
+ unreachable!("Enum variants have its corresponding
identifier");
+ };
+
+ // Reject tuple-like or struct-like variants.
+ if let Some(TokenTree::Group(g)) = tokens.peek() {
+ let variant_kind = match g.delimiter() {
+ Delimiter::Brace => "struct-like",
+ Delimiter::Parenthesis => "tuple-like",
+ _ => unreachable!("Invalid enum variant syntax"),
+ };
+ errs.extend(
+ format!(
+
"::core::compile_error!(\"`#[derive({derive_target})]` does not \
+ support {variant_kind} variant `{enum_ident}::{ident}`; \
+ only unit variants are allowed\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap(),
+ );
+ }
+
+ // Skip through the comma.
+ for tt in tokens.by_ref() {
+ if matches!(tt, TokenTree::Punct(p) if p.as_char() == ',')
{
+ break;
+ }
+ }
+
+ variants.push(ident);
+ }
+
+ if !errs.is_empty() {
+ return Err(errs);
+ }
+
+ Ok(variants)
+}
+
+fn impl_try_from(ty: &Ident, enum_ident: &Ident, variants:
&[Ident]) -> TokenStream {
+ let param = Ident::new("value", Span::mixed_site());
+
+ let clauses = variants.iter().map(|variant| {
+ quote! {
+ if #param == Self::#variant as #ty {
+ ::core::result::Result::Ok(Self::#variant)
+ } else
+ }
+ });
+
+ quote! {
+ #[automatically_derived]
+ impl ::core::convert::TryFrom<#ty> for #enum_ident {
+ type Error = ::kernel::prelude::Error;
+ fn try_from(#param: #ty) -> Result<Self, Self::Error> {
+ #(#clauses)* {
+ ::core::result::Result::Err(::kernel::prelude::EINVAL)
+ }
+ }
+ }
+ }
+}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index fa847cf3a9b5..569198f188f7 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -14,6 +14,7 @@
#[macro_use]
mod quote;
mod concat_idents;
+mod convert;
mod export;
mod helpers;
mod kunit;
@@ -425,3 +426,126 @@ pub fn paste(input: TokenStream) -> TokenStream {
pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
kunit::kunit_tests(attr, ts)
}
+
+/// A derive macro for generating an impl of the [`TryFrom`] trait.
+///
+/// This macro automatically derives [`TryFrom`] trait for a given enum.
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
+///
+/// The macro generates [`TryFrom`] implementations that:
+/// - Convert numeric values to enum variants by matching discriminant values.
+/// - Return `Ok(VARIANT)` for valid matches.
+/// - Return `Err(EINVAL)` for invalid matches (where `EINVAL` is from
+/// [`kernel::error::code`]).
+///
+/// The macro uses the `try_from` custom attribute or `repr` attribute to
generate
+/// corresponding [`TryFrom`] implementations. `try_from` always takes
precedence
+/// over `repr`.
+///
+/// [`kernel::error::code`]: ../kernel/error/code/index.html
+///
+/// # Caveats
+///
+/// Ensure that every integer type specified in `#[try_from(...)]` 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 `TryFrom<isize>`:
+///
+/// [repr-rs]:
https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
+///
+/// ```rust
+/// use kernel::macros::TryFrom;
+/// use kernel::prelude::*;
+///
+/// #[derive(Debug, Default, PartialEq, TryFrom)]
+/// enum Foo {
+/// #[default]
+/// A,
+/// B = 0x17,
+/// }
+///
+/// assert_eq!(Foo::try_from(0isize), Ok(Foo::A));
+/// assert_eq!(Foo::try_from(0x17isize), Ok(Foo::B));
+/// assert_eq!(Foo::try_from(0x19isize), Err(EINVAL));
+/// ```
+///
+/// ## With `#[repr(T)]`
+///
+/// The macro implements `TryFrom<T>`:
+///
+/// ```rust
+/// use kernel::macros::TryFrom;
+/// use kernel::prelude::*;
+///
+/// #[derive(Debug, Default, PartialEq, TryFrom)]
+/// #[repr(u8)]
+/// enum Foo {
+/// #[default]
+/// A,
+/// B = 0x17,
+/// }
+///
+/// assert_eq!(Foo::try_from(0u8), Ok(Foo::A));
+/// assert_eq!(Foo::try_from(0x17u8), Ok(Foo::B));
+/// assert_eq!(Foo::try_from(0x19u8), Err(EINVAL));
+/// ```
+///
+/// ## With `#[try_from(...)]`
+///
+/// The macro implements `TryFrom<T>` for each `T` specified in
`#[try_from(...)]`,
+/// which always overrides `#[repr(...)]`:
+///
+/// ```rust
+/// use kernel::macros::TryFrom;
+/// use kernel::prelude::*;
+///
+/// #[derive(Debug, Default, PartialEq, TryFrom)]
+/// #[try_from(u8, u16)]
+/// #[repr(u8)]
+/// enum Foo {
+/// #[default]
+/// A,
+/// B = 0x17,
+/// }
+///
+/// assert_eq!(Foo::try_from(0u16), Ok(Foo::A));
+/// assert_eq!(Foo::try_from(0x17u16), Ok(Foo::B));
+/// assert_eq!(Foo::try_from(0x19u16), Err(EINVAL));
+/// ```
+///
+/// ## Unsupported Cases
+///
+/// The following examples do not compile:
+///
+/// ```compile_fail
+/// # use kernel::macros::TryFrom;
+/// // Generic parameters are not allowed.
+/// #[derive(TryFrom)]
+/// enum Foo<T> {
+/// A,
+/// }
+///
+/// // Tuple-like enums or struct-like enums are not allowed.
+/// #[derive(TryFrom)]
+/// enum Bar {
+/// A(u8),
+/// B { inner: u8 },
+/// }
+///
+/// // Structs are not allowed.
+/// #[derive(TryFrom)]
+/// struct Baz(u8);
+/// ```
+#[proc_macro_derive(TryFrom, attributes(try_from))]
+pub fn derive_try_from(input: TokenStream) -> TokenStream {
+ convert::derive_try_from(input)
+}
--
2.39.5
Jesung Yang
2025-Aug-15 05:32 UTC
[PATCH v2 5/5] rust: macros: add derive macro for `Into`
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 <acourbot at nvidia.com>
Link: https://lore.kernel.org/rust-for-linux/20250624132337.2242-1-dakr at
kernel.org/ [1]
Signed-off-by: Jesung Yang <y.j3ms.n at gmail.com>
---
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
Alice Ryhl
2025-Aug-28 06:39 UTC
[PATCH v2 2/5] rust: macros: extend custom `quote!()` macro
On Fri, Aug 15, 2025 at 7:32?AM Jesung Yang <y.j3ms.n at gmail.com> wrote:> > Extend the `quote_spanned!()` macro to support additional punctuation > tokens: `->`, `<`, `>`, and `==`. This symbols are commonly needed when > dealing with functions, generic bounds, and equality comparisons. > > Tested-by: Alexandre Courbot <acourbot at nvidia.com> > Signed-off-by: Jesung Yang <y.j3ms.n at gmail.com> > --- > rust/macros/quote.rs | 30 ++++++++++++++++++++++++++++++ > 1 file changed, 30 insertions(+) > > diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs > index 8a89f0b1e785..24764b04a07d 100644 > --- a/rust/macros/quote.rs > +++ b/rust/macros/quote.rs > @@ -150,6 +150,36 @@ macro_rules! quote_spanned { > )); > quote_spanned!(@proc $v $span $($tt)*); > }; > + (@proc $v:ident $span:ident -> $($tt:tt)*) => { > + $v.push(::proc_macro::TokenTree::Punct( > + ::proc_macro::Punct::new('-', ::proc_macro::Spacing::Joint) > + )); > + $v.push(::proc_macro::TokenTree::Punct( > + ::proc_macro::Punct::new('>', ::proc_macro::Spacing::Alone) > + )); > + quote_spanned!(@proc $v $span $($tt)*); > + }; > + (@proc $v:ident $span:ident < $($tt:tt)*) => { > + $v.push(::proc_macro::TokenTree::Punct( > + ::proc_macro::Punct::new('<', ::proc_macro::Spacing::Alone) > + )); > + quote_spanned!(@proc $v $span $($tt)*); > + }; > + (@proc $v:ident $span:ident > $($tt:tt)*) => { > + $v.push(::proc_macro::TokenTree::Punct( > + ::proc_macro::Punct::new('>', ::proc_macro::Spacing::Alone) > + )); > + quote_spanned!(@proc $v $span $($tt)*); > + }; > + (@proc $v:ident $span:ident == $($tt:tt)*) => { > + $v.push(::proc_macro::TokenTree::Punct( > + ::proc_macro::Punct::new('=', ::proc_macro::Spacing::Joint) > + )); > + $v.push(::proc_macro::TokenTree::Punct( > + ::proc_macro::Punct::new('=', ::proc_macro::Spacing::Alone) > + )); > + quote_spanned!(@proc $v $span $($tt)*);Not a blocker, but if the way to implement this one is to push twice, then I think the pattern should just be a single = and then you push a = once. The pattern can match twice to handle ==. Alice
Alice Ryhl
2025-Aug-29 08:38 UTC
[PATCH v2 0/5] rust: add `TryFrom` and `Into` derive macros
On Fri, Aug 15, 2025 at 05:32:10AM +0000, Jesung Yang wrote:> This patch series introduces derive macros for `TryFrom` and `Into` > traits. > > A few enhancements were made to the custom `quote!()` macro to write > the derive macro. These include support for additional punctuation > tokens and a fix for an unused variable warning when quoting simple > forms. Detailed information about these enhancements is provided in the > relevant patches. > > This series builds on the previous work [1], where the `FromPrimitive` > trait was considered too heavy for the current use cases. In response > to the emerging need for functionality similar to `ToPrimitive`, this > series also implements the `Into` derive macro. > > The original discussion can be found on Zulip [2]. > > [1] https://lore.kernel.org/rust-for-linux/cover.1750689857.git.y.j3ms.n at gmail.com/ > [2] https://rust-for-linux.zulipchat.com/#narrow/channel/288089/topic/x/near/524335626Reviewed-by: Alice Ryhl <aliceryhl at google.com>
Alexandre Courbot
2025-Sep-30 04:58 UTC
[PATCH v2 2/5] rust: macros: extend custom `quote!()` macro
Hi Jesung, On Fri Aug 15, 2025 at 2:32 PM JST, Jesung Yang wrote:> Extend the `quote_spanned!()` macro to support additional punctuation > tokens: `->`, `<`, `>`, and `==`. This symbols are commonly needed when > dealing with functions, generic bounds, and equality comparisons. > > Tested-by: Alexandre Courbot <acourbot at nvidia.com> > Signed-off-by: Jesung Yang <y.j3ms.n at gmail.com> > ---Note that this patch doesn't apply cleanly in `rust-next`, I've had to add the following on top of it. I suggest waiting for -rc1 to be released and using it as a base for a new version - hopefully this will also give time for more feedback to come. diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs index 76a99f7e01c4..bb6970fd2a26 100644 --- a/rust/macros/quote.rs +++ b/rust/macros/quote.rs @@ -147,33 +147,33 @@ macro_rules! quote_spanned { quote_spanned!(@proc $v $span $($tt)*); }; (@proc $v:ident $span:ident -> $($tt:tt)*) => { - $v.push(::proc_macro::TokenTree::Punct( + $v.extend([::proc_macro::TokenTree::Punct( ::proc_macro::Punct::new('-', ::proc_macro::Spacing::Joint) - )); - $v.push(::proc_macro::TokenTree::Punct( + )]); + $v.extend([::proc_macro::TokenTree::Punct( ::proc_macro::Punct::new('>', ::proc_macro::Spacing::Alone) - )); + )]); quote_spanned!(@proc $v $span $($tt)*); }; (@proc $v:ident $span:ident < $($tt:tt)*) => { - $v.push(::proc_macro::TokenTree::Punct( + $v.extend([::proc_macro::TokenTree::Punct( ::proc_macro::Punct::new('<', ::proc_macro::Spacing::Alone) - )); + )]); quote_spanned!(@proc $v $span $($tt)*); }; (@proc $v:ident $span:ident > $($tt:tt)*) => { - $v.push(::proc_macro::TokenTree::Punct( + $v.extend([::proc_macro::TokenTree::Punct( ::proc_macro::Punct::new('>', ::proc_macro::Spacing::Alone) - )); + )]); quote_spanned!(@proc $v $span $($tt)*); }; (@proc $v:ident $span:ident == $($tt:tt)*) => { - $v.push(::proc_macro::TokenTree::Punct( + $v.extend([::proc_macro::TokenTree::Punct( ::proc_macro::Punct::new('=', ::proc_macro::Spacing::Joint) - )); - $v.push(::proc_macro::TokenTree::Punct( + )]); + $v.extend([::proc_macro::TokenTree::Punct( ::proc_macro::Punct::new('=', ::proc_macro::Spacing::Alone) - )); + )]); quote_spanned!(@proc $v $span $($tt)*); }; (@proc $v:ident $span:ident # $($tt:tt)*) => {
Miguel Ojeda
2025-Sep-30 20:13 UTC
[PATCH v2 0/5] rust: add `TryFrom` and `Into` derive macros
On Fri, Aug 15, 2025 at 7:32?AM Jesung Yang <y.j3ms.n at gmail.com> wrote:> > This patch series introduces derive macros for `TryFrom` and `Into` > traits.I took a quick look -- it is a nice series! Some notes: - My biggest concern is the overflow caveat, which is a fairly big one if one, especially if one is dealing with runtime values. Can we do better? Accessing the discriminant via `as` is available in const context, and you already have every variant easily available, so can we check that every variant fits in the relevant target types? For instance, by creating some item-level const blocks (`static_assert!`s) -- dummy example for an unsigned-to-unsigned case: const _: () = { assert! (E::A as u128 <= u8::MAX as u128); }; const _: () = { assert! (E::B as u128 <= u8::MAX as u128); }; ... and so on. There may be better ways to do it -- I just quickly brute forced it that unsigned case with what you already had for handling variants: variants.iter().map(|variant| { quote! { const _: () = { assert!(#enum_ident::#variant as u128 <= #ty::MAX as u128); }; } }); Maybe this was already discussed elsewhere and there is a reason not to do something like this, but it seems to me that we should try to avoid that risk. - On other news, I will post in the coming days the `syn` patches, and my plan is to merge them for next cycle, so when those are out, Benno thought you could give them a go (we can still merge this with your current approach and then convert, but still, more `syn` users and converting the existing macros would be nice :). (By the way, the linked patches about converting the existing macros to `syn` are an RFC in the sense that they cannot be applied, but having `syn` itself is something we already agreed on a long time ago.) - Couple nits: typo arise -> arises, and I would do `repr-rust` instead of `repr-rs` since that is the anchor in the reference that you are linking. Thanks a lot! Cheers, Miguel
Jesung Yang
2025-Oct-10 10:08 UTC
[PATCH v2 0/5] rust: add `TryFrom` and `Into` derive macros
Hi, On Wed, Oct 1, 2025 at 5:13?AM Miguel Ojeda <miguel.ojeda.sandonis at gmail.com> wrote: [...]> - My biggest concern is the overflow caveat, which is a fairly big > one if one, especially if one is dealing with runtime values. > > Can we do better? Accessing the discriminant via `as` is available > in const context, and you already have every variant easily available, > so can we check that every variant fits in the relevant target types? > > For instance, by creating some item-level const blocks > (`static_assert!`s) -- dummy example for an unsigned-to-unsigned case: > > const _: () = { assert! (E::A as u128 <= u8::MAX as u128); }; > const _: () = { assert! (E::B as u128 <= u8::MAX as u128); }; > ... > > and so on. There may be better ways to do it -- I just quickly > brute forced it that unsigned case with what you already had for > handling variants: > > variants.iter().map(|variant| { > quote! { > const _: () = { assert!(#enum_ident::#variant as u128 > <= #ty::MAX as u128); }; > } > }); > > Maybe this was already discussed elsewhere and there is a reason > not to do something like this, but it seems to me that we should try > to avoid that risk.Thanks, I see your point, and I agree that compile-time checking for potential overflows is a better and safer approach. That said, it becomes a bit trickier when dealing with conversions between signed and unsigned types, particularly when `u128` and `i128` are involved. For example: #[derive(TryFrom, Into)] #[try_from(u128)] #[into(u128)] #[repr(i128)] enum MyEnum { A = 0xffffffffffffffff0, // larger than u64::MAX B = -1 } In this case, since there's no numeric type that can encompass both `u128` and `i128`, I don't think we can express a compile-time assertion like the one you suggested. While such edge cases involving 128-bit numeric types are unlikely in practice, the broader challenge is that, in signed-to-unsigned conversions, I think it's difficult to detect overflows using only the `repr` type, the target type, and the discriminant value interpreted as the target type (please correct me if I've misunderstood something here). I'm considering an alternative approach: performing these checks while parsing the macro inputs, to handle all combinations of `repr` and target types (such as `u128` in the above example) in a unified way. I believe this makes the behavior easier to reason about and better covers edge cases like conversions between `i128` and `u128`. For example: const U128_ALLOWED: [&str; 9] ["u8", "i8", "u16", "i16", "i32", "u32", "u64", "i64", "u128"]; const I128_ALLOWED: [&str; 9] ["u8", "i8", "u16", "i16", "i32", "u32", "u64", "i64", "i128"]; ... // Use this function after parsing `#[repr(...)]`, `#[into(u128)]` // and `#[try_from(...)]`. fn check_overflow( discriminant_repr: &str, helper_inputs: Vec<&str> ) -> Vec<&str> { let mut violations = Vec::new(); if discriminant_repr == "u128" { for ty in helper_inputs.iter() { if !U128_ALLOWED.contains(&ty) { violations.push(ty); } } } else if discriminant_repr == "i128" { for ty in helper_inputs.iter() { if !I128_ALLOWED.contains(&ty) { violations.push(ty); } } } ... violations } This is a rough sketch, but it gives a consistent way to reject obviously invalid combinations early during parsing. I'd appreciate your thoughts -- does this approach seem reasonable to you as well?> - On other news, I will post in the coming days the `syn` patches, > and my plan is to merge them for next cycle, so when those are out, > Benno thought you could give them a go (we can still merge this with > your current approach and then convert, but still, more `syn` users > and converting the existing macros would be nice :). > > (By the way, the linked patches about converting the existing > macros to `syn` are an RFC in the sense that they cannot be applied, > but having `syn` itself is something we already agreed on a long time > ago.)Sounds good -- I'd be happy to give `syn` a try. It should simplify the parsing logic quite a bit, and I believe it'll also make things easier for reviewers.> - Couple nits: typo arise -> arises, and I would do `repr-rust` > instead of `repr-rs` since that is the anchor in the reference that > you are linking.Thanks, I'll fix them in v3. Best Regards, Jesung> > Thanks a lot! > > Cheers, > Miguel