public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros
@ 2026-01-29 14:32 Jesung Yang via B4 Relay
  2026-01-29 14:32 ` [PATCH v5 1/4] rust: macros: add derive macro for `Into` Jesung Yang via B4 Relay
                   ` (5 more replies)
  0 siblings, 6 replies; 16+ messages in thread
From: Jesung Yang via B4 Relay @ 2026-01-29 14:32 UTC (permalink / raw)
  To: Miguel Ojeda, 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 the `TryFrom` and `Into`
traits.

I dropped support for `#[repr(C)]` fieldless enums (i.e., disallow
deriving `TryFrom` and `Into` on those enums), since the actual
representation of discriminants is defined by rustc internally, and
documentation around it is not yet settled [1][2].

I would like to highlight some feedback from the previous series where I
have decided to retain the original behavior in this series. In part, I
feel more community feedback is needed to reach the right design; I've
also elaborated on my personal reasoning for each point below:

1) Require explicit `#[repr(..)]` for enums using the macros.

Given the drop of `#[repr(C)]` support, I believe defaulting to `isize`
when no `#[repr(..)]` is specified is safe, as it follows the
compiler's (documented) default behavior [3]. By "safe", I mean I see no
potential regressions or hidden issues; we even have a compile-time
overflow assertion which ensure all enum discriminants fit within the
types involved in the conversion.

2) Generate trait implementation for a type in `#[repr(..)]` even when
   the helper attribute is present. For example:

     #[derive(TryFrom)]
     #[try_from(bool)]
     #[repr(u8)]
     enum E {
         A = 0,
         B = 1,
     }

   make the above snippet generate both `TryFrom<u8>` and
   `TryFrom<bool>` implementations.

However, sometimes users might want to prevent generating trait
implementations for the type in `#[repr(..)]`, especially when the enum
encodes types like `bool` or `Bounded<u8, 4>` that cannot be used in
`#[repr(..)]`, like the above snippet. If we were to follow the feedback
directly, users would end up with an unnecessary `TryFrom<u8>`
implementation. Therefore, I think it is reasonable to treat the helper
attribute as an override that ignores the type in `#[repr(..)]` when
present.

One last point: the current `Into` implementation relies on
`Bounded::from_expr()`, which utilizes `build_assert!()`. So the
expanded result looks roughly like this:

  impl From<Enum> for Bounded<u8, 4> {
      fn from(value: Enum) -> Bounded<u8, 4> {
          // Compile-time assertions to guarantee `value` fits within
          // `u8` (Omitted)

          Bounded::<u8, 4>::from_expr(value as u8)
      }
  }

After some experimentation, it appears that the compiler correctly
optimizes out the assertion if (and only if) the `Bounded` type covers
all enum discriminants, though I'm not 100% certain of this behavior in
all cases. Can we approach this better?

Appreciate any feedback or suggestions.

[1] https://github.com/rust-lang/rust/issues/124403
[2] https://github.com/rust-lang/rust/pull/147017
[3] https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust

Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
---
Changes in v5:
- Drop support for `#[repr(C)]` enums. (Benno Lossin)
- Allow `#[repr(C, {primitive_int_type})]` for future-proofing.
  (Benno Lossin)
- Parse types in helper attributes into `syn::Type` prior to validation.
  (Benno Lossin)
- Replace doctests for `TryFrom` with correct examples. (Benno Lossin)
- Reorganize error reporting structure.
- Add documentation about `#[repr(C)]` fieldless enums.
- Rebase on commit a7c013f77953 ("Merge patch series "refactor Rust proc
  macros with `syn`"")
- Link to v4: https://lore.kernel.org/r/20251225-try-from-into-macro-v4-0-4a563d597836@gmail.com

Changes in v4:
- Fix typos.
- Link to (broken) v3: https://lore.kernel.org/rust-for-linux/cover.1766544407.git.y.j3ms.n@gmail.com/

Changes in v3:
- Use the vendored `syn` and `quote` crates.
- Support `kernel::num::Bounded`.
- Add compile-time overflow assertion.
- Add a comment about `#[repr(C)]` enums.
- Drop Tested-by and Reviewed-by tags, as the code structure has
  changed substantially. (Thanks for the previous reviews and testing!)
- Link to v2: https://lore.kernel.org/rust-for-linux/cover.1755235180.git.y.j3ms.n@gmail.com/

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.
- Link to v1: https://lore.kernel.org/rust-for-linux/cover.1754228164.git.y.j3ms.n@gmail.com/

---
Jesung Yang (4):
      rust: macros: add derive macro for `Into`
      rust: macros: add derive macro for `TryFrom`
      rust: macros: add private doctests for `Into` derive macro
      rust: macros: add private doctests for `TryFrom` derive macro

 rust/macros/convert.rs | 1599 ++++++++++++++++++++++++++++++++++++++++++++++++
 rust/macros/lib.rs     |  349 ++++++++++-
 2 files changed, 1947 insertions(+), 1 deletion(-)
---
base-commit: a7c013f779530190d0c1e1aa5e7c8a61f0bd479e
change-id: 20251225-try-from-into-macro-1665d0afcfc8

Best regards,
-- 
Jesung Yang <y.j3ms.n@gmail.com>



