* [PATCH 0/4] rust: add `FromPrimitive` support
@ 2025-06-23 15:14 Jesung Yang
2025-06-23 15:14 ` [PATCH 1/4] rust: introduce `FromPrimitive` trait Jesung Yang
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Jesung Yang @ 2025-06-23 15:14 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
Cc: linux-kernel, rust-for-linux, nouveau, Jesung Yang
This patch series introduces a new `FromPrimitive` trait along with its
corresponding derive macro.
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.
While cleaning up the implementations, I came across an alternative
form of the `FromPrimitive` trait that might better suit the current
use case. Since types that implement this trait may often rely on just
one `from_*` method, the following design could be a simpler fit:
trait FromPrimitive: Sized {
type Primitive;
fn from_bool(b: bool) -> Option<Self>
where
<Self as FromPrimitive>::Primitive: From<bool>,
{
Self::from_primitive(b.into())
}
fn from_primitive(n: Self::Primitive) -> Option<Self>;
}
This is just a thought and not something I feel strongly about, but I
wanted to share it in case others find the idea useful. Feedback or
suggestions are very welcome.
The original discussion of FromPrimitive can be found on Zulip [1].
[1] https://rust-for-linux.zulipchat.com/#narrow/channel/288089/topic/x/near/524427350
Jesung Yang (4):
rust: introduce `FromPrimitive` trait
rust: macros: extend custom `quote!` macro
rust: macros: prefix variable `span` with underscore
rust: macros: add derive macro for `FromPrimitive`
rust/kernel/convert.rs | 154 +++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
rust/macros/convert.rs | 217 +++++++++++++++++++++++++++++++++++++++++
rust/macros/lib.rs | 71 ++++++++++++++
rust/macros/quote.rs | 46 ++++++++-
5 files changed, 487 insertions(+), 2 deletions(-)
create mode 100644 rust/kernel/convert.rs
create mode 100644 rust/macros/convert.rs
base-commit: dc35ddcf97e99b18559d0855071030e664aae44d
--
2.39.5
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/4] rust: introduce `FromPrimitive` trait
2025-06-23 15:14 [PATCH 0/4] rust: add `FromPrimitive` support Jesung Yang
@ 2025-06-23 15:14 ` Jesung Yang
2025-06-23 15:14 ` [PATCH 2/4] rust: macros: extend custom `quote!` macro Jesung Yang
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Jesung Yang @ 2025-06-23 15:14 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
Cc: linux-kernel, rust-for-linux, nouveau, Jesung Yang
Introduce a new `FromPrimitive` trait under `kernel::convert` that
enables fallible conversion from primitive types to user-defined
types.
This is useful when numeric values need to be interpreted as structured
representations such as enums. These situations often arise when
working with low-level data sources, for example when reading values
from hardware registers.
Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
---
rust/kernel/convert.rs | 154 +++++++++++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
2 files changed, 155 insertions(+)
create mode 100644 rust/kernel/convert.rs
diff --git a/rust/kernel/convert.rs b/rust/kernel/convert.rs
new file mode 100644
index 000000000000..fb01a0e1507a
--- /dev/null
+++ b/rust/kernel/convert.rs
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Traits for type conversion.
+
+/// A trait for fallible conversions from primitive types.
+///
+/// [`FromPrimitive`] allows converting from various built-in primitive types
+/// (such as integers and `bool`) into a user-defined type, typically an `enum`.
+///
+/// At least [`from_i64`] and [`from_u64`] should be implemented. All other methods
+/// have default implementations that convert to `i64` or `u64` using fallible casts and
+/// delegate to those two core methods.
+///
+/// Enums with wide representations such as `#[repr(i128)]` or `#[repr(u128)]` may lose
+/// information through narrowing in the default implementations. In such cases, override
+/// [`from_i128`] and [`from_u128`] explicitly.
+///
+/// This trait can be used with `#[derive]`.
+/// See [`FromPrimitive`](../../macros/derive.FromPrimitive.html) derive macro for more
+/// information.
+///
+/// [`from_i64`]: FromPrimitive::from_i64
+/// [`from_i128`]: FromPrimitive::from_i128
+/// [`from_u64`]: FromPrimitive::from_u64
+/// [`from_u128`]: FromPrimitive::from_u128
+///
+/// # Examples
+///
+/// ```rust
+/// use kernel::convert::FromPrimitive;
+///
+/// #[derive(PartialEq)]
+/// enum Foo {
+/// A,
+/// B = 0x17,
+/// C = -2,
+/// }
+///
+/// impl FromPrimitive for Foo {
+/// fn from_i64(n: i64) -> Option<Self> {
+/// match n {
+/// 0 => Some(Self::A),
+/// 0x17 => Some(Self::B),
+/// -2 => Some(Self::C),
+/// _ => None,
+/// }
+/// }
+///
+/// fn from_u64(n: u64) -> Option<Self> {
+/// i64::try_from(n).ok().and_then(Self::from_i64)
+/// }
+/// }
+///
+/// assert_eq!(Foo::from_u64(0), Some(Foo::A));
+/// assert_eq!(Foo::from_u64(0x17), Some(Foo::B));
+/// assert_eq!(Foo::from_i64(-2), Some(Foo::C));
+/// assert_eq!(Foo::from_i64(-3), None);
+/// ```
+pub trait FromPrimitive: Sized {
+ /// Attempts to convert a `bool` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ #[inline]
+ fn from_bool(b: bool) -> Option<Self> {
+ Self::from_u64(u64::from(b))
+ }
+
+ /// Attempts to convert an `isize` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ #[inline]
+ fn from_isize(n: isize) -> Option<Self> {
+ i64::try_from(n).ok().and_then(Self::from_i64)
+ }
+
+ /// Attempts to convert an `i8` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ #[inline]
+ fn from_i8(n: i8) -> Option<Self> {
+ Self::from_i64(i64::from(n))
+ }
+
+ /// Attempts to convert an `i16` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ #[inline]
+ fn from_i16(n: i16) -> Option<Self> {
+ Self::from_i64(i64::from(n))
+ }
+
+ /// Attempts to convert an `i32` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ #[inline]
+ fn from_i32(n: i32) -> Option<Self> {
+ Self::from_i64(i64::from(n))
+ }
+
+ /// Attempts to convert an `i64` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ fn from_i64(n: i64) -> Option<Self>;
+
+ /// Attempts to convert an `i128` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ ///
+ /// The default implementation delegates to [`from_i64`](FromPrimitive::from_i64)
+ /// by downcasting from `i128` to `i64`, which may result in information loss.
+ /// Consider overriding this method if `Self` can represent values outside the
+ /// `i64` range.
+ #[inline]
+ fn from_i128(n: i128) -> Option<Self> {
+ i64::try_from(n).ok().and_then(Self::from_i64)
+ }
+
+ /// Attempts to convert a `usize` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ #[inline]
+ fn from_usize(n: usize) -> Option<Self> {
+ u64::try_from(n).ok().and_then(Self::from_u64)
+ }
+
+ /// Attempts to convert a `u8` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ #[inline]
+ fn from_u8(n: u8) -> Option<Self> {
+ Self::from_u64(u64::from(n))
+ }
+
+ /// Attempts to convert a `u16` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ #[inline]
+ fn from_u16(n: u16) -> Option<Self> {
+ Self::from_u64(u64::from(n))
+ }
+
+ /// Attempts to convert a `u32` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ #[inline]
+ fn from_u32(n: u32) -> Option<Self> {
+ Self::from_u64(u64::from(n))
+ }
+
+ /// Attempts to convert a `u64` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ fn from_u64(n: u64) -> Option<Self>;
+
+ /// Attempts to convert a `u128` to `Self`. Returns `Some(Self)` if the input
+ /// corresponds to a known value; otherwise, `None`.
+ ///
+ /// The default implementation delegates to [`from_u64`](FromPrimitive::from_u64)
+ /// by downcasting from `u128` to `u64`, which may result in information loss.
+ /// Consider overriding this method if `Self` can represent values outside the
+ /// `u64` range.
+ #[inline]
+ fn from_u128(n: u128) -> Option<Self> {
+ u64::try_from(n).ok().and_then(Self::from_u64)
+ }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 6b4774b2b1c3..861c9340d9c2 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -61,6 +61,7 @@
pub mod clk;
#[cfg(CONFIG_CONFIGFS_FS)]
pub mod configfs;
+pub mod convert;
pub mod cpu;
#[cfg(CONFIG_CPU_FREQ)]
pub mod cpufreq;
--
2.39.5
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/4] rust: macros: extend custom `quote!` macro
2025-06-23 15:14 [PATCH 0/4] rust: add `FromPrimitive` support Jesung Yang
2025-06-23 15:14 ` [PATCH 1/4] rust: introduce `FromPrimitive` trait Jesung Yang
@ 2025-06-23 15:14 ` Jesung Yang
2025-06-23 15:14 ` [PATCH 3/4] rust: macros: prefix variable `span` with underscore Jesung Yang
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Jesung Yang @ 2025-06-23 15:14 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
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.
Implement the `ToTokens` trait for `proc_macro::Literal`. This enables
the direct use of literals in the `quote!` macro, which is useful when
emitting numeric constants.
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 | 42 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs
index 92cacc4067c9..d05f60f55623 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 {
@@ -27,6 +33,12 @@ fn to_tokens(&self, tokens: &mut TokenStream) {
}
}
+impl ToTokens for proc_macro::Literal {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ tokens.extend([TokenTree::from(self.clone())]);
+ }
+}
+
impl ToTokens for TokenTree {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend([self.clone()]);
@@ -144,6 +156,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 3/4] rust: macros: prefix variable `span` with underscore
2025-06-23 15:14 [PATCH 0/4] rust: add `FromPrimitive` support Jesung Yang
2025-06-23 15:14 ` [PATCH 1/4] rust: introduce `FromPrimitive` trait Jesung Yang
2025-06-23 15:14 ` [PATCH 2/4] rust: macros: extend custom `quote!` macro Jesung Yang
@ 2025-06-23 15:14 ` Jesung Yang
2025-06-23 15:14 ` [PATCH 4/4] rust: macros: add derive macro for `FromPrimitive` Jesung Yang
2025-06-25 14:07 ` [PATCH 0/4] rust: add `FromPrimitive` support Alexandre Courbot
4 siblings, 0 replies; 7+ messages in thread
From: Jesung Yang @ 2025-06-23 15:14 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
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 d05f60f55623..e6c36bd7f925 100644
--- a/rust/macros/quote.rs
+++ b/rust/macros/quote.rs
@@ -63,8 +63,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 4/4] rust: macros: add derive macro for `FromPrimitive`
2025-06-23 15:14 [PATCH 0/4] rust: add `FromPrimitive` support Jesung Yang
` (2 preceding siblings ...)
2025-06-23 15:14 ` [PATCH 3/4] rust: macros: prefix variable `span` with underscore Jesung Yang
@ 2025-06-23 15:14 ` Jesung Yang
2025-06-25 14:07 ` [PATCH 0/4] rust: add `FromPrimitive` support Alexandre Courbot
4 siblings, 0 replies; 7+ messages in thread
From: Jesung Yang @ 2025-06-23 15:14 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
Cc: linux-kernel, rust-for-linux, nouveau, Jesung Yang
Introduce a procedural macro `FromPrimitive` to automatically implement
the `FromPrimitive` trait for unit-only enums.
Motivation
==========
This reduces boilerplate in cases where mapping a numeric value to a
corresponding enum variant is needed. 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.
Design
======
The macro currently rejects generics, zero-variant enums, tuple-like
variants, andstruct-like variants at compile time, as the intended use
case does not require support for those forms for now.
Each method implementation by the macro avoids silent overflows by
using per-variant constants and fallible `try_from` conversions, rather
than `as` casts. This is important for enums with wide representations
such as `#[repr(i128)]`, or enums with negative discriminants.
For example, in the case below, `Foo::from_u8(255)` returns `None`,
which is expected:
\#[derive(Debug, FromPrimitive)]
enum Foo {
A = -1,
}
// The result of macro expansion (relevant part only).
impl FromPrimitive for Foo {
fn from_u8(n: u8) -> Option<Self> {
const A: isize = -1;
if Ok(n) == u8::try_from(A) {
::core::option::Option::Some(Self::A)
} else {
::core::option::Option::None
}
}
}
assert_eq!(Foo::from_u8(255), None);
By contrast, in the following case, `Foo::from_u8(255)` returns
`Some(Foo::A)` due to a silent overflow.
impl FromPrimitive for Foo {
fn from_u8(n: u8) -> Option<Self> {
if n == Self::A as u8 {
::core::option::Option::Some(Self::A)
} else {
::core::option::Option::None
}
}
}
assert_eq!(Foo::from_u8(255), Some(Foo::A));
Parsing
=======
Only minimal token inspection is used for now, since a pending RFC [1]
proposes adding the `syn` crate [2] as a dependency. This macro keeps
its parsing logic narrow until that discussion is settled.
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 | 217 +++++++++++++++++++++++++++++++++++++++++
rust/macros/lib.rs | 71 ++++++++++++++
2 files changed, 288 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..99550eb9f841
--- /dev/null
+++ b/rust/macros/convert.rs
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use proc_macro::{token_stream, Delimiter, Ident, Literal, Span, TokenStream, TokenTree};
+use std::iter::Peekable;
+
+pub(crate) fn derive(input: TokenStream) -> TokenStream {
+ let mut tokens = input.into_iter().peekable();
+
+ // 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 repr_ty_ident =
+ get_repr(&mut tokens).unwrap_or_else(|| Ident::new("isize", Span::mixed_site()));
+
+ // 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 "::core::compile_error!(\"`#[derive(FromPrimitive)]` 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(
+ "::core::compile_error!(\"`#[derive(FromPrimitive)]` \
+ does not support enums with generic parameters\");"
+ .parse::<TokenStream>()
+ .unwrap(),
+ );
+ }
+
+ let variants_group = tokens
+ .find_map(|tt| match tt {
+ TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => Some(g),
+ _ => None,
+ })
+ .expect("Missing main body of an enum");
+
+ let zero = Literal::usize_unsuffixed(0);
+ let one = Literal::usize_unsuffixed(1);
+ let mut const_defs = vec![];
+ let mut variant_idents = vec![];
+ let mut variant_tokens = variants_group.stream().into_iter().peekable();
+
+ if variant_tokens.peek().is_none() {
+ return "::core::compile_error!(\"`#[derive(FromPrimitive)]` does not \
+ support zero-variant enums \");"
+ .parse::<TokenStream>()
+ .unwrap();
+ }
+
+ while let Some(tt) = variant_tokens.next() {
+ // Skip attributes like `#[...]` if present.
+ if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '#') {
+ variant_tokens.next();
+ continue;
+ }
+
+ let TokenTree::Ident(ident) = tt else {
+ unreachable!("Missing enum variant identifier");
+ };
+
+ // Reject tuple-like or struct-like variants.
+ if let Some(TokenTree::Group(g)) = variant_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(FromPrimitive)]` does not \
+ support {variant_kind} variant `{enum_ident}::{ident}`; \
+ only unit variants are allowed\");"
+ )
+ .parse::<TokenStream>()
+ .unwrap(),
+ );
+ }
+
+ let const_expr: TokenStream = match variant_tokens.next() {
+ Some(TokenTree::Punct(p)) if p.as_char() == '=' => {
+ // Extract the explicit discriminant, which is a constant expression.
+ // See: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.explicit.intro
+ variant_tokens
+ .by_ref()
+ .take_while(|tt| !matches!(&tt, TokenTree::Punct(p) if p.as_char() == ','))
+ .collect()
+ }
+ _ => {
+ // In this case, we have an implicit discriminant.
+ // Generate constant expression based on the previous identifier.
+ match variant_idents.last() {
+ Some(prev) => quote! { #prev + #one },
+ None => quote! { #zero },
+ }
+ }
+ };
+
+ // These constants, named after each variant identifier, help detect overflows.
+ const_defs.push(quote! {
+ #[allow(non_upper_case_globals)]
+ const #ident: #repr_ty_ident = #const_expr;
+ });
+
+ variant_idents.push(ident);
+ }
+
+ if !errs.is_empty() {
+ return errs;
+ }
+
+ // Implement `from_*` methods for these types; other types use default implementations
+ // that delegate to `from_i64` or `from_u64`. While `isize`, `i128`, `usize`, `u128`
+ // also have default implementations, providing explicit ones avoids relying on
+ // `u64::try_from`, which may silently fail (false negative) with `None` if the enum
+ // is marked with a wide representation like `#[repr(i128)]`.
+ let type_names = ["isize", "i64", "i128", "usize", "u64", "u128"];
+ let methods = type_names.into_iter().map(|ty| {
+ impl_method(
+ &Ident::new(ty, Span::mixed_site()),
+ &Ident::new(&format!("from_{ty}"), Span::mixed_site()),
+ &variant_idents,
+ &const_defs,
+ )
+ });
+
+ quote! {
+ #[automatically_derived]
+ impl FromPrimitive for #enum_ident {
+ #(#methods)*
+ }
+ }
+}
+
+fn get_repr(tokens: &mut Peekable<token_stream::IntoIter>) -> Option<Ident> {
+ const PRIM_REPRS: [&str; 12] = [
+ "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize",
+ ];
+
+ // Scan only the attributes. As soon as we see a token that is
+ // not `#`, we know we have consumed all attributes.
+ while let TokenTree::Punct(p) = tokens.peek()? {
+ if p.as_char() != '#' {
+ break;
+ }
+ tokens.next();
+
+ // The next token should be a `Group` delimited by brackets.
+ let TokenTree::Group(attr) = tokens.next()? else {
+ break;
+ };
+
+ let mut inner = attr.stream().into_iter();
+
+ // Skip attributes other than `repr`.
+ if !matches!(inner.next()?, TokenTree::Ident(ident) if ident.to_string() == "repr") {
+ continue;
+ }
+
+ // Extract arguments passed to `repr`.
+ let TokenTree::Group(repr_args) = inner.next()? else {
+ break;
+ };
+
+ // Look for any specified primitive representation in `#[repr(...)]` args.
+ for arg in repr_args.stream() {
+ if let TokenTree::Ident(ident) = arg {
+ if PRIM_REPRS.contains(&ident.to_string().as_str()) {
+ return Some(ident);
+ }
+ }
+ }
+ }
+
+ None
+}
+
+fn impl_method(
+ ty: &Ident,
+ method: &Ident,
+ variants: &[Ident],
+ const_defs: &[TokenStream],
+) -> TokenStream {
+ let param = Ident::new("n", Span::mixed_site());
+
+ // Discriminants can only be cast to integers using `as`, which may silently
+ // overflow. To avoid this, we use `try_from` on the defined constants instead.
+ // A failed conversion indicates an overflow, which means the value doesn't
+ // match the intended discriminant, so we fall through to the next clause.
+ let clauses = variants.iter().map(|ident| {
+ quote! {
+ if Ok(#param) == #ty::try_from(#ident) {
+ ::core::option::Option::Some(Self::#ident)
+ } else
+ }
+ });
+
+ quote! {
+ #[inline]
+ fn #method(#param: #ty) -> ::core::option::Option<Self> {
+ #(#const_defs)*
+ #(#clauses)* {
+ ::core::option::Option::None
+ }
+ }
+ }
+}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index fa847cf3a9b5..fe7a261cc078 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,73 @@ 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 trait [`FromPrimitive`].
+///
+/// This macro automatically derives [`FromPrimitive`] 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
+///
+/// When you manually implement [`FromPrimitive`], only `from_i64` and `from_u64` are
+/// required since the other `from_*` methods delegate to those two defaults. However,
+/// an enum may be marked with `#[repr(i128)]` or `#[repr(u128)]`, which means discriminants
+/// outside the 64-bit range would never be matched by the defaults. To avoid this silent
+/// failure, this macro also generates explicit `from_i128` and `from_u128` implementations.
+/// As a side note, `from_isize` and `from_usize` are also explicitly implemented, since
+/// converting from 64-bit integers to pointer-sized integers involves `Result`.
+///
+/// [`FromPrimitive`]: ../kernel/convert/trait.FromPrimitive.html
+///
+/// # Examples
+///
+/// You may give each variant an explicit discriminant; the macro uses those values during
+/// expansion.
+///
+/// ```rust
+/// use kernel::convert::FromPrimitive;
+/// use kernel::macros::FromPrimitive;
+///
+/// #[derive(Default, FromPrimitive)]
+/// #[repr(u8)]
+/// enum Foo {
+/// #[default]
+/// A,
+/// B = 0x17,
+/// }
+///
+/// assert_eq!(Foo::from_u8(0), Some(Foo::A));
+/// assert_eq!(Foo::from_u8(0x17), Some(Foo::B));
+/// assert_eq!(Foo::from_u8(0x19), None);
+/// ```
+///
+/// The following examples do not compile.
+///
+/// ```compile_fail
+/// # use kernel::convert::FromPrimitive;
+/// # use kernel::macros::FromPrimitive;
+/// // Generic parameters are not allowed.
+/// #[derive(FromPrimitive)]
+/// enum Foo<T> {
+/// A,
+/// }
+///
+/// // Tuple-like enums or struct-like enums are not allowed.
+/// #[derive(FromPrimitive)]
+/// enum Bar {
+/// A(u8),
+/// B {
+/// inner: u8,
+/// },
+/// }
+///
+/// // Structs are not allowed.
+/// #[derive(FromPrimitive)]
+/// struct Baz(u8);
+/// ```
+#[proc_macro_derive(FromPrimitive)]
+pub fn derive_from_primitive(input: TokenStream) -> TokenStream {
+ convert::derive(input)
+}
--
2.39.5
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 0/4] rust: add `FromPrimitive` support
2025-06-23 15:14 [PATCH 0/4] rust: add `FromPrimitive` support Jesung Yang
` (3 preceding siblings ...)
2025-06-23 15:14 ` [PATCH 4/4] rust: macros: add derive macro for `FromPrimitive` Jesung Yang
@ 2025-06-25 14:07 ` Alexandre Courbot
2025-06-26 14:23 ` Jesung Yang
4 siblings, 1 reply; 7+ messages in thread
From: Alexandre Courbot @ 2025-06-25 14:07 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 Tue Jun 24, 2025 at 12:14 AM JST, Jesung Yang wrote:
> This patch series introduces a new `FromPrimitive` trait along with its
> corresponding derive macro.
>
> 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.
Thanks for crafting this! I have been able to sucessfully use it to
provide the implementations needed for Nova's `register!()` macro.
>
> While cleaning up the implementations, I came across an alternative
> form of the `FromPrimitive` trait that might better suit the current
> use case. Since types that implement this trait may often rely on just
> one `from_*` method, the following design could be a simpler fit:
>
> trait FromPrimitive: Sized {
> type Primitive;
>
> fn from_bool(b: bool) -> Option<Self>
> where
> <Self as FromPrimitive>::Primitive: From<bool>,
> {
> Self::from_primitive(b.into())
> }
>
> fn from_primitive(n: Self::Primitive) -> Option<Self>;
> }
This alternative form looks like it could be more suitable for us
indeed.
The userspace `num::FromPrimitive` is a bit too exhaustive to my taste,
as it makes methods available to build from primitives that we don't
really want. For instance, if I have an enum type that should only be
built from a `u16` or larger (because it has variants with values bigger
than 256), then it will still have a `from_u8` method, which looks
terribly wrong to me as the very fact that you are trying to build from
a `u8` indicates that you have mistakenly truncated the value at some
point, and thus you will possibly obtain a different variant from the
one you would get if you hadn't!
So from this perspective, having an associated type to indicate the
valid primitive type like you suggest sounds like an excellent idea. I
probably also wouldn't mind if we only supported that specific type
either, as callers can make the required conversion themselves and doing
so actually forces them to be conscious of the types they are passing
and be warned of potential mismatches.
But I guess that if we do so we can just introduce a derive macro that
implements `TryFrom` for us, without needing to introduce a new trait. I
might be too focused on my own use-case and would like to hear other
usage perspectives for this work.
If you add an associated type, I guess this means the derive macro
should have a helper attribute to specify it?
Another important aspect discussed on Zulip is the counterpart to
`FromPrimitive`, `ToPrimitive`. Here I feel more strongly that we should
*not* follow that the userspace `num` crate does, i.e. having all
operations return an `Option` - that would result in a lot of unneeded
error-checking code in the kernel. No, here it is pretty clear that we
should only provide infallible methods for the types that can store all
the possible values. Which is basically... one or several `Into`
implementations?
So indeed I'd like to understand first whether we actually need a new
trait, or whether our needs can be met by derive macros that provide the
right `TryFrom` and `Into` implementations. For nova-core, I think the
latter would be ok, but maybe I am missing the larger picture.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 0/4] rust: add `FromPrimitive` support
2025-06-25 14:07 ` [PATCH 0/4] rust: add `FromPrimitive` support Alexandre Courbot
@ 2025-06-26 14:23 ` Jesung Yang
0 siblings, 0 replies; 7+ messages in thread
From: Jesung Yang @ 2025-06-26 14:23 UTC (permalink / raw)
To: Alexandre Courbot, 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 Wed, Jun 25, 2025 at 11:07 PM Alexandre Courbot <acourbot@nvidia.com> wrote:
>
> If you add an associated type, I guess this means the derive macro
> should have a helper attribute to specify it?
The current implementation tries to detect the enum's representation
(e.g., `#[repr(u8)]`) and falls back to `isize` if nothing is provided,
since it's the default [1]. However, when `#[repr(C)]` is used, the
internal representation is not controlled on the Rust side. To quote
the reference [2]:
For field-less enums, the C representation has the size and
alignment of the default enum size and alignment for the target
platform's C ABI.
Given this, it would definitely help with determinism if users provided
an explicit attribute. Being explicit also often improves clarity and
makes the intent more obvious.
[1]: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
[2]: https://doc.rust-lang.org/reference/type-layout.html?highlight=repr#r-layout.repr.c.enum
Best regards,
Jesung
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-06-26 14:23 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-23 15:14 [PATCH 0/4] rust: add `FromPrimitive` support Jesung Yang
2025-06-23 15:14 ` [PATCH 1/4] rust: introduce `FromPrimitive` trait Jesung Yang
2025-06-23 15:14 ` [PATCH 2/4] rust: macros: extend custom `quote!` macro Jesung Yang
2025-06-23 15:14 ` [PATCH 3/4] rust: macros: prefix variable `span` with underscore Jesung Yang
2025-06-23 15:14 ` [PATCH 4/4] rust: macros: add derive macro for `FromPrimitive` Jesung Yang
2025-06-25 14:07 ` [PATCH 0/4] rust: add `FromPrimitive` support Alexandre Courbot
2025-06-26 14:23 ` Jesung Yang
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).