* [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros
@ 2025-08-03 14:20 Jesung Yang
2025-08-03 14:20 ` [PATCH 1/4] rust: macros: extend custom `quote!()` macro Jesung Yang
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Jesung Yang @ 2025-08-03 14:20 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Alexandre Courbot
Cc: linux-kernel, rust-for-linux, nouveau, Jesung Yang
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@gmail.com/
[2] https://rust-for-linux.zulipchat.com/#narrow/channel/288089/topic/x/near/524335626
Jesung Yang (4):
rust: macros: extend custom `quote!()` macro
rust: macros: prefix variable `span` with underscore
rust: macro: add derive macro for `TryFrom`
rust: macro: 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
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/4] rust: macros: extend custom `quote!()` macro
2025-08-03 14:20 [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
@ 2025-08-03 14:20 ` Jesung Yang
2025-08-08 9:15 ` Alexandre Courbot
2025-08-03 14:20 ` [PATCH 2/4] rust: macros: prefix variable `span` with underscore Jesung Yang
` (3 subsequent siblings)
4 siblings, 1 reply; 7+ messages in thread
From: Jesung Yang @ 2025-08-03 14:20 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Alexandre Courbot
Cc: linux-kernel, rust-for-linux, nouveau, Jesung Yang
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.
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.
Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
---
rust/macros/quote.rs | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs
index 92cacc4067c9..24764b04a07d 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 {
@@ -144,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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/4] rust: macros: prefix variable `span` with underscore
2025-08-03 14:20 [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
2025-08-03 14:20 ` [PATCH 1/4] rust: macros: extend custom `quote!()` macro Jesung Yang
@ 2025-08-03 14:20 ` Jesung Yang
2025-08-03 14:20 ` [PATCH 3/4] rust: macro: add derive macro for `TryFrom` Jesung Yang
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Jesung Yang @ 2025-08-03 14:20 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Alexandre Courbot
Cc: linux-kernel, rust-for-linux, nouveau, Jesung Yang
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.
Signed-off-by: Jesung Yang <y.j3ms.n@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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/4] rust: macro: add derive macro for `TryFrom`
2025-08-03 14:20 [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
2025-08-03 14:20 ` [PATCH 1/4] rust: macros: extend custom `quote!()` macro Jesung Yang
2025-08-03 14:20 ` [PATCH 2/4] rust: macros: prefix variable `span` with underscore Jesung Yang
@ 2025-08-03 14:20 ` Jesung Yang
2025-08-03 14:20 ` [PATCH 4/4] rust: macro: add derive macro for `Into` Jesung Yang
2025-08-08 9:13 ` [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros Alexandre Courbot
4 siblings, 0 replies; 7+ messages in thread
From: Jesung Yang @ 2025-08-03 14:20 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Alexandre Courbot
Cc: linux-kernel, rust-for-linux, nouveau, Jesung Yang
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.
Link: https://lore.kernel.org/rust-for-linux/20250304225536.2033853-1-benno.lossin@proton.me [1]
Link: https://docs.rs/syn/latest/syn [2]
Signed-off-by: Jesung Yang <y.j3ms.n@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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 4/4] rust: macro: add derive macro for `Into`
2025-08-03 14:20 [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
` (2 preceding siblings ...)
2025-08-03 14:20 ` [PATCH 3/4] rust: macro: add derive macro for `TryFrom` Jesung Yang
@ 2025-08-03 14:20 ` Jesung Yang
2025-08-08 9:13 ` [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros Alexandre Courbot
4 siblings, 0 replies; 7+ messages in thread
From: Jesung Yang @ 2025-08-03 14:20 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Alexandre Courbot
Cc: linux-kernel, rust-for-linux, nouveau, Jesung Yang
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.
Link: https://lore.kernel.org/rust-for-linux/20250624132337.2242-1-dakr@kernel.org/ [1]
Signed-off-by: Jesung Yang <y.j3ms.n@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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros
2025-08-03 14:20 [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
` (3 preceding siblings ...)
2025-08-03 14:20 ` [PATCH 4/4] rust: macro: add derive macro for `Into` Jesung Yang
@ 2025-08-08 9:13 ` Alexandre Courbot
4 siblings, 0 replies; 7+ messages in thread
From: Alexandre Courbot @ 2025-08-08 9:13 UTC (permalink / raw)
To: Jesung Yang, Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: linux-kernel, rust-for-linux, nouveau
On Sun Aug 3, 2025 at 11:20 PM JST, 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@gmail.com/
> [2] https://rust-for-linux.zulipchat.com/#narrow/channel/288089/topic/x/near/524335626
I have tried this series on nova-core and it allowed me to remove 150
lines of boiletplate just like that. As far as nova-core is concerned,
this is a perfect fit.
For the larger kernel, where using the right type is essential and where
we want to avoid error paths whenever possible, I also feel like this is
a much better fit than the initial `FromPrimitive`. But it would be nice
to hear other opinions on the matter.
I will try to review as much as my proc-macro knowledge allows me to,
but for now:
Tested-by: Alexandre Courbot <acourbot@nvidia.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 1/4] rust: macros: extend custom `quote!()` macro
2025-08-03 14:20 ` [PATCH 1/4] rust: macros: extend custom `quote!()` macro Jesung Yang
@ 2025-08-08 9:15 ` Alexandre Courbot
0 siblings, 0 replies; 7+ messages in thread
From: Alexandre Courbot @ 2025-08-08 9:15 UTC (permalink / raw)
To: Jesung Yang, Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: linux-kernel, rust-for-linux, nouveau
On Sun Aug 3, 2025 at 11:20 PM JST, Jesung Yang wrote:
> 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.
>
> 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.
These two paragraphs feel like they should be split into separate
patches, as they are not related to one another AFAICT.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-08-08 9:15 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-03 14:20 [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
2025-08-03 14:20 ` [PATCH 1/4] rust: macros: extend custom `quote!()` macro Jesung Yang
2025-08-08 9:15 ` Alexandre Courbot
2025-08-03 14:20 ` [PATCH 2/4] rust: macros: prefix variable `span` with underscore Jesung Yang
2025-08-03 14:20 ` [PATCH 3/4] rust: macro: add derive macro for `TryFrom` Jesung Yang
2025-08-03 14:20 ` [PATCH 4/4] rust: macro: add derive macro for `Into` Jesung Yang
2025-08-08 9:13 ` [PATCH 0/4] rust: add `TryFrom` and `Into` derive macros Alexandre Courbot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).