^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v5 1/4] rust: macros: add derive macro for `Into`
  2026-01-29 14:32 [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang via B4 Relay
@ 2026-01-29 14:32 ` Jesung Yang via B4 Relay
  2026-01-29 16:11   ` Charalampos Mitrodimas
  2026-01-29 14:32 ` [PATCH v5 2/4] rust: macros: add derive macro for `TryFrom` Jesung Yang via B4 Relay
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 16+ messages in thread
From: Jesung Yang via B4 Relay @ 2026-01-29 14:32 UTC (permalink / raw)
  To: Miguel Ojeda, 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

From: Jesung Yang <y.j3ms.n@gmail.com>

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].

The macro not only supports primitive types such as `bool` or `i8`, but
also `Bounded`, a wrapper around integer types limiting the number of
bits usable for value representation. This accommodates the shift toward
more restrictive register field representations in nova-core where
values are constrained to specific bit ranges.

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 | 520 +++++++++++++++++++++++++++++++++++++++++++++++++
 rust/macros/lib.rs     | 173 +++++++++++++++-
 2 files changed, 692 insertions(+), 1 deletion(-)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
new file mode 100644
index 000000000000..096e3c9fdc1b
--- /dev/null
+++ b/rust/macros/convert.rs
@@ -0,0 +1,520 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use proc_macro2::{
+    Span,
+    TokenStream, //
+};
+
+use std::fmt;
+
+use syn::{
+    parse_quote,
+    parse_str,
+    punctuated::Punctuated,
+    spanned::Spanned,
+    AngleBracketedGenericArguments,
+    Attribute,
+    Data,
+    DeriveInput,
+    Expr,
+    ExprLit,
+    Fields,
+    GenericArgument,
+    Ident,
+    Lit,
+    LitInt,
+    PathArguments,
+    PathSegment,
+    Token,
+    Type,
+    TypePath, //
+};
+
+pub(crate) fn derive_into(input: DeriveInput) -> syn::Result<TokenStream> {
+    derive(DeriveTarget::Into, input)
+}
+
+fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result<TokenStream> {
+    let data_enum = match input.data {
+        Data::Enum(data) => data,
+        Data::Struct(data) => {
+            let msg = format!(
+                "expected `enum`, found `struct`; \
+                `#[derive({})]` can only be applied to a unit-only enum",
+                target.get_trait_name(),
+            );
+            return Err(syn::Error::new(data.struct_token.span(), msg));
+        }
+        Data::Union(data) => {
+            let msg = format!(
+                "expected `enum`, found `union`; \
+                `#[derive({})]` can only be applied to a unit-only enum",
+                target.get_trait_name(),
+            );
+            return Err(syn::Error::new(data.union_token.span(), msg));
+        }
+    };
+
+    let mut errors: Option<syn::Error> = None;
+    let mut combine_error = |err| match errors.as_mut() {
+        Some(errors) => errors.combine(err),
+        None => errors = Some(err),
+    };
+
+    let (helper_tys, is_repr_c, repr_ty) = parse_attrs(target, &input.attrs)?;
+
+    let mut valid_helper_tys = Vec::with_capacity(helper_tys.len());
+    for ty in helper_tys {
+        match validate_type(&ty) {
+            Ok(valid_ty) => valid_helper_tys.push(valid_ty),
+            Err(err) => combine_error(err),
+        }
+    }
+
+    let mut is_unit_only = true;
+    for variant in &data_enum.variants {
+        match &variant.fields {
+            Fields::Unit => continue,
+            Fields::Named(_) => {
+                let msg = format!(
+                    "expected unit-like variant, found struct-like variant; \
+                    `#[derive({})]` can only be applied to a unit-only enum",
+                    target.get_trait_name(),
+                );
+                combine_error(syn::Error::new_spanned(variant, msg));
+            }
+            Fields::Unnamed(_) => {
+                let msg = format!(
+                    "expected unit-like variant, found tuple-like variant; \
+                    `#[derive({})]` can only be applied to a unit-only enum",
+                    target.get_trait_name(),
+                );
+                combine_error(syn::Error::new_spanned(variant, msg));
+            }
+        }
+
+        is_unit_only = false;
+    }
+
+    if is_repr_c && is_unit_only && repr_ty.is_none() {
+        let msg = "`#[repr(C)]` fieldless enums are not supported";
+        return Err(syn::Error::new(input.ident.span(), msg));
+    }
+
+    if let Some(errors) = errors {
+        return Err(errors);
+    }
+
+    let variants: Vec<_> = data_enum
+        .variants
+        .into_iter()
+        .map(|variant| variant.ident)
+        .collect();
+
+    // Extract the representation passed by `#[repr(...)]` if present. If nothing is
+    // specified, the default is `Rust` representation, which uses `isize` for its
+    // discriminant type.
+    // See: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
+    let repr_ty = repr_ty.unwrap_or_else(|| Ident::new("isize", Span::call_site()));
+
+    Ok(derive_for_enum(
+        target,
+        &input.ident,
+        &variants,
+        repr_ty,
+        valid_helper_tys,
+    ))
+}
+
+#[derive(Clone, Copy, Debug)]
+enum DeriveTarget {
+    Into,
+}
+
+impl DeriveTarget {
+    fn get_trait_name(&self) -> &'static str {
+        match self {
+            Self::Into => "Into",
+        }
+    }
+
+    fn get_helper_name(&self) -> &'static str {
+        match self {
+            Self::Into => "into",
+        }
+    }
+}
+
+fn parse_attrs(
+    target: DeriveTarget,
+    attrs: &[Attribute],
+) -> syn::Result<(Vec<Type>, bool, Option<Ident>)> {
+    let helper = target.get_helper_name();
+
+    let mut is_repr_c = false;
+    let mut repr_ty = None;
+    let mut helper_tys = Vec::new();
+    for attr in attrs {
+        if attr.path().is_ident("repr") {
+            attr.parse_nested_meta(|meta| {
+                let ident = meta.path.get_ident();
+                if let Some(i) = ident {
+                    if is_valid_primitive(i) {
+                        repr_ty = ident.cloned();
+                    } else if i == "C" {
+                        is_repr_c = true;
+                    }
+                }
+                // Delegate `repr` attribute validation to rustc.
+                Ok(())
+            })?;
+        } else if attr.path().is_ident(helper) && helper_tys.is_empty() {
+            let args = attr.parse_args_with(Punctuated::<Type, Token![,]>::parse_terminated)?;
+            helper_tys.extend(args);
+        }
+    }
+
+    Ok((helper_tys, is_repr_c, repr_ty))
+}
+
+fn derive_for_enum(
+    target: DeriveTarget,
+    enum_ident: &Ident,
+    variants: &[Ident],
+    repr_ty: Ident,
+    helper_tys: Vec<ValidTy>,
+) -> TokenStream {
+    let impl_fn = match target {
+        DeriveTarget::Into => impl_into,
+    };
+
+    let qualified_repr_ty: syn::Path = parse_quote! { ::core::primitive::#repr_ty };
+
+    return if helper_tys.is_empty() {
+        let ty = ValidTy::Primitive(repr_ty);
+        let implementation = impl_fn(enum_ident, variants, &qualified_repr_ty, &ty);
+        ::quote::quote! { #implementation }
+    } else {
+        let impls = helper_tys
+            .into_iter()
+            .map(|ty| impl_fn(enum_ident, variants, &qualified_repr_ty, &ty));
+        ::quote::quote! { #(#impls)* }
+    };
+
+    fn impl_into(
+        enum_ident: &Ident,
+        variants: &[Ident],
+        repr_ty: &syn::Path,
+        input_ty: &ValidTy,
+    ) -> TokenStream {
+        let param = Ident::new("value", Span::call_site());
+
+        let overflow_assertion = emit_overflow_assert(enum_ident, variants, repr_ty, input_ty);
+        let cast = match input_ty {
+            ValidTy::Bounded(inner) => {
+                let base_ty = inner.emit_qualified_base_ty();
+                let expr = parse_quote! { #param as #base_ty };
+                // Since the discriminant of `#param`, an enum variant, is determined
+                // at compile-time, we can rely on `Bounded::from_expr()`. It requires
+                // the provided expression to be verifiable at compile-time to avoid
+                // triggering a build error.
+                inner.emit_from_expr(&expr)
+            }
+            ValidTy::Primitive(ident) if ident == "bool" => {
+                ::quote::quote! { (#param as #repr_ty) == 1 }
+            }
+            qualified @ ValidTy::Primitive(_) => ::quote::quote! { #param as #qualified },
+        };
+
+        ::quote::quote! {
+            #[automatically_derived]
+            impl ::core::convert::From<#enum_ident> for #input_ty {
+                fn from(#param: #enum_ident) -> #input_ty {
+                    #overflow_assertion
+
+                    #cast
+                }
+            }
+        }
+    }
+
+    fn emit_overflow_assert(
+        enum_ident: &Ident,
+        variants: &[Ident],
+        repr_ty: &syn::Path,
+        input_ty: &ValidTy,
+    ) -> TokenStream {
+        let qualified_i128: syn::Path = parse_quote! { ::core::primitive::i128 };
+        let qualified_u128: syn::Path = parse_quote! { ::core::primitive::u128 };
+
+        let input_min = input_ty.emit_min();
+        let input_max = input_ty.emit_max();
+
+        let variant_fits = variants.iter().map(|variant| {
+            let msg = format!(
+                "enum discriminant overflow: \
+                `{enum_ident}::{variant}` does not fit in `{input_ty}`",
+            );
+            ::quote::quote! {
+                ::core::assert!(fits(#enum_ident::#variant as #repr_ty), #msg);
+            }
+        });
+
+        ::quote::quote! {
+            const _: () = {
+                const fn fits(d: #repr_ty) -> ::core::primitive::bool {
+                    // For every integer type, its minimum value always fits in `i128`.
+                    let dst_min = #input_min;
+                    // For every integer type, its maximum value always fits in `u128`.
+                    let dst_max = #input_max;
+
+                    #[allow(unused_comparisons)]
+                    let is_src_signed = #repr_ty::MIN < 0;
+                    #[allow(unused_comparisons)]
+                    let is_dst_signed = dst_min < 0;
+
+                    if is_src_signed && is_dst_signed {
+                        // Casting from a signed value to `i128` does not overflow since
+                        // `i128` is the largest signed primitive integer type.
+                        (d as #qualified_i128) >= (dst_min as #qualified_i128)
+                            && (d as #qualified_i128) <= (dst_max as #qualified_i128)
+                    } else if is_src_signed && !is_dst_signed {
+                        // Casting from a signed value greater than 0 to `u128` does not
+                        // overflow since `u128::MAX` is greater than `i128::MAX`.
+                        d >= 0 && (d as #qualified_u128) <= (dst_max as #qualified_u128)
+                    } else {
+                        // Casting from an unsigned value to `u128` does not overflow since
+                        // `u128` is the largest unsigned primitive integer type.
+                        (d as #qualified_u128) <= (dst_max as #qualified_u128)
+                    }
+                }
+
+                #(#variant_fits)*
+            };
+        }
+    }
+}
+
+enum ValidTy {
+    Bounded(Bounded),
+    Primitive(Ident),
+}
+
+impl ValidTy {
+    fn emit_min(&self) -> TokenStream {
+        match self {
+            Self::Bounded(inner) => inner.emit_min(),
+            Self::Primitive(ident) if ident == "bool" => {
+                ::quote::quote! { 0 }
+            }
+            qualified @ Self::Primitive(_) => ::quote::quote! { #qualified::MIN },
+        }
+    }
+
+    fn emit_max(&self) -> TokenStream {
+        match self {
+            Self::Bounded(inner) => inner.emit_max(),
+            Self::Primitive(ident) if ident == "bool" => {
+                ::quote::quote! { 1 }
+            }
+            qualified @ Self::Primitive(_) => ::quote::quote! { #qualified::MAX },
+        }
+    }
+}
+
+impl ::quote::ToTokens for ValidTy {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            Self::Bounded(inner) => inner.to_tokens(tokens),
+            Self::Primitive(ident) => {
+                let qualified_name: syn::Path = parse_quote! { ::core::primitive::#ident };
+                qualified_name.to_tokens(tokens)
+            }
+        }
+    }
+}
+
+impl fmt::Display for ValidTy {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Bounded(inner) => inner.fmt(f),
+            Self::Primitive(ident) => ident.fmt(f),
+        }
+    }
+}
+
+struct Bounded {
+    base_ty: Ident,
+    bits: LitInt,
+}
+
+impl Bounded {
+    const NAME: &'static str = "Bounded";
+    const QUALIFIED_NAME: &'static str = "::kernel::num::Bounded";
+
+    fn emit_from_expr(&self, expr: &Expr) -> TokenStream {
+        let Self { base_ty, bits, .. } = self;
+        let qualified_name: syn::Path = parse_str(Self::QUALIFIED_NAME).expect("valid path");
+        ::quote::quote! {
+            #qualified_name::<#base_ty, #bits>::from_expr(#expr)
+        }
+    }
+
+    fn emit_qualified_base_ty(&self) -> TokenStream {
+        let base_ty = &self.base_ty;
+        ::quote::quote! { ::core::primitive::#base_ty }
+    }
+
+    fn emit_min(&self) -> TokenStream {
+        let bits = &self.bits;
+        let base_ty = self.emit_qualified_base_ty();
+        ::quote::quote! { #base_ty::MIN >> (#base_ty::BITS - #bits) }
+    }
+
+    fn emit_max(&self) -> TokenStream {
+        let bits = &self.bits;
+        let base_ty = self.emit_qualified_base_ty();
+        ::quote::quote! { #base_ty::MAX >> (#base_ty::BITS - #bits) }
+    }
+}
+
+impl ::quote::ToTokens for Bounded {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let bits = &self.bits;
+        let base_ty = self.emit_qualified_base_ty();
+        let qualified_name: syn::Path = parse_str(Self::QUALIFIED_NAME).expect("valid path");
+
+        tokens.extend(::quote::quote! {
+            #qualified_name<#base_ty, #bits>
+        });
+    }
+}
+
+impl fmt::Display for Bounded {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}<{}, {}>", Self::NAME, self.base_ty, self.bits)
+    }
+}
+
+fn validate_type(ty: &Type) -> syn::Result<ValidTy> {
+    let Type::Path(type_path) = ty else {
+        return Err(make_err(ty));
+    };
+
+    let TypePath { qself, path } = type_path;
+    if qself.is_some() {
+        return Err(make_err(ty));
+    }
+
+    let syn::Path {
+        leading_colon,
+        segments,
+    } = path;
+    if leading_colon.is_some() || segments.len() != 1 {
+        return Err(make_err(ty));
+    }
+
+    let segment = &path.segments[0];
+    if segment.ident == Bounded::NAME {
+        return validate_bounded(segment);
+    } else {
+        return validate_primitive(&segment.ident);
+    }
+
+    fn make_err(ty: &Type) -> syn::Error {
+        let msg = format!(
+            "expected unqualified form of `bool`, primitive integer type, or `{}<T, N>`",
+            Bounded::NAME,
+        );
+        syn::Error::new_spanned(ty, msg)
+    }
+}
+
+fn validate_bounded(path_segment: &PathSegment) -> syn::Result<ValidTy> {
+    let PathSegment { ident, arguments } = path_segment;
+    return match arguments {
+        PathArguments::AngleBracketed(inner) if ident == Bounded::NAME => {
+            let AngleBracketedGenericArguments {
+                colon2_token, args, ..
+            } = inner;
+
+            if colon2_token.is_some() {
+                return Err(make_outer_err(path_segment));
+            }
+
+            if args.len() != 2 {
+                return Err(make_outer_err(path_segment));
+            }
+
+            let (base_ty, bits) = (&args[0], &args[1]);
+            let GenericArgument::Type(Type::Path(base_ty_lowered)) = base_ty else {
+                return Err(make_base_ty_err(base_ty));
+            };
+
+            if base_ty_lowered.qself.is_some() {
+                return Err(make_base_ty_err(base_ty));
+            }
+
+            let Some(base_ty_ident) = base_ty_lowered.path.get_ident() else {
+                return Err(make_base_ty_err(base_ty));
+            };
+
+            if !is_valid_primitive(base_ty_ident) {
+                return Err(make_base_ty_err(base_ty));
+            }
+
+            let GenericArgument::Const(Expr::Lit(ExprLit {
+                lit: Lit::Int(bits),
+                ..
+            })) = bits
+            else {
+                return Err(syn::Error::new_spanned(bits, "expected integer literal"));
+            };
+
+            let bounded = Bounded {
+                base_ty: base_ty_ident.clone(),
+                bits: bits.clone(),
+            };
+            Ok(ValidTy::Bounded(bounded))
+        }
+        _ => Err(make_outer_err(path_segment)),
+    };
+
+    fn make_outer_err(path_segment: &PathSegment) -> syn::Error {
+        let msg = format!("expected `{0}<T, N>` (e.g., {0}<u8, 4>)", Bounded::NAME);
+        syn::Error::new_spanned(path_segment, msg)
+    }
+
+    fn make_base_ty_err(base_ty: &GenericArgument) -> syn::Error {
+        let msg = "expected unqualified form of primitive integer type";
+        syn::Error::new_spanned(base_ty, msg)
+    }
+}
+
+fn validate_primitive(ident: &Ident) -> syn::Result<ValidTy> {
+    if is_valid_primitive(ident) {
+        return Ok(ValidTy::Primitive(ident.clone()));
+    }
+    let msg =
+        format!("expected `bool` or primitive integer type (e.g., `u8`, `i8`), found {ident}");
+    Err(syn::Error::new(ident.span(), msg))
+}
+
+fn is_valid_primitive(ident: &Ident) -> bool {
+    matches!(
+        ident.to_string().as_str(),
+        "bool"
+            | "u8"
+            | "u16"
+            | "u32"
+            | "u64"
+            | "u128"
+            | "usize"
+            | "i8"
+            | "i16"
+            | "i32"
+            | "i64"
+            | "i128"
+            | "isize"
+    )
+}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 85b7938c08e5..8842067d1017 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -12,6 +12,7 @@
 #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
 
 mod concat_idents;
+mod convert;
 mod export;
 mod fmt;
 mod helpers;
@@ -22,7 +23,10 @@
 
 use proc_macro::TokenStream;
 
-use syn::parse_macro_input;
+use syn::{
+    parse_macro_input,
+    DeriveInput, //
+};
 
 /// Declares a kernel module.
 ///
@@ -486,3 +490,170 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
         .unwrap_or_else(|e| e.into_compile_error())
         .into()
 }
+
+/// A derive macro for providing an implementation of the [`Into`] trait.
+///
+/// This macro automatically derives the [`Into`] trait for a given enum by generating
+/// the relevant [`From`] implementation. Currently, it only supports [unit-only enum]s.
+///
+/// [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`.
+///
+/// - Currently, the macro does not support `repr(C)` fieldless enums since the actual
+///   representation of discriminants is defined by rustc internally, and documentation
+///   around it is not yet settled. See [Rust issue #124403] and [Rust PR #147017]
+///   for more information.
+///
+/// - The macro generates a compile-time assertion for every variant to ensure its
+///   discriminant value fits within the type being converted into.
+///
+/// [Rust issue #124403]: https://github.com/rust-lang/rust/issues/124403
+/// [Rust PR #147017]: https://github.com/rust-lang/rust/pull/147017
+///
+/// # Supported types in `#[into(...)]`
+///
+/// - [`bool`]
+/// - Primitive integer types (e.g., [`i8`], [`u8`])
+/// - [`Bounded`]
+///
+/// [`Bounded`]: ../kernel/num/bounded/struct.Bounded.html
+///
+/// # Examples
+///
+/// ## Without Attributes
+///
+/// Since [the default `Rust` representation uses `isize` for the discriminant type][repr-rust],
+/// the macro implements `From<Foo>` for `isize`:
+///
+/// [repr-rust]: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
+///
+/// ```
+/// use kernel::macros::Into;
+///
+/// #[derive(Debug, Default, Into)]
+/// enum Foo {
+///     #[default]
+///     A,
+///     B = 0x7,
+/// }
+///
+/// assert_eq!(0_isize, Foo::A.into());
+/// assert_eq!(0x7_isize, Foo::B.into());
+/// ```
+///
+/// ## With `#[repr(T)]`
+///
+/// The macro implements `From<Foo>` for `T`:
+///
+/// ```
+/// use kernel::macros::Into;
+///
+/// #[derive(Debug, Default, Into)]
+/// #[repr(u8)]
+/// enum Foo {
+///     #[default]
+///     A,
+///     B = 0x7,
+/// }
+///
+/// assert_eq!(0_u8, Foo::A.into());
+/// assert_eq!(0x7_u8, Foo::B.into());
+/// ```
+///
+/// ## With `#[into(...)]`
+///
+/// The macro implements `From<Foo>` for each `T` specified in `#[into(...)]`,
+/// which always overrides `#[repr(...)]`:
+///
+/// ```
+/// use kernel::{
+///     macros::Into,
+///     num::Bounded, //
+/// };
+///
+/// #[derive(Debug, Default, Into)]
+/// #[into(bool, i16, Bounded<u8, 4>)]
+/// #[repr(u8)]
+/// enum Foo {
+///     #[default]
+///     A,
+///     B,
+/// }
+///
+/// assert_eq!(false, Foo::A.into());
+/// assert_eq!(true, Foo::B.into());
+///
+/// assert_eq!(0_i16, Foo::A.into());
+/// assert_eq!(1_i16, Foo::B.into());
+///
+/// let foo_a: Bounded<u8, 4> = Foo::A.into();
+/// let foo_b: Bounded<u8, 4> = Foo::B.into();
+/// assert_eq!(Bounded::<u8, 4>::new::<0>(), foo_a);
+/// assert_eq!(Bounded::<u8, 4>::new::<1>(), foo_b);
+/// ```
+///
+/// ## Compile-time Overflow Assertion
+///
+/// The following examples do not compile:
+///
+/// ```compile_fail
+/// # use kernel::macros::Into;
+/// #[derive(Into)]
+/// #[into(u8)]
+/// enum Foo {
+///     // `256` is larger than `u8::MAX`.
+///     A = 256,
+/// }
+/// ```
+///
+/// ```compile_fail
+/// # use kernel::macros::Into;
+/// #[derive(Into)]
+/// #[into(u8)]
+/// enum Foo {
+///     // `-1` cannot be represented with `u8`.
+///     A = -1,
+/// }
+/// ```
+///
+/// ## Unsupported Cases
+///
+/// The following examples do not compile:
+///
+/// ```compile_fail
+/// # use kernel::macros::Into;
+/// // Tuple-like enums or struct-like enums are not allowed.
+/// #[derive(Into)]
+/// enum Foo {
+///     A(u8),
+///     B { inner: u8 },
+/// }
+/// ```
+///
+/// ```compile_fail
+/// # use kernel::macros::Into;
+/// // Structs are not allowed.
+/// #[derive(Into)]
+/// struct Foo(u8);
+/// ```
+///
+/// ```compile_fail
+/// # use kernel::macros::Into;
+/// // `repr(C)` enums are not allowed.
+/// #[derive(Into)]
+/// struct Foo(u8);
+/// ```
+#[proc_macro_derive(Into, attributes(into))]
+pub fn derive_into(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    convert::derive_into(input)
+        .unwrap_or_else(syn::Error::into_compile_error)
+        .into()
+}

-- 
2.52.0



^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v5 2/4] rust: macros: add derive macro for `TryFrom`
  2026-01-29 14:32 [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang via B4 Relay
  2026-01-29 14:32 ` [PATCH v5 1/4] rust: macros: add derive macro for `Into` Jesung Yang via B4 Relay
@ 2026-01-29 14:32 ` Jesung Yang via B4 Relay
  2026-02-04  1:39   ` Charalampos Mitrodimas
  2026-01-29 14:32 ` [PATCH v5 3/4] rust: macros: add private doctests for `Into` derive macro Jesung Yang via B4 Relay
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 16+ messages in thread
From: Jesung Yang via B4 Relay @ 2026-01-29 14:32 UTC (permalink / raw)
  To: Miguel Ojeda, 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

From: Jesung Yang <y.j3ms.n@gmail.com>

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 arises 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.

The macro not only supports primitive types such as `bool` or `i8`, but
also `Bounded`, a wrapper around integer types limiting the number of
bits usable for value representation. This accommodates the shift toward
more restrictive register field representations in nova-core where
values are constrained to specific bit ranges.

Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
---
 rust/macros/convert.rs |  64 ++++++++++++++++++
 rust/macros/lib.rs     | 176 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 240 insertions(+)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index 096e3c9fdc1b..a7a43b1a2caf 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -34,6 +34,10 @@ pub(crate) fn derive_into(input: DeriveInput) -> syn::Result<TokenStream> {
     derive(DeriveTarget::Into, input)
 }
 
+pub(crate) fn derive_try_from(input: DeriveInput) -> syn::Result<TokenStream> {
+    derive(DeriveTarget::TryFrom, input)
+}
+
 fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result<TokenStream> {
     let data_enum = match input.data {
         Data::Enum(data) => data,
@@ -129,18 +133,21 @@ fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result<TokenStream>
 #[derive(Clone, Copy, Debug)]
 enum DeriveTarget {
     Into,
+    TryFrom,
 }
 
 impl DeriveTarget {
     fn get_trait_name(&self) -> &'static str {
         match self {
             Self::Into => "Into",
+            Self::TryFrom => "TryFrom",
         }
     }
 
     fn get_helper_name(&self) -> &'static str {
         match self {
             Self::Into => "into",
+            Self::TryFrom => "try_from",
         }
     }
 }
@@ -186,6 +193,7 @@ fn derive_for_enum(
 ) -> TokenStream {
     let impl_fn = match target {
         DeriveTarget::Into => impl_into,
+        DeriveTarget::TryFrom => impl_try_from,
     };
 
     let qualified_repr_ty: syn::Path = parse_quote! { ::core::primitive::#repr_ty };
@@ -238,6 +246,54 @@ fn from(#param: #enum_ident) -> #input_ty {
         }
     }
 
+    fn impl_try_from(
+        enum_ident: &Ident,
+        variants: &[Ident],
+        repr_ty: &syn::Path,
+        input_ty: &ValidTy,
+    ) -> TokenStream {
+        let param = Ident::new("value", Span::call_site());
+
+        let overflow_assertion = emit_overflow_assert(enum_ident, variants, repr_ty, input_ty);
+        let emit_cast = |variant| {
+            let variant = ::quote::quote! { #enum_ident::#variant };
+            match input_ty {
+                ValidTy::Bounded(inner) => {
+                    let base_ty = inner.emit_qualified_base_ty();
+                    let expr = parse_quote! { #variant as #base_ty };
+                    inner.emit_new(&expr)
+                }
+                ValidTy::Primitive(ident) if ident == "bool" => {
+                    ::quote::quote! { ((#variant as #repr_ty) == 1) }
+                }
+                qualified @ ValidTy::Primitive(_) => ::quote::quote! { #variant as #qualified },
+            }
+        };
+
+        let clauses = variants.iter().map(|variant| {
+            let cast = emit_cast(variant);
+            ::quote::quote! {
+                if #param == #cast {
+                    ::core::result::Result::Ok(#enum_ident::#variant)
+                } else
+            }
+        });
+
+        ::quote::quote! {
+            #[automatically_derived]
+            impl ::core::convert::TryFrom<#input_ty> for #enum_ident {
+                type Error = ::kernel::prelude::Error;
+                fn try_from(#param: #input_ty) -> Result<#enum_ident, Self::Error> {
+                    #overflow_assertion
+
+                    #(#clauses)* {
+                        ::core::result::Result::Err(::kernel::prelude::EINVAL)
+                    }
+                }
+            }
+        }
+    }
+
     fn emit_overflow_assert(
         enum_ident: &Ident,
         variants: &[Ident],
@@ -360,6 +416,14 @@ fn emit_from_expr(&self, expr: &Expr) -> TokenStream {
         }
     }
 
+    fn emit_new(&self, expr: &Expr) -> TokenStream {
+        let Self { base_ty, bits, .. } = self;
+        let qualified_name: syn::Path = parse_str(Self::QUALIFIED_NAME).expect("valid path");
+        ::quote::quote! {
+            #qualified_name::<#base_ty, #bits>::new::<{ #expr }>()
+        }
+    }
+
     fn emit_qualified_base_ty(&self) -> TokenStream {
         let base_ty = &self.base_ty;
         ::quote::quote! { ::core::primitive::#base_ty }
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 8842067d1017..893adecb9080 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -657,3 +657,179 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
         .unwrap_or_else(syn::Error::into_compile_error)
         .into()
 }
+
+/// A derive macro for generating an implementation of the [`TryFrom`] trait.
+///
+/// This macro automatically derives the [`TryFrom`] trait for a given enum. Currently,
+/// it only supports [unit-only enum]s.
+///
+/// [unit-only enum]: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.unit-only
+///
+/// # Notes
+///
+/// - The macro generates [`TryFrom`] implementations that:
+///   - Return `Ok(VARIANT)` when the input corresponds to a variant.
+///   - Return `Err(EINVAL)` when the input does not correspond to any variant.
+///     (where `EINVAL` is from [`kernel::error::code`]).
+///
+/// - The macro uses the `try_from` custom attribute or `repr` attribute to generate
+///   [`TryFrom`] implementations. `try_from` always takes precedence over `repr`.
+///
+/// - Currently, the macro does not support `repr(C)` fieldless enums since the actual
+///   representation of discriminants is defined by rustc internally, and documentation
+///   around it is not yet settled. See [Rust issue #124403] and [Rust PR #147017]
+///   for more information.
+///
+/// - The macro generates a compile-time assertion for every variant to ensure its
+///   discriminant value fits within the type being converted from.
+///
+/// [`kernel::error::code`]: ../kernel/error/code/index.html
+/// [Rust issue #124403]: https://github.com/rust-lang/rust/issues/124403
+/// [Rust PR #147017]: https://github.com/rust-lang/rust/pull/147017
+///
+/// # Supported types in `#[try_from(...)]`
+///
+/// - [`bool`]
+/// - Primitive integer types (e.g., [`i8`], [`u8`])
+/// - [`Bounded`]
+///
+/// [`Bounded`]: ../kernel/num/bounded/struct.Bounded.html
+///
+/// # Examples
+///
+/// ## Without Attributes
+///
+/// Since [the default `Rust` representation uses `isize` for the discriminant type][repr-rust],
+/// the macro implements `TryFrom<isize>`:
+///
+/// [repr-rust]: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
+///
+/// ```rust
+/// # use kernel::prelude::*;
+/// use kernel::macros::TryFrom;
+///
+/// #[derive(Debug, Default, PartialEq, TryFrom)]
+/// enum Foo {
+///     #[default]
+///     A,
+///     B = 0x7,
+/// }
+///
+/// assert_eq!(Err(EINVAL), Foo::try_from(-1_isize));
+/// assert_eq!(Ok(Foo::A), Foo::try_from(0_isize));
+/// assert_eq!(Ok(Foo::B), Foo::try_from(0x7_isize));
+/// assert_eq!(Err(EINVAL), Foo::try_from(0x8_isize));
+/// ```
+///
+/// ## With `#[repr(T)]`
+///
+/// The macro implements `TryFrom<T>`:
+///
+/// ```rust
+/// # use kernel::prelude::*;
+/// use kernel::macros::TryFrom;
+///
+/// #[derive(Debug, Default, PartialEq, TryFrom)]
+/// #[repr(u8)]
+/// enum Foo {
+///     #[default]
+///     A,
+///     B = 0x7,
+/// }
+///
+/// assert_eq!(Ok(Foo::A), Foo::try_from(0_u8));
+/// assert_eq!(Ok(Foo::B), Foo::try_from(0x7_u8));
+/// assert_eq!(Err(EINVAL), Foo::try_from(0x8_u8));
+/// ```
+///
+/// ## With `#[try_from(...)]`
+///
+/// The macro implements `TryFrom<T>` for each `T` specified in `#[try_from(...)]`,
+/// which always overrides `#[repr(...)]`:
+///
+/// ```rust
+/// # use kernel::prelude::*;
+/// use kernel::{
+///     macros::TryFrom,
+///     num::Bounded, //
+/// };
+///
+/// #[derive(Debug, Default, PartialEq, TryFrom)]
+/// #[try_from(bool, i16, Bounded<u8, 4>)]
+/// #[repr(u8)]
+/// enum Foo {
+///     #[default]
+///     A,
+///     B,
+/// }
+///
+/// assert_eq!(Err(EINVAL), Foo::try_from(-1_i16));
+/// assert_eq!(Ok(Foo::A), Foo::try_from(0_i16));
+/// assert_eq!(Ok(Foo::B), Foo::try_from(1_i16));
+/// assert_eq!(Err(EINVAL), Foo::try_from(2_i16));
+///
+/// assert_eq!(Ok(Foo::A), Foo::try_from(false));
+/// assert_eq!(Ok(Foo::B), Foo::try_from(true));
+///
+/// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u8, 4>::new::<0>()));
+/// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 4>::new::<1>()));
+/// ```
+///
+/// ## Compile-time Overflow Assertion
+///
+/// The following examples do not compile:
+///
+/// ```compile_fail
+/// # use kernel::macros::TryFrom;
+/// #[derive(TryFrom)]
+/// #[try_from(u8)]
+/// enum Foo {
+///     // `256` is larger than `u8::MAX`.
+///     A = 256,
+/// }
+/// ```
+///
+/// ```compile_fail
+/// # use kernel::macros::TryFrom;
+/// #[derive(TryFrom)]
+/// #[try_from(u8)]
+/// enum Foo {
+///     // `-1` cannot be represented with `u8`.
+///     A = -1,
+/// }
+/// ```
+///
+/// ## Unsupported Cases
+///
+/// The following examples do not compile:
+///
+/// ```compile_fail
+/// # use kernel::macros::TryFrom;
+/// // Tuple-like enums or struct-like enums are not allowed.
+/// #[derive(TryFrom)]
+/// enum Foo {
+///     A(u8),
+///     B { inner: u8 },
+/// }
+/// ```
+///
+/// ```compile_fail
+/// # use kernel::macros::TryFrom;
+/// // Structs are not allowed.
+/// #[derive(TryFrom)]
+/// struct Foo(u8);
+/// ```
+///
+/// ```compile_fail
+/// # use kernel::macros::TryFrom;
+/// // `repr(C)` enums are not allowed.
+/// #[derive(TryFrom)]
+/// struct Foo(u8)
+/// ```
+#[proc_macro_derive(TryFrom, attributes(try_from))]
+pub fn derive_try_from(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    convert::derive_try_from(input)
+        .unwrap_or_else(syn::Error::into_compile_error)
+        .into()
+}

-- 
2.52.0



^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v5 3/4] rust: macros: add private doctests for `Into` derive macro
  2026-01-29 14:32 [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang via B4 Relay
  2026-01-29 14:32 ` [PATCH v5 1/4] rust: macros: add derive macro for `Into` Jesung Yang via B4 Relay
  2026-01-29 14:32 ` [PATCH v5 2/4] rust: macros: add derive macro for `TryFrom` Jesung Yang via B4 Relay
@ 2026-01-29 14:32 ` Jesung Yang via B4 Relay
  2026-01-29 14:32 ` [PATCH v5 4/4] rust: macros: add private doctests for `TryFrom` " Jesung Yang via B4 Relay
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 16+ messages in thread
From: Jesung Yang via B4 Relay @ 2026-01-29 14:32 UTC (permalink / raw)
  To: Miguel Ojeda, 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

From: Jesung Yang <y.j3ms.n@gmail.com>

Add internal doctests to verify the `Into` derive macro's logic. This
ensures comprehensive testing while keeping the public-facing
documentation compact and readable.

Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
---
 rust/macros/convert.rs | 436 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 436 insertions(+)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index a7a43b1a2caf..41ccbd849348 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -582,3 +582,439 @@ fn is_valid_primitive(ident: &Ident) -> bool {
             | "isize"
     )
 }
+
+mod derive_into_tests {
+    /// ```
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(u8)]
+    /// enum Foo {
+    ///     // Works with const expressions.
+    ///     A = add(0, 0),
+    ///     B = 2_isize.pow(1) - 1,
+    /// }
+    ///
+    /// const fn add(a: isize, b: isize) -> isize {
+    ///     a + b
+    /// }
+    ///
+    /// assert_eq!(0_u8, Foo::A.into());
+    /// assert_eq!(1_u8, Foo::B.into());
+    /// ```
+    mod works_with_const_expr {}
+
+    /// ```
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(bool)]
+    /// enum Foo {
+    ///     A,
+    ///     B,
+    /// }
+    ///
+    /// assert_eq!(false, Foo::A.into());
+    /// assert_eq!(true, Foo::B.into());
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(bool)]
+    /// enum Foo {
+    ///     // `-1` cannot be represented with `bool`.
+    ///     A = -1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(bool)]
+    /// enum Foo {
+    ///     // `2` cannot be represented with `bool`.
+    ///     A = 2,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_bool {}
+
+    /// ```
+    /// use kernel::{
+    ///     macros::Into,
+    ///     num::Bounded, //
+    /// };
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i8, 7>)]
+    /// enum Foo {
+    ///     A = -1 << 6,      // The minimum value of `Bounded<i8, 7>`.
+    ///     B = (1 << 6) - 1, // The maximum value of `Bounded<i8, 7>`.
+    /// }
+    ///
+    /// let foo_a: Bounded<i8, 7> = Foo::A.into();
+    /// let foo_b: Bounded<i8, 7> = Foo::B.into();
+    /// assert_eq!(Bounded::<i8, 7>::new::<{ -1_i8 << 6 }>(), foo_a);
+    /// assert_eq!(Bounded::<i8, 7>::new::<{ (1_i8 << 6) - 1 }>(), foo_b);
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i8, 7>)]
+    /// enum Foo {
+    ///     // `1 << 6` cannot be represented with `Bounded<i8, 7>`.
+    ///     A = 1 << 6,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i8, 7>)]
+    /// enum Foo {
+    ///     // `(-1 << 6) - 1` cannot be represented with `Bounded<i8, 7>`.
+    ///     A = (-1 << 6) - 1,
+    /// }
+    /// ```
+    ///
+    /// ```
+    /// use kernel::{
+    ///     macros::Into,
+    ///     num::Bounded, //
+    /// };
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i8, 1>)]
+    /// enum Foo {
+    ///     A = -1, // The minimum value of `Bounded<i8, 1>`.
+    ///     B,      // The maximum value of `Bounded<i8, 1>`.
+    /// }
+    ///
+    /// let foo_a: Bounded<i8, 1> = Foo::A.into();
+    /// let foo_b: Bounded<i8, 1> = Foo::B.into();
+    /// assert_eq!(Bounded::<i8, 1>::new::<{ -1_i8 }>(), foo_a);
+    /// assert_eq!(Bounded::<i8, 1>::new::<{ 0_i8 } >(), foo_b);
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i8, 1>)]
+    /// enum Foo {
+    ///     // `1` cannot be represented with `Bounded<i8, 1>`.
+    ///     A = 1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i8, 1>)]
+    /// enum Foo {
+    ///     // `-2` cannot be represented with `Bounded<i8, 1>`.
+    ///     A = -2,
+    /// }
+    /// ```
+    ///
+    /// ```
+    /// use kernel::{
+    ///     macros::Into,
+    ///     num::Bounded, //
+    /// };
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i32, 32>)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = i32::MIN as i64,
+    ///     B = i32::MAX as i64,
+    /// }
+    ///
+    /// let foo_a: Bounded<i32, 32> = Foo::A.into();
+    /// let foo_b: Bounded<i32, 32> = Foo::B.into();
+    /// assert_eq!(Bounded::<i32, 32>::new::<{ i32::MIN }>(), foo_a);
+    /// assert_eq!(Bounded::<i32, 32>::new::<{ i32::MAX }>(), foo_b);
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i32, 32>)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     // `1 << 31` cannot be represented with `Bounded<i32, 32>`.
+    ///     A = 1 << 31,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i32, 32>)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     // `(-1 << 31) - 1` cannot be represented with `Bounded<i32, 32>`.
+    ///     A = (-1 << 31) - 1,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_signed_bounded {}
+
+    /// ```
+    /// use kernel::{
+    ///     macros::Into,
+    ///     num::Bounded, //
+    /// };
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<u8, 7>)]
+    /// enum Foo {
+    ///     A,                // The minimum value of `Bounded<u8, 7>`.
+    ///     B = (1 << 7) - 1, // The maximum value of `Bounded<u8, 7>`.
+    /// }
+    ///
+    /// let foo_a: Bounded<u8, 7> = Foo::A.into();
+    /// let foo_b: Bounded<u8, 7> = Foo::B.into();
+    /// assert_eq!(Bounded::<u8, 7>::new::<{ 0 }>(), foo_a);
+    /// assert_eq!(Bounded::<u8, 7>::new::<{ (1_u8 << 7) - 1 }>(), foo_b);
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<u8, 7>)]
+    /// enum Foo {
+    ///     // `1 << 7` cannot be represented with `Bounded<u8, 7>`.
+    ///     A = 1 << 7,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<u8, 7>)]
+    /// enum Foo {
+    ///     // `-1` cannot be represented with `Bounded<u8, 7>`.
+    ///     A = -1,
+    /// }
+    /// ```
+    ///
+    /// ```
+    /// use kernel::{
+    ///     macros::Into,
+    ///     num::Bounded, //
+    /// };
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<u8, 1>)]
+    /// enum Foo {
+    ///     A, // The minimum value of `Bounded<u8, 1>`.
+    ///     B, // The maximum value of `Bounded<u8, 1>`.
+    /// }
+    ///
+    /// let foo_a: Bounded<u8, 1> = Foo::A.into();
+    /// let foo_b: Bounded<u8, 1> = Foo::B.into();
+    /// assert_eq!(Bounded::<u8, 1>::new::<{ 0 }>(), foo_a);
+    /// assert_eq!(Bounded::<u8, 1>::new::<{ 1 }>(), foo_b);
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<u8, 1>)]
+    /// enum Foo {
+    ///     // `2` cannot be represented with `Bounded<u8, 1>`.
+    ///     A = 2,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<u8, 1>)]
+    /// enum Foo {
+    ///     // `-1` cannot be represented with `Bounded<u8, 1>`.
+    ///     A = -1,
+    /// }
+    /// ```
+    ///
+    /// ```
+    /// use kernel::{
+    ///     macros::Into,
+    ///     num::Bounded, //
+    /// };
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<u32, 32>)]
+    /// #[repr(u64)]
+    /// enum Foo {
+    ///     A = u32::MIN as u64,
+    ///     B = u32::MAX as u64,
+    /// }
+    ///
+    /// let foo_a: Bounded<u32, 32> = Foo::A.into();
+    /// let foo_b: Bounded<u32, 32> = Foo::B.into();
+    /// assert_eq!(Bounded::<u32, 32>::new::<{ u32::MIN }>(), foo_a);
+    /// assert_eq!(Bounded::<u32, 32>::new::<{ u32::MAX }>(), foo_b);
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<u32, 32>)]
+    /// #[repr(u64)]
+    /// enum Foo {
+    ///     // `1 << 32` cannot be represented with `Bounded<u32, 32>`.
+    ///     A = 1 << 32,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<u32, 32>)]
+    /// #[repr(u64)]
+    /// enum Foo {
+    ///     // `-1` cannot be represented with `Bounded<u32, 32>`.
+    ///     A = -1,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_unsigned_bounded {}
+
+    /// ```
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(isize)]
+    /// #[repr(isize)]
+    /// enum Foo {
+    ///     A = isize::MIN,
+    ///     B = isize::MAX,
+    /// }
+    ///
+    /// assert_eq!(isize::MIN, Foo::A.into());
+    /// assert_eq!(isize::MAX, Foo::B.into());
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(isize)]
+    /// #[repr(usize)]
+    /// enum Foo {
+    ///     A = (isize::MAX as usize) + 1
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(i32)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = (i32::MIN as i64) - 1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(i32)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = (i32::MAX as i64) + 1,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_signed_int {}
+
+    /// ```
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(usize)]
+    /// #[repr(usize)]
+    /// enum Foo {
+    ///     A = usize::MIN,
+    ///     B = usize::MAX,
+    /// }
+    ///
+    /// assert_eq!(usize::MIN, Foo::A.into());
+    /// assert_eq!(usize::MAX, Foo::B.into());
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(usize)]
+    /// #[repr(isize)]
+    /// enum Foo {
+    ///     A = -1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(u32)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = (u32::MIN as i64) - 1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(u32)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = (u32::MAX as i64) + 1,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_unsigned_int {}
+
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(Bounded<i8, 7>, i8, i16, i32, i64)]
+    /// #[repr(i8)]
+    /// enum Foo {
+    ///     // `i8::MAX` cannot be represented with `Bounded<i8, 7>`.
+    ///     A = i8::MAX,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    ///
+    /// #[derive(Into)]
+    /// #[into(i8, i16, i32, i64, Bounded<i8, 7>)]
+    /// #[repr(i8)]
+    /// enum Foo {
+    ///     // `i8::MAX` cannot be represented with `Bounded<i8, 7>`.
+    ///     A = i8::MAX,
+    /// }
+    /// ```
+    mod any_into_target_overflow_is_rejected {}
+}

-- 
2.52.0



^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v5 4/4] rust: macros: add private doctests for `TryFrom` derive macro
  2026-01-29 14:32 [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang via B4 Relay
                   ` (2 preceding siblings ...)
  2026-01-29 14:32 ` [PATCH v5 3/4] rust: macros: add private doctests for `Into` derive macro Jesung Yang via B4 Relay
@ 2026-01-29 14:32 ` Jesung Yang via B4 Relay
  2026-02-03 12:48 ` [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros shivam kalra
  2026-02-28  5:31 ` Alexandre Courbot
  5 siblings, 0 replies; 16+ messages in thread
From: Jesung Yang via B4 Relay @ 2026-01-29 14:32 UTC (permalink / raw)
  To: Miguel Ojeda, 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

From: Jesung Yang <y.j3ms.n@gmail.com>

Add internal doctests to verify the `TryFrom` derive macro's logic. This
ensures comprehensive testing while keeping the public-facing
documentation compact and readable.

Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
---
 rust/macros/convert.rs | 579 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 579 insertions(+)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index 41ccbd849348..2acaafb58e93 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -1018,3 +1018,582 @@ mod overflow_assert_works_on_unsigned_int {}
     /// ```
     mod any_into_target_overflow_is_rejected {}
 }
+
+mod derive_try_from_tests {
+    /// ```
+    /// use kernel::{
+    ///     macros::{
+    ///         Into,
+    ///         TryFrom, //
+    ///     },
+    ///     num::Bounded,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, Into, PartialEq, TryFrom)]
+    /// #[into(bool, Bounded<i8, 7>, Bounded<u8, 7>, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize)]
+    /// #[try_from(bool, Bounded<i8, 7>, Bounded<u8, 7>, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize)]
+    /// enum Foo {
+    ///     A,
+    ///     B,
+    /// }
+    ///
+    /// assert_eq!(false, Foo::A.into());
+    /// assert_eq!(true, Foo::B.into());
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(false));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(true));
+    ///
+    /// let foo_a: Bounded<i8, 7> = Foo::A.into();
+    /// let foo_b: Bounded<i8, 7> = Foo::B.into();
+    /// assert_eq!(Bounded::<i8, 7>::new::<0>(), foo_a);
+    /// assert_eq!(Bounded::<i8, 7>::new::<1>(), foo_b);
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<i8, 7>::new::<0>()));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<i8, 7>::new::<1>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i8, 7>::new::<-1>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i8, 7>::new::<2>()));
+    ///
+    /// let foo_a: Bounded<u8, 7> = Foo::A.into();
+    /// let foo_b: Bounded<u8, 7> = Foo::B.into();
+    /// assert_eq!(Bounded::<u8, 7>::new::<0>(), foo_a);
+    /// assert_eq!(Bounded::<u8, 7>::new::<1>(), foo_b);
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u8, 7>::new::<0>()));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 7>::new::<1>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u8, 7>::new::<2>()));
+    ///
+    /// macro_rules! gen_signed_tests {
+    ///     ($($type:ty),*) => {
+    ///         $(
+    ///             assert_eq!(0 as $type, Foo::A.into());
+    ///             assert_eq!(1 as $type, Foo::B.into());
+    ///             assert_eq!(Ok(Foo::A), Foo::try_from(0 as $type));
+    ///             assert_eq!(Ok(Foo::B), Foo::try_from(1 as $type));
+    ///             assert_eq!(Err(EINVAL), Foo::try_from((0 as $type) - 1));
+    ///             assert_eq!(Err(EINVAL), Foo::try_from((1 as $type) + 1));
+    ///         )*
+    ///     };
+    /// }
+    /// macro_rules! gen_unsigned_tests {
+    ///     ($($type:ty),*) => {
+    ///         $(
+    ///             assert_eq!(0 as $type, Foo::A.into());
+    ///             assert_eq!(1 as $type, Foo::B.into());
+    ///             assert_eq!(Ok(Foo::A), Foo::try_from(0 as $type));
+    ///             assert_eq!(Ok(Foo::B), Foo::try_from(1 as $type));
+    ///             assert_eq!(Err(EINVAL), Foo::try_from((1 as $type) + 1));
+    ///         )*
+    ///     };
+    /// }
+    /// gen_signed_tests!(i8, i16, i32, i64, i128, isize);
+    /// gen_unsigned_tests!(u8, u16, u32, u64, u128, usize);
+    /// ```
+    mod works_with_derive_into {}
+
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(u8)]
+    /// enum Foo {
+    ///     // Works with const expressions.
+    ///     A = add(0, 0),
+    ///     B = 2_isize.pow(1) - 1,
+    /// }
+    ///
+    /// const fn add(a: isize, b: isize) -> isize {
+    ///     a + b
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(0_u8));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(1_u8));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(2_u8));
+    /// ```
+    mod works_with_const_expr {}
+
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(bool)]
+    /// enum Foo {
+    ///     A,
+    ///     B,
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(false));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(true));
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(bool)]
+    /// enum Bar {
+    ///     A,
+    /// }
+    ///
+    /// assert_eq!(Ok(Bar::A), Bar::try_from(false));
+    /// assert_eq!(Err(EINVAL), Bar::try_from(true));
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(bool)]
+    /// enum Baz {
+    ///     A = 1,
+    /// }
+    ///
+    /// assert_eq!(Err(EINVAL), Baz::try_from(false));
+    /// assert_eq!(Ok(Baz::A), Baz::try_from(true));
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(bool)]
+    /// enum Foo {
+    ///     // `-1` cannot be represented with `bool`.
+    ///     A = -1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(bool)]
+    /// enum Foo {
+    ///     // `2` cannot be represented with `bool`.
+    ///     A = 2,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_bool {}
+
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     num::Bounded,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<i8, 7>)]
+    /// enum Foo {
+    ///     A = -1 << 6,      // The minimum value of `Bounded<i8, 7>`.
+    ///     B = (1 << 6) - 1, // The maximum value of `Bounded<i8, 7>`.
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<i8, 7>::new::<{ -1_i8 << 6 }>()));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<i8, 7>::new::<{ (1_i8 << 6) - 1 }>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i8, 7>::new::<{ (-1_i8 << 6) + 1 }>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i8, 7>::new::<{ (1_i8 << 6) - 2 }>()));
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<i8, 7>)]
+    /// enum Foo {
+    ///     // `1 << 6` cannot be represented with `Bounded<i8, 7>`.
+    ///     A = 1 << 6,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<i8, 7>)]
+    /// enum Foo {
+    ///     // `(-1 << 6) - 1` cannot be represented with `Bounded<i8, 7>`.
+    ///     A = (-1 << 6) - 1,
+    /// }
+    /// ```
+    ///
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     num::Bounded,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<i8, 1>)]
+    /// enum Foo {
+    ///     A = -1, // The minimum value of `Bounded<i8, 1>`.
+    ///     B,      // The maximum value of `Bounded<i8, 1>`.
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<i8, 1>::new::<{ -1_i8 }>()));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<i8, 1>::new::<{ 0_i8 } >()));
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<i8, 1>)]
+    /// enum Bar {
+    ///     A = -1, // The minimum value of `Bounded<i8, 1>`.
+    /// }
+    ///
+    /// assert_eq!(Ok(Bar::A), Bar::try_from(Bounded::<i8, 1>::new::<{ -1_i8 }>()));
+    /// assert_eq!(Err(EINVAL), Bar::try_from(Bounded::<i8, 1>::new::<{ 0_i8 } >()));
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<i8, 1>)]
+    /// enum Baz {
+    ///     A, // The maximum value of `Bounded<i8, 1>`.
+    /// }
+    ///
+    /// assert_eq!(Err(EINVAL), Baz::try_from(Bounded::<i8, 1>::new::<{ -1_i8 }>()));
+    /// assert_eq!(Ok(Baz::A), Baz::try_from(Bounded::<i8, 1>::new::<{ 0_i8 } >()));
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<i8, 1>)]
+    /// enum Foo {
+    ///     // `1` cannot be represented with `Bounded<i8, 1>`.
+    ///     A = 1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<i8, 1>)]
+    /// enum Foo {
+    ///     // `-2` cannot be represented with `Bounded<i8, 1>`.
+    ///     A = -2,
+    /// }
+    /// ```
+    ///
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     num::Bounded,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<i32, 32>)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = i32::MIN as i64,
+    ///     B = i32::MAX as i64,
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<i32, 32>::new::<{ i32::MIN }>()));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<i32, 32>::new::<{ i32::MAX }>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i32, 32>::new::<{ i32::MIN + 1 }>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i32, 32>::new::<{ i32::MAX - 1 }>()));
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<i32, 32>)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     // `1 << 31` cannot be represented with `Bounded<i32, 32>`.
+    ///     A = 1 << 31,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<i32, 32>)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     // `(-1 << 31) - 1` cannot be represented with `Bounded<i32, 32>`.
+    ///     A = (-1 << 31) - 1,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_signed_bounded {}
+
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     num::Bounded,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<u8, 7>)]
+    /// enum Foo {
+    ///     A,                // The minimum value of `Bounded<u8, 7>`.
+    ///     B = (1 << 7) - 1, // The maximum value of `Bounded<u8, 7>`.
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u8, 7>::new::<{ 0 }>()));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 7>::new::<{ (1_u8 << 7) - 1 }>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u8, 7>::new::<{ 1 }>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u8, 7>::new::<{ (1_u8 << 7) - 2 }>()));
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<u8, 7>)]
+    /// enum Foo {
+    ///     // `1 << 7` cannot be represented with `Bounded<u8, 7>`.
+    ///     A = 1 << 7,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<u8, 7>)]
+    /// enum Foo {
+    ///     // `-1` cannot be represented with `Bounded<u8, 7>`.
+    ///     A = -1,
+    /// }
+    /// ```
+    ///
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     num::Bounded,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<u8, 1>)]
+    /// enum Foo {
+    ///     A, // The minimum value of `Bounded<u8, 1>`.
+    ///     B, // The maximum value of `Bounded<u8, 1>`.
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u8, 1>::new::<{ 0 }>()));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 1>::new::<{ 1 }>()));
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<u8, 1>)]
+    /// enum Bar {
+    ///     A, // The minimum value of `Bounded<u8, 1>`.
+    /// }
+    ///
+    /// assert_eq!(Ok(Bar::A), Bar::try_from(Bounded::<u8, 1>::new::<{ 0 }>()));
+    /// assert_eq!(Err(EINVAL), Bar::try_from(Bounded::<u8, 1>::new::<{ 1 }>()));
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<u8, 1>)]
+    /// enum Baz {
+    ///     A = 1, // The maximum value of `Bounded<u8, 1>`.
+    /// }
+    ///
+    /// assert_eq!(Err(EINVAL), Baz::try_from(Bounded::<u8, 1>::new::<{ 0 }>()));
+    /// assert_eq!(Ok(Baz::A), Baz::try_from(Bounded::<u8, 1>::new::<{ 1 }>()));
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<u8, 1>)]
+    /// enum Foo {
+    ///     // `2` cannot be represented with `Bounded<u8, 1>`.
+    ///     A = 2,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<u8, 1>)]
+    /// enum Foo {
+    ///     // `-1` cannot be represented with `Bounded<u8, 1>`.
+    ///     A = -1,
+    /// }
+    /// ```
+    ///
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     num::Bounded,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(Bounded<u32, 32>)]
+    /// #[repr(u64)]
+    /// enum Foo {
+    ///     A = u32::MIN as u64,
+    ///     B = u32::MAX as u64,
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u32, 32>::new::<{ u32::MIN }>()));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u32, 32>::new::<{ u32::MAX }>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u32, 32>::new::<{ u32::MIN + 1 }>()));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u32, 32>::new::<{ u32::MAX - 1 }>()));
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<u32, 32>)]
+    /// #[repr(u64)]
+    /// enum Foo {
+    ///     // `1 << 32` cannot be represented with `Bounded<u32, 32>`.
+    ///     A = 1 << 32,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<u32, 32>)]
+    /// #[repr(u64)]
+    /// enum Foo {
+    ///     // `-1` cannot be represented with `Bounded<u32, 32>`.
+    ///     A = -1,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_unsigned_bounded {}
+
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     num::Bounded,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(isize)]
+    /// #[repr(isize)]
+    /// enum Foo {
+    ///     A = isize::MIN,
+    ///     B = isize::MAX,
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(isize::MIN));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(isize::MAX));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(isize::MIN + 1));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(isize::MAX - 1));
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(isize)]
+    /// #[repr(usize)]
+    /// enum Foo {
+    ///     A = (isize::MAX as usize) + 1
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(i32)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = (i32::MIN as i64) - 1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(i32)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = (i32::MAX as i64) + 1,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_signed_int {}
+
+    /// ```
+    /// use kernel::{
+    ///     macros::TryFrom,
+    ///     num::Bounded,
+    ///     prelude::*, //
+    /// };
+    ///
+    /// #[derive(Debug, PartialEq, TryFrom)]
+    /// #[try_from(usize)]
+    /// #[repr(usize)]
+    /// enum Foo {
+    ///     A = usize::MIN,
+    ///     B = usize::MAX,
+    /// }
+    ///
+    /// assert_eq!(Ok(Foo::A), Foo::try_from(usize::MIN));
+    /// assert_eq!(Ok(Foo::B), Foo::try_from(usize::MAX));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(usize::MIN + 1));
+    /// assert_eq!(Err(EINVAL), Foo::try_from(usize::MAX - 1));
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(usize)]
+    /// #[repr(isize)]
+    /// enum Foo {
+    ///     A = -1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(u32)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = (u32::MIN as i64) - 1,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(u32)]
+    /// #[repr(i64)]
+    /// enum Foo {
+    ///     A = (u32::MAX as i64) + 1,
+    /// }
+    /// ```
+    mod overflow_assert_works_on_unsigned_int {}
+
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(Bounded<i8, 7>, i8, i16, i32, i64)]
+    /// #[repr(i8)]
+    /// enum Foo {
+    ///     // `i8::MAX` cannot be represented with `Bounded<i8, 7>`.
+    ///     A = i8::MAX,
+    /// }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    ///
+    /// #[derive(TryFrom)]
+    /// #[try_from(i8, i16, i32, i64, Bounded<i8, 7>)]
+    /// #[repr(i8)]
+    /// enum Foo {
+    ///     // `i8::MAX` cannot be represented with `Bounded<i8, 7>`.
+    ///     A = i8::MAX,
+    /// }
+    /// ```
+    mod any_try_from_target_overflow_is_rejected {}
+}

-- 
2.52.0



^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 1/4] rust: macros: add derive macro for `Into`
  2026-01-29 14:32 ` [PATCH v5 1/4] rust: macros: add derive macro for `Into` Jesung Yang via B4 Relay
@ 2026-01-29 16:11   ` Charalampos Mitrodimas
  2026-01-30 10:03     ` Jesung Yang
  0 siblings, 1 reply; 16+ messages in thread
From: Charalampos Mitrodimas @ 2026-01-29 16:11 UTC (permalink / raw)
  To: Jesung Yang via B4 Relay
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Alexandre Courbot, y.j3ms.n, linux-kernel,
	rust-for-linux, nouveau

Jesung Yang via B4 Relay <devnull+y.j3ms.n.gmail.com@kernel.org> writes:

> From: Jesung Yang <y.j3ms.n@gmail.com>
>
> 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].
>
> The macro not only supports primitive types such as `bool` or `i8`, but
> also `Bounded`, a wrapper around integer types limiting the number of
> bits usable for value representation. This accommodates the shift toward
> more restrictive register field representations in nova-core where
> values are constrained to specific bit ranges.
>
> 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 | 520 +++++++++++++++++++++++++++++++++++++++++++++++++
>  rust/macros/lib.rs     | 173 +++++++++++++++-
>  2 files changed, 692 insertions(+), 1 deletion(-)
>
> diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
> new file mode 100644
> index 000000000000..096e3c9fdc1b
> --- /dev/null
> +++ b/rust/macros/convert.rs
> @@ -0,0 +1,520 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +use proc_macro2::{
> +    Span,
> +    TokenStream, //
> +};
> +
> +use std::fmt;
> +
> +use syn::{
> +    parse_quote,
> +    parse_str,
> +    punctuated::Punctuated,
> +    spanned::Spanned,
> +    AngleBracketedGenericArguments,
> +    Attribute,
> +    Data,
> +    DeriveInput,
> +    Expr,
> +    ExprLit,
> +    Fields,
> +    GenericArgument,
> +    Ident,
> +    Lit,
> +    LitInt,
> +    PathArguments,
> +    PathSegment,
> +    Token,
> +    Type,
> +    TypePath, //
> +};
> +
> +pub(crate) fn derive_into(input: DeriveInput) -> syn::Result<TokenStream> {
> +    derive(DeriveTarget::Into, input)
> +}
> +
> +fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result<TokenStream> {
> +    let data_enum = match input.data {
> +        Data::Enum(data) => data,
> +        Data::Struct(data) => {
> +            let msg = format!(
> +                "expected `enum`, found `struct`; \
> +                `#[derive({})]` can only be applied to a unit-only enum",
> +                target.get_trait_name(),
> +            );
> +            return Err(syn::Error::new(data.struct_token.span(), msg));
> +        }
> +        Data::Union(data) => {
> +            let msg = format!(
> +                "expected `enum`, found `union`; \
> +                `#[derive({})]` can only be applied to a unit-only enum",
> +                target.get_trait_name(),
> +            );
> +            return Err(syn::Error::new(data.union_token.span(), msg));
> +        }
> +    };
> +
> +    let mut errors: Option<syn::Error> = None;
> +    let mut combine_error = |err| match errors.as_mut() {
> +        Some(errors) => errors.combine(err),
> +        None => errors = Some(err),
> +    };
> +
> +    let (helper_tys, is_repr_c, repr_ty) = parse_attrs(target, &input.attrs)?;
> +
> +    let mut valid_helper_tys = Vec::with_capacity(helper_tys.len());
> +    for ty in helper_tys {
> +        match validate_type(&ty) {
> +            Ok(valid_ty) => valid_helper_tys.push(valid_ty),
> +            Err(err) => combine_error(err),
> +        }
> +    }
> +
> +    let mut is_unit_only = true;
> +    for variant in &data_enum.variants {
> +        match &variant.fields {
> +            Fields::Unit => continue,
> +            Fields::Named(_) => {
> +                let msg = format!(
> +                    "expected unit-like variant, found struct-like variant; \
> +                    `#[derive({})]` can only be applied to a unit-only enum",
> +                    target.get_trait_name(),
> +                );
> +                combine_error(syn::Error::new_spanned(variant, msg));
> +            }
> +            Fields::Unnamed(_) => {
> +                let msg = format!(
> +                    "expected unit-like variant, found tuple-like variant; \
> +                    `#[derive({})]` can only be applied to a unit-only enum",
> +                    target.get_trait_name(),
> +                );
> +                combine_error(syn::Error::new_spanned(variant, msg));
> +            }
> +        }
> +
> +        is_unit_only = false;
> +    }
> +
> +    if is_repr_c && is_unit_only && repr_ty.is_none() {
> +        let msg = "`#[repr(C)]` fieldless enums are not supported";
> +        return Err(syn::Error::new(input.ident.span(), msg));
> +    }
> +
> +    if let Some(errors) = errors {
> +        return Err(errors);
> +    }
> +
> +    let variants: Vec<_> = data_enum
> +        .variants
> +        .into_iter()
> +        .map(|variant| variant.ident)
> +        .collect();
> +
> +    // Extract the representation passed by `#[repr(...)]` if present. If nothing is
> +    // specified, the default is `Rust` representation, which uses `isize` for its
> +    // discriminant type.
> +    // See: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
> +    let repr_ty = repr_ty.unwrap_or_else(|| Ident::new("isize", Span::call_site()));
> +
> +    Ok(derive_for_enum(
> +        target,
> +        &input.ident,
> +        &variants,
> +        repr_ty,
> +        valid_helper_tys,
> +    ))
> +}
> +
> +#[derive(Clone, Copy, Debug)]
> +enum DeriveTarget {
> +    Into,
> +}
> +
> +impl DeriveTarget {
> +    fn get_trait_name(&self) -> &'static str {
> +        match self {
> +            Self::Into => "Into",
> +        }
> +    }
> +
> +    fn get_helper_name(&self) -> &'static str {
> +        match self {
> +            Self::Into => "into",
> +        }
> +    }
> +}
> +
> +fn parse_attrs(
> +    target: DeriveTarget,
> +    attrs: &[Attribute],
> +) -> syn::Result<(Vec<Type>, bool, Option<Ident>)> {
> +    let helper = target.get_helper_name();
> +
> +    let mut is_repr_c = false;
> +    let mut repr_ty = None;
> +    let mut helper_tys = Vec::new();
> +    for attr in attrs {
> +        if attr.path().is_ident("repr") {
> +            attr.parse_nested_meta(|meta| {
> +                let ident = meta.path.get_ident();
> +                if let Some(i) = ident {
> +                    if is_valid_primitive(i) {
> +                        repr_ty = ident.cloned();
> +                    } else if i == "C" {
> +                        is_repr_c = true;
> +                    }
> +                }
> +                // Delegate `repr` attribute validation to rustc.
> +                Ok(())
> +            })?;
> +        } else if attr.path().is_ident(helper) && helper_tys.is_empty() {
> +            let args = attr.parse_args_with(Punctuated::<Type, Token![,]>::parse_terminated)?;
> +            helper_tys.extend(args);
> +        }
> +    }
> +
> +    Ok((helper_tys, is_repr_c, repr_ty))
> +}
> +
> +fn derive_for_enum(
> +    target: DeriveTarget,
> +    enum_ident: &Ident,
> +    variants: &[Ident],
> +    repr_ty: Ident,
> +    helper_tys: Vec<ValidTy>,
> +) -> TokenStream {
> +    let impl_fn = match target {
> +        DeriveTarget::Into => impl_into,
> +    };
> +
> +    let qualified_repr_ty: syn::Path = parse_quote! { ::core::primitive::#repr_ty };
> +
> +    return if helper_tys.is_empty() {
> +        let ty = ValidTy::Primitive(repr_ty);
> +        let implementation = impl_fn(enum_ident, variants, &qualified_repr_ty, &ty);
> +        ::quote::quote! { #implementation }
> +    } else {
> +        let impls = helper_tys
> +            .into_iter()
> +            .map(|ty| impl_fn(enum_ident, variants, &qualified_repr_ty, &ty));
> +        ::quote::quote! { #(#impls)* }
> +    };
> +
> +    fn impl_into(
> +        enum_ident: &Ident,
> +        variants: &[Ident],
> +        repr_ty: &syn::Path,
> +        input_ty: &ValidTy,
> +    ) -> TokenStream {
> +        let param = Ident::new("value", Span::call_site());
> +
> +        let overflow_assertion = emit_overflow_assert(enum_ident, variants, repr_ty, input_ty);
> +        let cast = match input_ty {
> +            ValidTy::Bounded(inner) => {
> +                let base_ty = inner.emit_qualified_base_ty();
> +                let expr = parse_quote! { #param as #base_ty };
> +                // Since the discriminant of `#param`, an enum variant, is determined
> +                // at compile-time, we can rely on `Bounded::from_expr()`. It requires
> +                // the provided expression to be verifiable at compile-time to avoid
> +                // triggering a build error.
> +                inner.emit_from_expr(&expr)
> +            }
> +            ValidTy::Primitive(ident) if ident == "bool" => {
> +                ::quote::quote! { (#param as #repr_ty) == 1 }
> +            }
> +            qualified @ ValidTy::Primitive(_) => ::quote::quote! { #param as #qualified },
> +        };
> +
> +        ::quote::quote! {
> +            #[automatically_derived]
> +            impl ::core::convert::From<#enum_ident> for #input_ty {
> +                fn from(#param: #enum_ident) -> #input_ty {
> +                    #overflow_assertion
> +
> +                    #cast
> +                }
> +            }
> +        }
> +    }
> +
> +    fn emit_overflow_assert(
> +        enum_ident: &Ident,
> +        variants: &[Ident],
> +        repr_ty: &syn::Path,
> +        input_ty: &ValidTy,
> +    ) -> TokenStream {
> +        let qualified_i128: syn::Path = parse_quote! { ::core::primitive::i128 };
> +        let qualified_u128: syn::Path = parse_quote! { ::core::primitive::u128 };
> +
> +        let input_min = input_ty.emit_min();
> +        let input_max = input_ty.emit_max();
> +
> +        let variant_fits = variants.iter().map(|variant| {
> +            let msg = format!(
> +                "enum discriminant overflow: \
> +                `{enum_ident}::{variant}` does not fit in `{input_ty}`",
> +            );
> +            ::quote::quote! {
> +                ::core::assert!(fits(#enum_ident::#variant as #repr_ty), #msg);
> +            }
> +        });
> +
> +        ::quote::quote! {
> +            const _: () = {
> +                const fn fits(d: #repr_ty) -> ::core::primitive::bool {
> +                    // For every integer type, its minimum value always fits in `i128`.
> +                    let dst_min = #input_min;
> +                    // For every integer type, its maximum value always fits in `u128`.
> +                    let dst_max = #input_max;
> +
> +                    #[allow(unused_comparisons)]
> +                    let is_src_signed = #repr_ty::MIN < 0;
> +                    #[allow(unused_comparisons)]
> +                    let is_dst_signed = dst_min < 0;
> +
> +                    if is_src_signed && is_dst_signed {
> +                        // Casting from a signed value to `i128` does not overflow since
> +                        // `i128` is the largest signed primitive integer type.
> +                        (d as #qualified_i128) >= (dst_min as #qualified_i128)
> +                            && (d as #qualified_i128) <= (dst_max as #qualified_i128)
> +                    } else if is_src_signed && !is_dst_signed {
> +                        // Casting from a signed value greater than 0 to `u128` does not
> +                        // overflow since `u128::MAX` is greater than `i128::MAX`.
> +                        d >= 0 && (d as #qualified_u128) <= (dst_max as #qualified_u128)
> +                    } else {
> +                        // Casting from an unsigned value to `u128` does not overflow since
> +                        // `u128` is the largest unsigned primitive integer type.
> +                        (d as #qualified_u128) <= (dst_max as #qualified_u128)
> +                    }
> +                }
> +
> +                #(#variant_fits)*
> +            };
> +        }
> +    }
> +}
> +
> +enum ValidTy {
> +    Bounded(Bounded),
> +    Primitive(Ident),
> +}
> +
> +impl ValidTy {
> +    fn emit_min(&self) -> TokenStream {
> +        match self {
> +            Self::Bounded(inner) => inner.emit_min(),
> +            Self::Primitive(ident) if ident == "bool" => {
> +                ::quote::quote! { 0 }
> +            }
> +            qualified @ Self::Primitive(_) => ::quote::quote! { #qualified::MIN },
> +        }
> +    }
> +
> +    fn emit_max(&self) -> TokenStream {
> +        match self {
> +            Self::Bounded(inner) => inner.emit_max(),
> +            Self::Primitive(ident) if ident == "bool" => {
> +                ::quote::quote! { 1 }
> +            }
> +            qualified @ Self::Primitive(_) => ::quote::quote! { #qualified::MAX },
> +        }
> +    }
> +}
> +
> +impl ::quote::ToTokens for ValidTy {
> +    fn to_tokens(&self, tokens: &mut TokenStream) {
> +        match self {
> +            Self::Bounded(inner) => inner.to_tokens(tokens),
> +            Self::Primitive(ident) => {
> +                let qualified_name: syn::Path = parse_quote! { ::core::primitive::#ident };
> +                qualified_name.to_tokens(tokens)
> +            }
> +        }
> +    }
> +}
> +
> +impl fmt::Display for ValidTy {
> +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
> +        match self {
> +            Self::Bounded(inner) => inner.fmt(f),
> +            Self::Primitive(ident) => ident.fmt(f),
> +        }
> +    }
> +}
> +
> +struct Bounded {
> +    base_ty: Ident,
> +    bits: LitInt,
> +}
> +
> +impl Bounded {
> +    const NAME: &'static str = "Bounded";
> +    const QUALIFIED_NAME: &'static str = "::kernel::num::Bounded";
> +
> +    fn emit_from_expr(&self, expr: &Expr) -> TokenStream {
> +        let Self { base_ty, bits, .. } = self;
> +        let qualified_name: syn::Path = parse_str(Self::QUALIFIED_NAME).expect("valid path");
> +        ::quote::quote! {
> +            #qualified_name::<#base_ty, #bits>::from_expr(#expr)
> +        }
> +    }
> +
> +    fn emit_qualified_base_ty(&self) -> TokenStream {
> +        let base_ty = &self.base_ty;
> +        ::quote::quote! { ::core::primitive::#base_ty }
> +    }
> +
> +    fn emit_min(&self) -> TokenStream {
> +        let bits = &self.bits;
> +        let base_ty = self.emit_qualified_base_ty();
> +        ::quote::quote! { #base_ty::MIN >> (#base_ty::BITS - #bits) }
> +    }
> +
> +    fn emit_max(&self) -> TokenStream {
> +        let bits = &self.bits;
> +        let base_ty = self.emit_qualified_base_ty();
> +        ::quote::quote! { #base_ty::MAX >> (#base_ty::BITS - #bits) }
> +    }
> +}
> +
> +impl ::quote::ToTokens for Bounded {
> +    fn to_tokens(&self, tokens: &mut TokenStream) {
> +        let bits = &self.bits;
> +        let base_ty = self.emit_qualified_base_ty();
> +        let qualified_name: syn::Path = parse_str(Self::QUALIFIED_NAME).expect("valid path");
> +
> +        tokens.extend(::quote::quote! {
> +            #qualified_name<#base_ty, #bits>
> +        });
> +    }
> +}
> +
> +impl fmt::Display for Bounded {
> +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
> +        write!(f, "{}<{}, {}>", Self::NAME, self.base_ty, self.bits)
> +    }
> +}
> +
> +fn validate_type(ty: &Type) -> syn::Result<ValidTy> {
> +    let Type::Path(type_path) = ty else {
> +        return Err(make_err(ty));
> +    };
> +
> +    let TypePath { qself, path } = type_path;
> +    if qself.is_some() {
> +        return Err(make_err(ty));
> +    }
> +
> +    let syn::Path {
> +        leading_colon,
> +        segments,
> +    } = path;
> +    if leading_colon.is_some() || segments.len() != 1 {
> +        return Err(make_err(ty));
> +    }
> +
> +    let segment = &path.segments[0];
> +    if segment.ident == Bounded::NAME {
> +        return validate_bounded(segment);
> +    } else {
> +        return validate_primitive(&segment.ident);
> +    }
> +
> +    fn make_err(ty: &Type) -> syn::Error {
> +        let msg = format!(
> +            "expected unqualified form of `bool`, primitive integer type, or `{}<T, N>`",
> +            Bounded::NAME,
> +        );
> +        syn::Error::new_spanned(ty, msg)
> +    }
> +}
> +
> +fn validate_bounded(path_segment: &PathSegment) -> syn::Result<ValidTy> {
> +    let PathSegment { ident, arguments } = path_segment;
> +    return match arguments {
> +        PathArguments::AngleBracketed(inner) if ident == Bounded::NAME => {
> +            let AngleBracketedGenericArguments {
> +                colon2_token, args, ..
> +            } = inner;
> +
> +            if colon2_token.is_some() {
> +                return Err(make_outer_err(path_segment));
> +            }
> +
> +            if args.len() != 2 {
> +                return Err(make_outer_err(path_segment));
> +            }
> +
> +            let (base_ty, bits) = (&args[0], &args[1]);
> +            let GenericArgument::Type(Type::Path(base_ty_lowered)) = base_ty else {
> +                return Err(make_base_ty_err(base_ty));
> +            };
> +
> +            if base_ty_lowered.qself.is_some() {
> +                return Err(make_base_ty_err(base_ty));
> +            }
> +
> +            let Some(base_ty_ident) = base_ty_lowered.path.get_ident() else {
> +                return Err(make_base_ty_err(base_ty));
> +            };
> +
> +            if !is_valid_primitive(base_ty_ident) {
> +                return Err(make_base_ty_err(base_ty));
> +            }
> +
> +            let GenericArgument::Const(Expr::Lit(ExprLit {
> +                lit: Lit::Int(bits),
> +                ..
> +            })) = bits
> +            else {
> +                return Err(syn::Error::new_spanned(bits, "expected integer literal"));
> +            };
> +
> +            let bounded = Bounded {
> +                base_ty: base_ty_ident.clone(),
> +                bits: bits.clone(),
> +            };
> +            Ok(ValidTy::Bounded(bounded))
> +        }
> +        _ => Err(make_outer_err(path_segment)),
> +    };
> +
> +    fn make_outer_err(path_segment: &PathSegment) -> syn::Error {
> +        let msg = format!("expected `{0}<T, N>` (e.g., {0}<u8, 4>)", Bounded::NAME);
> +        syn::Error::new_spanned(path_segment, msg)
> +    }
> +
> +    fn make_base_ty_err(base_ty: &GenericArgument) -> syn::Error {
> +        let msg = "expected unqualified form of primitive integer type";
> +        syn::Error::new_spanned(base_ty, msg)
> +    }
> +}
> +
> +fn validate_primitive(ident: &Ident) -> syn::Result<ValidTy> {
> +    if is_valid_primitive(ident) {
> +        return Ok(ValidTy::Primitive(ident.clone()));
> +    }
> +    let msg =
> +        format!("expected `bool` or primitive integer type (e.g., `u8`, `i8`), found {ident}");
> +    Err(syn::Error::new(ident.span(), msg))
> +}
> +
> +fn is_valid_primitive(ident: &Ident) -> bool {
> +    matches!(
> +        ident.to_string().as_str(),
> +        "bool"
> +            | "u8"
> +            | "u16"
> +            | "u32"
> +            | "u64"
> +            | "u128"
> +            | "usize"
> +            | "i8"
> +            | "i16"
> +            | "i32"
> +            | "i64"
> +            | "i128"
> +            | "isize"
> +    )
> +}
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index 85b7938c08e5..8842067d1017 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -12,6 +12,7 @@
>  #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
>  
>  mod concat_idents;
> +mod convert;
>  mod export;
>  mod fmt;
>  mod helpers;
> @@ -22,7 +23,10 @@
>  
>  use proc_macro::TokenStream;
>  
> -use syn::parse_macro_input;
> +use syn::{
> +    parse_macro_input,
> +    DeriveInput, //
> +};
>  
>  /// Declares a kernel module.
>  ///
> @@ -486,3 +490,170 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
>          .unwrap_or_else(|e| e.into_compile_error())
>          .into()
>  }
> +
> +/// A derive macro for providing an implementation of the [`Into`] trait.
> +///
> +/// This macro automatically derives the [`Into`] trait for a given enum by generating
> +/// the relevant [`From`] implementation. Currently, it only supports [unit-only enum]s.
> +///
> +/// [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`.
> +///
> +/// - Currently, the macro does not support `repr(C)` fieldless enums since the actual
> +///   representation of discriminants is defined by rustc internally, and documentation
> +///   around it is not yet settled. See [Rust issue #124403] and [Rust PR #147017]
> +///   for more information.
> +///
> +/// - The macro generates a compile-time assertion for every variant to ensure its
> +///   discriminant value fits within the type being converted into.
> +///
> +/// [Rust issue #124403]: https://github.com/rust-lang/rust/issues/124403
> +/// [Rust PR #147017]: https://github.com/rust-lang/rust/pull/147017
> +///
> +/// # Supported types in `#[into(...)]`
> +///
> +/// - [`bool`]
> +/// - Primitive integer types (e.g., [`i8`], [`u8`])
> +/// - [`Bounded`]
> +///
> +/// [`Bounded`]: ../kernel/num/bounded/struct.Bounded.html
> +///
> +/// # Examples
> +///
> +/// ## Without Attributes
> +///
> +/// Since [the default `Rust` representation uses `isize` for the discriminant type][repr-rust],
> +/// the macro implements `From<Foo>` for `isize`:
> +///
> +/// [repr-rust]: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
> +///
> +/// ```
> +/// use kernel::macros::Into;
> +///
> +/// #[derive(Debug, Default, Into)]
> +/// enum Foo {
> +///     #[default]
> +///     A,
> +///     B = 0x7,
> +/// }
> +///
> +/// assert_eq!(0_isize, Foo::A.into());
> +/// assert_eq!(0x7_isize, Foo::B.into());
> +/// ```
> +///
> +/// ## With `#[repr(T)]`
> +///
> +/// The macro implements `From<Foo>` for `T`:
> +///
> +/// ```
> +/// use kernel::macros::Into;
> +///
> +/// #[derive(Debug, Default, Into)]
> +/// #[repr(u8)]
> +/// enum Foo {
> +///     #[default]
> +///     A,
> +///     B = 0x7,
> +/// }
> +///
> +/// assert_eq!(0_u8, Foo::A.into());
> +/// assert_eq!(0x7_u8, Foo::B.into());
> +/// ```
> +///
> +/// ## With `#[into(...)]`
> +///
> +/// The macro implements `From<Foo>` for each `T` specified in `#[into(...)]`,
> +/// which always overrides `#[repr(...)]`:
> +///
> +/// ```
> +/// use kernel::{
> +///     macros::Into,
> +///     num::Bounded, //
> +/// };
> +///
> +/// #[derive(Debug, Default, Into)]
> +/// #[into(bool, i16, Bounded<u8, 4>)]
> +/// #[repr(u8)]
> +/// enum Foo {
> +///     #[default]
> +///     A,
> +///     B,
> +/// }
> +///
> +/// assert_eq!(false, Foo::A.into());
> +/// assert_eq!(true, Foo::B.into());
> +///
> +/// assert_eq!(0_i16, Foo::A.into());
> +/// assert_eq!(1_i16, Foo::B.into());
> +///
> +/// let foo_a: Bounded<u8, 4> = Foo::A.into();
> +/// let foo_b: Bounded<u8, 4> = Foo::B.into();
> +/// assert_eq!(Bounded::<u8, 4>::new::<0>(), foo_a);
> +/// assert_eq!(Bounded::<u8, 4>::new::<1>(), foo_b);
> +/// ```
> +///
> +/// ## Compile-time Overflow Assertion
> +///
> +/// The following examples do not compile:
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::Into;
> +/// #[derive(Into)]
> +/// #[into(u8)]
> +/// enum Foo {
> +///     // `256` is larger than `u8::MAX`.
> +///     A = 256,
> +/// }
> +/// ```
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::Into;
> +/// #[derive(Into)]
> +/// #[into(u8)]
> +/// enum Foo {
> +///     // `-1` cannot be represented with `u8`.
> +///     A = -1,
> +/// }
> +/// ```
> +///
> +/// ## Unsupported Cases
> +///
> +/// The following examples do not compile:
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::Into;
> +/// // Tuple-like enums or struct-like enums are not allowed.
> +/// #[derive(Into)]
> +/// enum Foo {
> +///     A(u8),
> +///     B { inner: u8 },
> +/// }
> +/// ```
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::Into;
> +/// // Structs are not allowed.
> +/// #[derive(Into)]
> +/// struct Foo(u8);
> +/// ```
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::Into;
> +/// // `repr(C)` enums are not allowed.
> +/// #[derive(Into)]
> +/// struct Foo(u8);

Should this be something like this? Also on the TryFrom (patch 2/4).

    /// // `repr(C)` enums are not allowed.
    /// #[derive(Into)]
    /// #[repr(C)]
    /// enum Foo {
    ///     A,
    ///     B,
    /// }

Cheers,
Charalampos Mitrodimas

> +/// ```
> +#[proc_macro_derive(Into, attributes(into))]
> +pub fn derive_into(input: TokenStream) -> TokenStream {
> +    let input = parse_macro_input!(input as DeriveInput);
> +    convert::derive_into(input)
> +        .unwrap_or_else(syn::Error::into_compile_error)
> +        .into()
> +}

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 1/4] rust: macros: add derive macro for `Into`
  2026-01-29 16:11   ` Charalampos Mitrodimas
@ 2026-01-30 10:03     ` Jesung Yang
  0 siblings, 0 replies; 16+ messages in thread
From: Jesung Yang @ 2026-01-30 10:03 UTC (permalink / raw)
  To: Charalampos Mitrodimas, Jesung Yang via B4 Relay
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Alexandre Courbot, y.j3ms.n, linux-kernel,
	rust-for-linux, nouveau

On Fri Jan 30, 2026 at 1:11 AM KST, Charalampos Mitrodimas wrote:
> Jesung Yang via B4 Relay <devnull+y.j3ms.n.gmail.com@kernel.org> writes:
>> +/// ```compile_fail
>> +/// # use kernel::macros::Into;
>> +/// // `repr(C)` enums are not allowed.
>> +/// #[derive(Into)]
>> +/// struct Foo(u8);
>
> Should this be something like this? Also on the TryFrom (patch 2/4).
>
>     /// // `repr(C)` enums are not allowed.
>     /// #[derive(Into)]
>     /// #[repr(C)]
>     /// enum Foo {
>     ///     A,
>     ///     B,
>     /// }

Nice catch, I'll buffer the fix until I get some more feedback.

Thanks for taking a look!

Best regards,
Jesung

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros
  2026-01-29 14:32 [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang via B4 Relay
                   ` (3 preceding siblings ...)
  2026-01-29 14:32 ` [PATCH v5 4/4] rust: macros: add private doctests for `TryFrom` " Jesung Yang via B4 Relay
@ 2026-02-03 12:48 ` shivam kalra
  2026-02-28  5:31 ` Alexandre Courbot
  5 siblings, 0 replies; 16+ messages in thread
From: shivam kalra @ 2026-02-03 12:48 UTC (permalink / raw)
  To: Jesung Yang, Miguel Ojeda, 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

On 29/01/26 20:02, Jesung Yang wrote:
> This patch series introduces derive macros for the `TryFrom` and `Into`
> traits.
> 
> I dropped support for `#[repr(C)]` fieldless enums (i.e., disallow
> deriving `TryFrom` and `Into` on those enums), since the actual
> representation of discriminants is defined by rustc internally, and
> documentation around it is not yet settled [1][2].
> 
> I would like to highlight some feedback from the previous series where I
> have decided to retain the original behavior in this series. In part, I
> feel more community feedback is needed to reach the right design; I've
> also elaborated on my personal reasoning for each point below:
> 
> 1) Require explicit `#[repr(..)]` for enums using the macros.
> 
> Given the drop of `#[repr(C)]` support, I believe defaulting to `isize`
> when no `#[repr(..)]` is specified is safe, as it follows the
> compiler's (documented) default behavior [3]. By "safe", I mean I see no
> potential regressions or hidden issues; we even have a compile-time
> overflow assertion which ensure all enum discriminants fit within the
> types involved in the conversion.
> 
> 2) Generate trait implementation for a type in `#[repr(..)]` even when
>    the helper attribute is present. For example:
> 
>      #[derive(TryFrom)]
>      #[try_from(bool)]
>      #[repr(u8)]
>      enum E {
>          A = 0,
>          B = 1,
>      }
> 
>    make the above snippet generate both `TryFrom<u8>` and
>    `TryFrom<bool>` implementations.
> 
> However, sometimes users might want to prevent generating trait
> implementations for the type in `#[repr(..)]`, especially when the enum
> encodes types like `bool` or `Bounded<u8, 4>` that cannot be used in
> `#[repr(..)]`, like the above snippet. If we were to follow the feedback
> directly, users would end up with an unnecessary `TryFrom<u8>`
> implementation. Therefore, I think it is reasonable to treat the helper
> attribute as an override that ignores the type in `#[repr(..)]` when
> present.
> 
> One last point: the current `Into` implementation relies on
> `Bounded::from_expr()`, which utilizes `build_assert!()`. So the
> expanded result looks roughly like this:
> 
>   impl From<Enum> for Bounded<u8, 4> {
>       fn from(value: Enum) -> Bounded<u8, 4> {
>           // Compile-time assertions to guarantee `value` fits within
>           // `u8` (Omitted)
> 
>           Bounded::<u8, 4>::from_expr(value as u8)
>       }
>   }
> 
> After some experimentation, it appears that the compiler correctly
> optimizes out the assertion if (and only if) the `Bounded` type covers
> all enum discriminants, though I'm not 100% certain of this behavior in
> all cases. Can we approach this better?
> 
> Appreciate any feedback or suggestions.
> 
> [1] https://github.com/rust-lang/rust/issues/124403
> [2] https://github.com/rust-lang/rust/pull/147017
> [3] https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
> 
> Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
> ---
> Changes in v5:
> - Drop support for `#[repr(C)]` enums. (Benno Lossin)
> - Allow `#[repr(C, {primitive_int_type})]` for future-proofing.
>   (Benno Lossin)
> - Parse types in helper attributes into `syn::Type` prior to validation.
>   (Benno Lossin)
> - Replace doctests for `TryFrom` with correct examples. (Benno Lossin)
> - Reorganize error reporting structure.
> - Add documentation about `#[repr(C)]` fieldless enums.
> - Rebase on commit a7c013f77953 ("Merge patch series "refactor Rust proc
>   macros with `syn`"")
> - Link to v4: https://lore.kernel.org/r/20251225-try-from-into-macro-v4-0-4a563d597836@gmail.com
> 
> Changes in v4:
> - Fix typos.
> - Link to (broken) v3: https://lore.kernel.org/rust-for-linux/cover.1766544407.git.y.j3ms.n@gmail.com/
> 
> Changes in v3:
> - Use the vendored `syn` and `quote` crates.
> - Support `kernel::num::Bounded`.
> - Add compile-time overflow assertion.
> - Add a comment about `#[repr(C)]` enums.
> - Drop Tested-by and Reviewed-by tags, as the code structure has
>   changed substantially. (Thanks for the previous reviews and testing!)
> - Link to v2: https://lore.kernel.org/rust-for-linux/cover.1755235180.git.y.j3ms.n@gmail.com/
> 
> 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.
> - Link to v1: https://lore.kernel.org/rust-for-linux/cover.1754228164.git.y.j3ms.n@gmail.com/
> 
> ---
> Jesung Yang (4):
>       rust: macros: add derive macro for `Into`
>       rust: macros: add derive macro for `TryFrom`
>       rust: macros: add private doctests for `Into` derive macro
>       rust: macros: add private doctests for `TryFrom` derive macro
> 
>  rust/macros/convert.rs | 1599 ++++++++++++++++++++++++++++++++++++++++++++++++
>  rust/macros/lib.rs     |  349 ++++++++++-
>  2 files changed, 1947 insertions(+), 1 deletion(-)
> ---
> base-commit: a7c013f779530190d0c1e1aa5e7c8a61f0bd479e
> change-id: 20251225-try-from-into-macro-1665d0afcfc8
> 
> Best regards,
Tested-by: Shivam Kalra <shivamklr@cock.li>
Tested on rust-next with:
- make LLVM=1 rusttest (all doctests pass)
- make LLVM=1 CLIPPY=1 rust/ (compiles without warnings)
- make LLVM=1 -j$(nproc) (full kernel build succeeds)

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 2/4] rust: macros: add derive macro for `TryFrom`
  2026-01-29 14:32 ` [PATCH v5 2/4] rust: macros: add derive macro for `TryFrom` Jesung Yang via B4 Relay
@ 2026-02-04  1:39   ` Charalampos Mitrodimas
  2026-02-05 21:06     ` Jesung Yang
  0 siblings, 1 reply; 16+ messages in thread
From: Charalampos Mitrodimas @ 2026-02-04  1:39 UTC (permalink / raw)
  To: y.j3ms.n
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Alexandre Courbot, linux-kernel, rust-for-linux,
	nouveau

Jesung Yang via B4 Relay <devnull+y.j3ms.n.gmail.com@kernel.org> writes:

> From: Jesung Yang <y.j3ms.n@gmail.com>
>
> 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 arises 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.
>
> The macro not only supports primitive types such as `bool` or `i8`, but
> also `Bounded`, a wrapper around integer types limiting the number of
> bits usable for value representation. This accommodates the shift toward
> more restrictive register field representations in nova-core where
> values are constrained to specific bit ranges.
>
> Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
> ---
>  rust/macros/convert.rs |  64 ++++++++++++++++++
>  rust/macros/lib.rs     | 176 +++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 240 insertions(+)
>
> diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
> index 096e3c9fdc1b..a7a43b1a2caf 100644
> --- a/rust/macros/convert.rs
> +++ b/rust/macros/convert.rs
> @@ -34,6 +34,10 @@ pub(crate) fn derive_into(input: DeriveInput) -> syn::Result<TokenStream> {
>      derive(DeriveTarget::Into, input)
>  }
>  
> +pub(crate) fn derive_try_from(input: DeriveInput) -> syn::Result<TokenStream> {
> +    derive(DeriveTarget::TryFrom, input)
> +}
> +
>  fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result<TokenStream> {
>      let data_enum = match input.data {
>          Data::Enum(data) => data,
> @@ -129,18 +133,21 @@ fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result<TokenStream>
>  #[derive(Clone, Copy, Debug)]
>  enum DeriveTarget {
>      Into,
> +    TryFrom,
>  }
>  
>  impl DeriveTarget {
>      fn get_trait_name(&self) -> &'static str {
>          match self {
>              Self::Into => "Into",
> +            Self::TryFrom => "TryFrom",
>          }
>      }
>  
>      fn get_helper_name(&self) -> &'static str {
>          match self {
>              Self::Into => "into",
> +            Self::TryFrom => "try_from",
>          }
>      }
>  }
> @@ -186,6 +193,7 @@ fn derive_for_enum(
>  ) -> TokenStream {
>      let impl_fn = match target {
>          DeriveTarget::Into => impl_into,
> +        DeriveTarget::TryFrom => impl_try_from,
>      };
>  
>      let qualified_repr_ty: syn::Path = parse_quote! { ::core::primitive::#repr_ty };
> @@ -238,6 +246,54 @@ fn from(#param: #enum_ident) -> #input_ty {
>          }
>      }
>  
> +    fn impl_try_from(
> +        enum_ident: &Ident,
> +        variants: &[Ident],
> +        repr_ty: &syn::Path,
> +        input_ty: &ValidTy,
> +    ) -> TokenStream {
> +        let param = Ident::new("value", Span::call_site());
> +
> +        let overflow_assertion = emit_overflow_assert(enum_ident, variants, repr_ty, input_ty);
> +        let emit_cast = |variant| {
> +            let variant = ::quote::quote! { #enum_ident::#variant };
> +            match input_ty {
> +                ValidTy::Bounded(inner) => {
> +                    let base_ty = inner.emit_qualified_base_ty();
> +                    let expr = parse_quote! { #variant as #base_ty };
> +                    inner.emit_new(&expr)
> +                }
> +                ValidTy::Primitive(ident) if ident == "bool" => {
> +                    ::quote::quote! { ((#variant as #repr_ty) == 1) }
> +                }
> +                qualified @ ValidTy::Primitive(_) => ::quote::quote! { #variant as #qualified },
> +            }
> +        };
> +
> +        let clauses = variants.iter().map(|variant| {
> +            let cast = emit_cast(variant);
> +            ::quote::quote! {
> +                if #param == #cast {
> +                    ::core::result::Result::Ok(#enum_ident::#variant)
> +                } else
> +            }
> +        });
> +
> +        ::quote::quote! {
> +            #[automatically_derived]
> +            impl ::core::convert::TryFrom<#input_ty> for #enum_ident {
> +                type Error = ::kernel::prelude::Error;
> +                fn try_from(#param: #input_ty) -> Result<#enum_ident, Self::Error> {
> +                    #overflow_assertion
> +
> +                    #(#clauses)* {
> +                        ::core::result::Result::Err(::kernel::prelude::EINVAL)

What happens if we need a different error type here? For example, a
quick look around in nova-core's "Chipset" enum, an unrecognized chipset
ID warrants ENODEV rather than EINVAL, since it's about device
identification.

Not sure if it fits the design, just wondering if this flexibility would
be useful, but would something like an optional

    error = <ERROR>

in the

   #[try_from(...)]

attribute make sense? e.g.

   #[try_from(u32, error = ENODEV)]

defaulting ofcourse to EINVAL if unspecified.

--
C. Mitrodimas

> +                    }
> +                }
> +            }
> +        }
> +    }
> +
>      fn emit_overflow_assert(
>          enum_ident: &Ident,
>          variants: &[Ident],
> @@ -360,6 +416,14 @@ fn emit_from_expr(&self, expr: &Expr) -> TokenStream {
>          }
>      }
>  
> +    fn emit_new(&self, expr: &Expr) -> TokenStream {
> +        let Self { base_ty, bits, .. } = self;
> +        let qualified_name: syn::Path = parse_str(Self::QUALIFIED_NAME).expect("valid path");
> +        ::quote::quote! {
> +            #qualified_name::<#base_ty, #bits>::new::<{ #expr }>()
> +        }
> +    }
> +
>      fn emit_qualified_base_ty(&self) -> TokenStream {
>          let base_ty = &self.base_ty;
>          ::quote::quote! { ::core::primitive::#base_ty }
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index 8842067d1017..893adecb9080 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -657,3 +657,179 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
>          .unwrap_or_else(syn::Error::into_compile_error)
>          .into()
>  }
> +
> +/// A derive macro for generating an implementation of the [`TryFrom`] trait.
> +///
> +/// This macro automatically derives the [`TryFrom`] trait for a given enum. Currently,
> +/// it only supports [unit-only enum]s.
> +///
> +/// [unit-only enum]: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.unit-only
> +///
> +/// # Notes
> +///
> +/// - The macro generates [`TryFrom`] implementations that:
> +///   - Return `Ok(VARIANT)` when the input corresponds to a variant.
> +///   - Return `Err(EINVAL)` when the input does not correspond to any variant.
> +///     (where `EINVAL` is from [`kernel::error::code`]).
> +///
> +/// - The macro uses the `try_from` custom attribute or `repr` attribute to generate
> +///   [`TryFrom`] implementations. `try_from` always takes precedence over `repr`.
> +///
> +/// - Currently, the macro does not support `repr(C)` fieldless enums since the actual
> +///   representation of discriminants is defined by rustc internally, and documentation
> +///   around it is not yet settled. See [Rust issue #124403] and [Rust PR #147017]
> +///   for more information.
> +///
> +/// - The macro generates a compile-time assertion for every variant to ensure its
> +///   discriminant value fits within the type being converted from.
> +///
> +/// [`kernel::error::code`]: ../kernel/error/code/index.html
> +/// [Rust issue #124403]: https://github.com/rust-lang/rust/issues/124403
> +/// [Rust PR #147017]: https://github.com/rust-lang/rust/pull/147017
> +///
> +/// # Supported types in `#[try_from(...)]`
> +///
> +/// - [`bool`]
> +/// - Primitive integer types (e.g., [`i8`], [`u8`])
> +/// - [`Bounded`]
> +///
> +/// [`Bounded`]: ../kernel/num/bounded/struct.Bounded.html
> +///
> +/// # Examples
> +///
> +/// ## Without Attributes
> +///
> +/// Since [the default `Rust` representation uses `isize` for the discriminant type][repr-rust],
> +/// the macro implements `TryFrom<isize>`:
> +///
> +/// [repr-rust]: https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.repr-rust
> +///
> +/// ```rust
> +/// # use kernel::prelude::*;
> +/// use kernel::macros::TryFrom;
> +///
> +/// #[derive(Debug, Default, PartialEq, TryFrom)]
> +/// enum Foo {
> +///     #[default]
> +///     A,
> +///     B = 0x7,
> +/// }
> +///
> +/// assert_eq!(Err(EINVAL), Foo::try_from(-1_isize));
> +/// assert_eq!(Ok(Foo::A), Foo::try_from(0_isize));
> +/// assert_eq!(Ok(Foo::B), Foo::try_from(0x7_isize));
> +/// assert_eq!(Err(EINVAL), Foo::try_from(0x8_isize));
> +/// ```
> +///
> +/// ## With `#[repr(T)]`
> +///
> +/// The macro implements `TryFrom<T>`:
> +///
> +/// ```rust
> +/// # use kernel::prelude::*;
> +/// use kernel::macros::TryFrom;
> +///
> +/// #[derive(Debug, Default, PartialEq, TryFrom)]
> +/// #[repr(u8)]
> +/// enum Foo {
> +///     #[default]
> +///     A,
> +///     B = 0x7,
> +/// }
> +///
> +/// assert_eq!(Ok(Foo::A), Foo::try_from(0_u8));
> +/// assert_eq!(Ok(Foo::B), Foo::try_from(0x7_u8));
> +/// assert_eq!(Err(EINVAL), Foo::try_from(0x8_u8));
> +/// ```
> +///
> +/// ## With `#[try_from(...)]`
> +///
> +/// The macro implements `TryFrom<T>` for each `T` specified in `#[try_from(...)]`,
> +/// which always overrides `#[repr(...)]`:
> +///
> +/// ```rust
> +/// # use kernel::prelude::*;
> +/// use kernel::{
> +///     macros::TryFrom,
> +///     num::Bounded, //
> +/// };
> +///
> +/// #[derive(Debug, Default, PartialEq, TryFrom)]
> +/// #[try_from(bool, i16, Bounded<u8, 4>)]
> +/// #[repr(u8)]
> +/// enum Foo {
> +///     #[default]
> +///     A,
> +///     B,
> +/// }
> +///
> +/// assert_eq!(Err(EINVAL), Foo::try_from(-1_i16));
> +/// assert_eq!(Ok(Foo::A), Foo::try_from(0_i16));
> +/// assert_eq!(Ok(Foo::B), Foo::try_from(1_i16));
> +/// assert_eq!(Err(EINVAL), Foo::try_from(2_i16));
> +///
> +/// assert_eq!(Ok(Foo::A), Foo::try_from(false));
> +/// assert_eq!(Ok(Foo::B), Foo::try_from(true));
> +///
> +/// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u8, 4>::new::<0>()));
> +/// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 4>::new::<1>()));
> +/// ```
> +///
> +/// ## Compile-time Overflow Assertion
> +///
> +/// The following examples do not compile:
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::TryFrom;
> +/// #[derive(TryFrom)]
> +/// #[try_from(u8)]
> +/// enum Foo {
> +///     // `256` is larger than `u8::MAX`.
> +///     A = 256,
> +/// }
> +/// ```
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::TryFrom;
> +/// #[derive(TryFrom)]
> +/// #[try_from(u8)]
> +/// enum Foo {
> +///     // `-1` cannot be represented with `u8`.
> +///     A = -1,
> +/// }
> +/// ```
> +///
> +/// ## Unsupported Cases
> +///
> +/// The following examples do not compile:
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::TryFrom;
> +/// // Tuple-like enums or struct-like enums are not allowed.
> +/// #[derive(TryFrom)]
> +/// enum Foo {
> +///     A(u8),
> +///     B { inner: u8 },
> +/// }
> +/// ```
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::TryFrom;
> +/// // Structs are not allowed.
> +/// #[derive(TryFrom)]
> +/// struct Foo(u8);
> +/// ```
> +///
> +/// ```compile_fail
> +/// # use kernel::macros::TryFrom;
> +/// // `repr(C)` enums are not allowed.
> +/// #[derive(TryFrom)]
> +/// struct Foo(u8)
> +/// ```
> +#[proc_macro_derive(TryFrom, attributes(try_from))]
> +pub fn derive_try_from(input: TokenStream) -> TokenStream {
> +    let input = parse_macro_input!(input as DeriveInput);
> +    convert::derive_try_from(input)
> +        .unwrap_or_else(syn::Error::into_compile_error)
> +        .into()
> +}

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 2/4] rust: macros: add derive macro for `TryFrom`
  2026-02-04  1:39   ` Charalampos Mitrodimas
@ 2026-02-05 21:06     ` Jesung Yang
  2026-02-17  1:55       ` Alexandre Courbot
  0 siblings, 1 reply; 16+ messages in thread
From: Jesung Yang @ 2026-02-05 21:06 UTC (permalink / raw)
  To: Charalampos Mitrodimas, y.j3ms.n
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Alexandre Courbot, linux-kernel, rust-for-linux,
	nouveau

On Wed Feb 4, 2026 at 10:39 AM KST, Charalampos Mitrodimas wrote:
> Jesung Yang via B4 Relay <devnull+y.j3ms.n.gmail.com@kernel.org> writes:
[...]
>> +    fn impl_try_from(
>> +        enum_ident: &Ident,
>> +        variants: &[Ident],
>> +        repr_ty: &syn::Path,
>> +        input_ty: &ValidTy,
>> +    ) -> TokenStream {
>> +        let param = Ident::new("value", Span::call_site());
>> +
>> +        let overflow_assertion = emit_overflow_assert(enum_ident, variants, repr_ty, input_ty);
>> +        let emit_cast = |variant| {
>> +            let variant = ::quote::quote! { #enum_ident::#variant };
>> +            match input_ty {
>> +                ValidTy::Bounded(inner) => {
>> +                    let base_ty = inner.emit_qualified_base_ty();
>> +                    let expr = parse_quote! { #variant as #base_ty };
>> +                    inner.emit_new(&expr)
>> +                }
>> +                ValidTy::Primitive(ident) if ident == "bool" => {
>> +                    ::quote::quote! { ((#variant as #repr_ty) == 1) }
>> +                }
>> +                qualified @ ValidTy::Primitive(_) => ::quote::quote! { #variant as #qualified },
>> +            }
>> +        };
>> +
>> +        let clauses = variants.iter().map(|variant| {
>> +            let cast = emit_cast(variant);
>> +            ::quote::quote! {
>> +                if #param == #cast {
>> +                    ::core::result::Result::Ok(#enum_ident::#variant)
>> +                } else
>> +            }
>> +        });
>> +
>> +        ::quote::quote! {
>> +            #[automatically_derived]
>> +            impl ::core::convert::TryFrom<#input_ty> for #enum_ident {
>> +                type Error = ::kernel::prelude::Error;
>> +                fn try_from(#param: #input_ty) -> Result<#enum_ident, Self::Error> {
>> +                    #overflow_assertion
>> +
>> +                    #(#clauses)* {
>> +                        ::core::result::Result::Err(::kernel::prelude::EINVAL)
>
> What happens if we need a different error type here? For example, a
> quick look around in nova-core's "Chipset" enum, an unrecognized chipset
> ID warrants ENODEV rather than EINVAL, since it's about device
> identification.
>
> Not sure if it fits the design, just wondering if this flexibility would
> be useful, but would something like an optional
>
>     error = <ERROR>
>
> in the
>
>    #[try_from(...)]
>
> attribute make sense? e.g.
>
>    #[try_from(u32, error = ENODEV)]
>
> defaulting ofcourse to EINVAL if unspecified.

I believe this is indeed a desired change.

Back in September, an RFC [1] using the same API (i.e., without error
customization) was sent; I took a quick look at the time and felt
everything was OK, but in hindsight, the need for this flexibility is
clear.

Your proposed API looks good to me. Unless there are objections, I'll
move forward with this approach.

Thanks!

[1] https://lore.kernel.org/rust-for-linux/20250930-nova-tryfrom-v1-1-0cc1bb507047@nvidia.com/

Best regards,
Jesung

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 2/4] rust: macros: add derive macro for `TryFrom`
  2026-02-05 21:06     ` Jesung Yang
@ 2026-02-17  1:55       ` Alexandre Courbot
  2026-02-17  2:45         ` Charalampos Mitrodimas
  0 siblings, 1 reply; 16+ messages in thread
From: Alexandre Courbot @ 2026-02-17  1:55 UTC (permalink / raw)
  To: Jesung Yang
  Cc: Charalampos Mitrodimas, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, linux-kernel, rust-for-linux,
	nouveau

On Fri Feb 6, 2026 at 6:06 AM JST, Jesung Yang wrote:
> On Wed Feb 4, 2026 at 10:39 AM KST, Charalampos Mitrodimas wrote:
>> Jesung Yang via B4 Relay <devnull+y.j3ms.n.gmail.com@kernel.org> writes:
> [...]
>>> +    fn impl_try_from(
>>> +        enum_ident: &Ident,
>>> +        variants: &[Ident],
>>> +        repr_ty: &syn::Path,
>>> +        input_ty: &ValidTy,
>>> +    ) -> TokenStream {
>>> +        let param = Ident::new("value", Span::call_site());
>>> +
>>> +        let overflow_assertion = emit_overflow_assert(enum_ident, variants, repr_ty, input_ty);
>>> +        let emit_cast = |variant| {
>>> +            let variant = ::quote::quote! { #enum_ident::#variant };
>>> +            match input_ty {
>>> +                ValidTy::Bounded(inner) => {
>>> +                    let base_ty = inner.emit_qualified_base_ty();
>>> +                    let expr = parse_quote! { #variant as #base_ty };
>>> +                    inner.emit_new(&expr)
>>> +                }
>>> +                ValidTy::Primitive(ident) if ident == "bool" => {
>>> +                    ::quote::quote! { ((#variant as #repr_ty) == 1) }
>>> +                }
>>> +                qualified @ ValidTy::Primitive(_) => ::quote::quote! { #variant as #qualified },
>>> +            }
>>> +        };
>>> +
>>> +        let clauses = variants.iter().map(|variant| {
>>> +            let cast = emit_cast(variant);
>>> +            ::quote::quote! {
>>> +                if #param == #cast {
>>> +                    ::core::result::Result::Ok(#enum_ident::#variant)
>>> +                } else
>>> +            }
>>> +        });
>>> +
>>> +        ::quote::quote! {
>>> +            #[automatically_derived]
>>> +            impl ::core::convert::TryFrom<#input_ty> for #enum_ident {
>>> +                type Error = ::kernel::prelude::Error;
>>> +                fn try_from(#param: #input_ty) -> Result<#enum_ident, Self::Error> {
>>> +                    #overflow_assertion
>>> +
>>> +                    #(#clauses)* {
>>> +                        ::core::result::Result::Err(::kernel::prelude::EINVAL)
>>
>> What happens if we need a different error type here? For example, a
>> quick look around in nova-core's "Chipset" enum, an unrecognized chipset
>> ID warrants ENODEV rather than EINVAL, since it's about device
>> identification.
>>
>> Not sure if it fits the design, just wondering if this flexibility would
>> be useful, but would something like an optional
>>
>>     error = <ERROR>
>>
>> in the
>>
>>    #[try_from(...)]
>>
>> attribute make sense? e.g.
>>
>>    #[try_from(u32, error = ENODEV)]
>>
>> defaulting ofcourse to EINVAL if unspecified.
>
> I believe this is indeed a desired change.
>
> Back in September, an RFC [1] using the same API (i.e., without error
> customization) was sent; I took a quick look at the time and felt
> everything was OK, but in hindsight, the need for this flexibility is
> clear.
>
> Your proposed API looks good to me. Unless there are objections, I'll
> move forward with this approach.

One problem I can see is that ultimately the error depends on the
context of the call, not the type itself.

Nova-core's `Chipset` returning `ENODEV` is a bit too opportunistic to
me - the only place where we are doing the conversion is within probe,
and that's the error that probe is expected to return. But in another
context (say, validating some user input), `EINVAL` could be the right
error to return.

There is technically only one reason for the derived `TryFrom`
implementations to fail, and that's because the passed value doesn't
exist in the enum. So really what we would ideally want here is a
conversion method returning an `Option`, like enumn's `n` [1], that we
`ok_or` into the correct error for the context.

But short of that, I guess we could also have a dedicated, single-value
error type for derived `TryFrom` implementations that we `map_err`. That
type could even have an `Into<Error>` implementation that converts it to
`EINVAL` by default, as that's going to be the most common case.

... but if we do that, that's not very different from returning `EINVAL`
and having callers `map_err` on that when they need it.

[1] https://docs.rs/enumn/latest/enumn/

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 2/4] rust: macros: add derive macro for `TryFrom`
  2026-02-17  1:55       ` Alexandre Courbot
@ 2026-02-17  2:45         ` Charalampos Mitrodimas
  0 siblings, 0 replies; 16+ messages in thread
From: Charalampos Mitrodimas @ 2026-02-17  2:45 UTC (permalink / raw)
  To: Alexandre Courbot
  Cc: Jesung Yang, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, linux-kernel, rust-for-linux,
	nouveau

"Alexandre Courbot" <acourbot@nvidia.com> writes:

> On Fri Feb 6, 2026 at 6:06 AM JST, Jesung Yang wrote:
>> On Wed Feb 4, 2026 at 10:39 AM KST, Charalampos Mitrodimas wrote:
>>> Jesung Yang via B4 Relay <devnull+y.j3ms.n.gmail.com@kernel.org> writes:
>> [...]
>>>> +    fn impl_try_from(
>>>> +        enum_ident: &Ident,
>>>> +        variants: &[Ident],
>>>> +        repr_ty: &syn::Path,
>>>> +        input_ty: &ValidTy,
>>>> +    ) -> TokenStream {
>>>> +        let param = Ident::new("value", Span::call_site());
>>>> +
>>>> +        let overflow_assertion = emit_overflow_assert(enum_ident, variants, repr_ty, input_ty);
>>>> +        let emit_cast = |variant| {
>>>> +            let variant = ::quote::quote! { #enum_ident::#variant };
>>>> +            match input_ty {
>>>> +                ValidTy::Bounded(inner) => {
>>>> +                    let base_ty = inner.emit_qualified_base_ty();
>>>> +                    let expr = parse_quote! { #variant as #base_ty };
>>>> +                    inner.emit_new(&expr)
>>>> +                }
>>>> +                ValidTy::Primitive(ident) if ident == "bool" => {
>>>> +                    ::quote::quote! { ((#variant as #repr_ty) == 1) }
>>>> +                }
>>>> +                qualified @ ValidTy::Primitive(_) => ::quote::quote! { #variant as #qualified },
>>>> +            }
>>>> +        };
>>>> +
>>>> +        let clauses = variants.iter().map(|variant| {
>>>> +            let cast = emit_cast(variant);
>>>> +            ::quote::quote! {
>>>> +                if #param == #cast {
>>>> +                    ::core::result::Result::Ok(#enum_ident::#variant)
>>>> +                } else
>>>> +            }
>>>> +        });
>>>> +
>>>> +        ::quote::quote! {
>>>> +            #[automatically_derived]
>>>> +            impl ::core::convert::TryFrom<#input_ty> for #enum_ident {
>>>> +                type Error = ::kernel::prelude::Error;
>>>> +                fn try_from(#param: #input_ty) -> Result<#enum_ident, Self::Error> {
>>>> +                    #overflow_assertion
>>>> +
>>>> +                    #(#clauses)* {
>>>> +                        ::core::result::Result::Err(::kernel::prelude::EINVAL)
>>>
>>> What happens if we need a different error type here? For example, a
>>> quick look around in nova-core's "Chipset" enum, an unrecognized chipset
>>> ID warrants ENODEV rather than EINVAL, since it's about device
>>> identification.
>>>
>>> Not sure if it fits the design, just wondering if this flexibility would
>>> be useful, but would something like an optional
>>>
>>>     error = <ERROR>
>>>
>>> in the
>>>
>>>    #[try_from(...)]
>>>
>>> attribute make sense? e.g.
>>>
>>>    #[try_from(u32, error = ENODEV)]
>>>
>>> defaulting ofcourse to EINVAL if unspecified.
>>
>> I believe this is indeed a desired change.
>>
>> Back in September, an RFC [1] using the same API (i.e., without error
>> customization) was sent; I took a quick look at the time and felt
>> everything was OK, but in hindsight, the need for this flexibility is
>> clear.
>>
>> Your proposed API looks good to me. Unless there are objections, I'll
>> move forward with this approach.
>
> One problem I can see is that ultimately the error depends on the
> context of the call, not the type itself.
>
> Nova-core's `Chipset` returning `ENODEV` is a bit too opportunistic to
> me - the only place where we are doing the conversion is within probe,
> and that's the error that probe is expected to return. But in another
> context (say, validating some user input), `EINVAL` could be the right
> error to return.
>
> There is technically only one reason for the derived `TryFrom`
> implementations to fail, and that's because the passed value doesn't
> exist in the enum. So really what we would ideally want here is a
> conversion method returning an `Option`, like enumn's `n` [1], that we
> `ok_or` into the correct error for the context.

Good one yes. This is a cleaner approach. Decoupling the error from the
type and letting the caller decode via ok_or looks better to me.

>
> But short of that, I guess we could also have a dedicated, single-value
> error type for derived `TryFrom` implementations that we `map_err`. That
> type could even have an `Into<Error>` implementation that converts it to
> `EINVAL` by default, as that's going to be the most common case.
>
> ... but if we do that, that's not very different from returning `EINVAL`
> and having callers `map_err` on that when they need it.
>
> [1] https://docs.rs/enumn/latest/enumn/

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros
  2026-01-29 14:32 [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang via B4 Relay
                   ` (4 preceding siblings ...)
  2026-02-03 12:48 ` [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros shivam kalra
@ 2026-02-28  5:31 ` Alexandre Courbot
  2026-02-28  5:33   ` Alexandre Courbot
  2026-03-20 10:04   ` Jesung Yang
  5 siblings, 2 replies; 16+ messages in thread
From: Alexandre Courbot @ 2026-02-28  5:31 UTC (permalink / raw)
  To: Jesung Yang via B4 Relay
  Cc: y.j3ms.n, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, linux-kernel, rust-for-linux,
	nouveau

On Thu Jan 29, 2026 at 11:32 PM JST, Jesung Yang via B4 Relay wrote:
> This patch series introduces derive macros for the `TryFrom` and `Into`
> traits.

FWIW I have converted nova-core to use this, and this results in -200LoC
delta. Obviously I like this very much. :)

A few pieces of feedback for things I noticed while doing the
conversion:

- Very often we need to convert a type from and into the same primitive.
  Not having to repeat the same thing in `#[try_from(foo)]` and
  `#[into(foo)]` for these cases would be nice.
- In some rare cases, we want to convert an enum with 4 variants into
  e.g. a `Bounded<u8, 2>`. This can be done using a `From`
  implementation, and that's what the register macro expects. These
  cases are not covered by the current macro (they are few however).
- When converting from/into boundeds, the number of used bits is the
  only thing that counts - the backing type (u8, u32, ...) is
  irrelevant. I thought it would be cool to be able to have a generic
  TryFrom/Into implementation that works irrespective of the backing
  type, but as far as I can tell this would be difficult to achieve.
  Just throwing the idea in case others are more inspired than I am. :)

<snip>
> One last point: the current `Into` implementation relies on
> `Bounded::from_expr()`, which utilizes `build_assert!()`. So the
> expanded result looks roughly like this:
>
>   impl From<Enum> for Bounded<u8, 4> {
>       fn from(value: Enum) -> Bounded<u8, 4> {
>           // Compile-time assertions to guarantee `value` fits within
>           // `u8` (Omitted)
>
>           Bounded::<u8, 4>::from_expr(value as u8)
>       }
>   }
>
> After some experimentation, it appears that the compiler correctly
> optimizes out the assertion if (and only if) the `Bounded` type covers
> all enum discriminants, though I'm not 100% certain of this behavior in
> all cases. Can we approach this better?

I think you should also be able to have a match arm for all variants and
call the `Bounded::new` const method - hopefully the compiler will
optimize that as well. That or rely on unsafe code and a (hypothetical)
`new_unchecked` constructor for Bounded, since the macro can infer that
its invariants are respected.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros
  2026-02-28  5:31 ` Alexandre Courbot
@ 2026-02-28  5:33   ` Alexandre Courbot
  2026-03-20 10:04   ` Jesung Yang
  1 sibling, 0 replies; 16+ messages in thread
From: Alexandre Courbot @ 2026-02-28  5:33 UTC (permalink / raw)
  To: Jesung Yang via B4 Relay
  Cc: y.j3ms.n, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, linux-kernel, rust-for-linux,
	nouveau

On Sat Feb 28, 2026 at 2:31 PM JST, Alexandre Courbot wrote:
> On Thu Jan 29, 2026 at 11:32 PM JST, Jesung Yang via B4 Relay wrote:
>> This patch series introduces derive macros for the `TryFrom` and `Into`
>> traits.
>
> FWIW I have converted nova-core to use this, and this results in -200LoC
> delta. Obviously I like this very much. :)

Forgot to mention, the commit updating nova-core is here:

https://github.com/Gnurou/linux/commit/27ef02934a34a88a26ac117b7a46bff85d014c0c


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros
  2026-02-28  5:31 ` Alexandre Courbot
  2026-02-28  5:33   ` Alexandre Courbot
@ 2026-03-20 10:04   ` Jesung Yang
  2026-03-22  6:14     ` Jesung Yang
  1 sibling, 1 reply; 16+ messages in thread
From: Jesung Yang @ 2026-03-20 10:04 UTC (permalink / raw)
  To: Alexandre Courbot, Jesung Yang via B4 Relay
  Cc: y.j3ms.n, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, linux-kernel, rust-for-linux,
	nouveau

Apologies for the delay.

On Sat Feb 28, 2026 at 2:31 PM KST, Alexandre Courbot wrote:
[...]
> FWIW I have converted nova-core to use this, and this results in -200LoC
> delta. Obviously I like this very much. :)
>
> A few pieces of feedback for things I noticed while doing the
> conversion:
>
> - Very often we need to convert a type from and into the same primitive.
>   Not having to repeat the same thing in `#[try_from(foo)]` and
>   `#[into(foo)]` for these cases would be nice.

I think I can add a common attribute that works for both `TryFrom` and
`Into`, e.g. `#[convert(foo)]`.

> - In some rare cases, we want to convert an enum with 4 variants into
>   e.g. a `Bounded<u8, 2>`. This can be done using a `From`
>   implementation, and that's what the register macro expects. These
>   cases are not covered by the current macro (they are few however).

I think you can just do the following?:

    #[derive(Into)]
    #[into(Bounded<u8, 2>)]
    enum Enum {
        A,
        B,
        C,
        D,
    }

    let a = Bounded::<u8, 2>::from(Enum::A);
    // or let a: Bounded<u8, 2> = Enum::A.into();

This works because `Into` actually generates the `From<Enum>`
implementation for `Bounded<u8, 2>`.

> - When converting from/into boundeds, the number of used bits is the
>   only thing that counts - the backing type (u8, u32, ...) is
>   irrelevant. I thought it would be cool to be able to have a generic
>   TryFrom/Into implementation that works irrespective of the backing
>   type, but as far as I can tell this would be difficult to achieve.
>   Just throwing the idea in case others are more inspired than I am. :)

Yeah, it seems difficult to me as well since those macros need the
full type information to generate the trait implementations.

>> One last point: the current `Into` implementation relies on
>> `Bounded::from_expr()`, which utilizes `build_assert!()`. So the
>> expanded result looks roughly like this:
>>
>>   impl From<Enum> for Bounded<u8, 4> {
>>       fn from(value: Enum) -> Bounded<u8, 4> {
>>           // Compile-time assertions to guarantee `value` fits within
>>           // `u8` (Omitted)
>>
>>           Bounded::<u8, 4>::from_expr(value as u8)
>>       }
>>   }
>>
>> After some experimentation, it appears that the compiler correctly
>> optimizes out the assertion if (and only if) the `Bounded` type covers
>> all enum discriminants, though I'm not 100% certain of this behavior in
>> all cases. Can we approach this better?
>
> I think you should also be able to have a match arm for all variants and
> call the `Bounded::new` const method - hopefully the compiler will
> optimize that as well. That or rely on unsafe code and a (hypothetical)
> `new_unchecked` constructor for Bounded, since the macro can infer that
> its invariants are respected.

This looks much better. Thanks for the idea!

I'll update the implementation based on your feedback within this week,
but I'd like to ping Benno first to check whether he's satisfied with
this v5 before I send those new changes.

Thanks for your patience.

Best regards,
Jesung

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros
  2026-03-20 10:04   ` Jesung Yang
@ 2026-03-22  6:14     ` Jesung Yang
  0 siblings, 0 replies; 16+ messages in thread
From: Jesung Yang @ 2026-03-22  6:14 UTC (permalink / raw)
  To: Jesung Yang, Alexandre Courbot, Jesung Yang via B4 Relay
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, linux-kernel, rust-for-linux, nouveau

On Fri Mar 20, 2026 at 7:04 PM KST, Jesung Yang wrote:
[...]
> I'll update the implementation based on your feedback within this week,

JFYI, the updated version can be found at:

https://github.com/J3m3/linux/tree/tryfrom-into-macro.v6

Best regards,
Jesung

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-03-22  6:14 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-29 14:32 [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang via B4 Relay
2026-01-29 14:32 ` [PATCH v5 1/4] rust: macros: add derive macro for `Into` Jesung Yang via B4 Relay
2026-01-29 16:11   ` Charalampos Mitrodimas
2026-01-30 10:03     ` Jesung Yang
2026-01-29 14:32 ` [PATCH v5 2/4] rust: macros: add derive macro for `TryFrom` Jesung Yang via B4 Relay
2026-02-04  1:39   ` Charalampos Mitrodimas
2026-02-05 21:06     ` Jesung Yang
2026-02-17  1:55       ` Alexandre Courbot
2026-02-17  2:45         ` Charalampos Mitrodimas
2026-01-29 14:32 ` [PATCH v5 3/4] rust: macros: add private doctests for `Into` derive macro Jesung Yang via B4 Relay
2026-01-29 14:32 ` [PATCH v5 4/4] rust: macros: add private doctests for `TryFrom` " Jesung Yang via B4 Relay
2026-02-03 12:48 ` [PATCH v5 0/4] rust: add `TryFrom` and `Into` derive macros shivam kalra
2026-02-28  5:31 ` Alexandre Courbot
2026-02-28  5:33   ` Alexandre Courbot
2026-03-20 10:04   ` Jesung Yang
2026-03-22  6:14     ` Jesung Yang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox