Rust for Linux List
 help / color / mirror / Atom feed
* [PATCH v6 00/10] rust: add conversion derives and exhaustive From support
@ 2026-09-29 13:58 Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 01/10] rust: macros: add derive macro for `Into` Kaiqi Guo
                   ` (9 more replies)
  0 siblings, 10 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda, Jesung Yang, Alexandre Courbot,
	Danilo Krummrich
  Cc: nova-gpu, dri-devel, linux-kernel, Kaiqi Guo, Boqun Feng,
	Gary Guo, bjorn3_gh, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Daniel Almeida, Tamir Duberstein, work,
	David Airlie, Simona Vetter, Charalampos Mitrodimas, Shivam Kalra

Nova register enums need conversions to and from integer fields. This
series adds Into and TryFrom derives and a shared convert helper, then
adds From for enums that cover every possible input value. Two Nova
register enums use the derives without changing their conversion results
or error types.

This continues Jesung Yang's work. The first five patches preserve his
authorship and implementation from the unpublished tryfrom-into-macro.v6
branch [1], rebased onto rust-next. Jesung has not formally posted this
v6 series. The remaining five patches fix test and name-resolution issues,
add exhaustive conversions, and exercise them in Nova. The last public
submission I found is v5 [2]; the v6 branch was announced in March [3].
The v6 numbering here follows that public submission history.

Alexandre requested an infallible Bounded-to-enum conversion when the enum
covers the entire field [4], referring to bounded_enum!'s compile-time
exhaustiveness check [5]. This version adds:

    #[derive(kernel::macros::From, kernel::macros::Into)]
    #[convert(Bounded<u8, 2>)]
    enum Mode {
        A = 0,
        B = 1,
        C = 2,
        D = 3,
    }

The direction-specific spelling is #[derive(From)] with #[from(...)].
From also supplies the standard blanket TryFrom with Error = Infallible;
an explicit TryFrom derive for the same input would conflict with it.

The exhaustiveness proof reuses the existing per-discriminant range
assertions and Rust's rejection of duplicate discriminants. It checks
that the number of distinct variants equals the input domain size,
comparing spans to avoid overflowing a full-width cardinality. The
generated function compares all but the final variant, then returns that
variant. Missing values, gaps, and out-of-range discriminants fail at
compile time even without a call site. No panic or unsafe path is needed.

This count-based proof is a new implementation choice, rather than the
MAX.. match pattern suggested in the thread. Review of this choice and
the From/from spelling would be welcome. No new conversion trait or
Bounded abstraction is introduced.

Changes since the public v5:

- Preserve Jesung's shared convert helper and replacement of from_expr
  with per-variant Bounded::new const-generic constructors.
- Preserve his separate private Into and TryFrom doctest patches.
- Rebase onto d266640c6c76, retaining the new ForLt/CovariantForLt macros.
- Correct repr(C) negative examples that still used structs, and make
  the negative unsigned Bounded tests exercise conversion overflow.
- Add From/from and compile-pass/fail tests for exhaustive conversions.
- Reject generic arguments on primitive helper types and qualify generated
  Result and Bounded backing-type names against local aliases.
- Migrate Architecture to TryFrom/Into and FalconCoreRevSubversion to
  From/Into, preserving EINVAL and Infallible respectively.

Two earlier API questions remain deliberately visible for review:

- The absence of an explicit integer repr still defaults to isize.
- Helper arguments still override the repr-derived conversion type;
  they do not add an extra implementation for that repr type.

These retain Jesung's v6 choices. TryFrom also retains Error/EINVAL;
context-specific errors remain the caller's responsibility. This series
does not change Chipset's ENODEV conversion or add a configurable error
API. Bounded constructor support is unchanged (up to 64-bit and pointer
width backing integers); primitive u128/i128 domains are checked too.

Validation on Debian arm64, Rust 1.85.1, LLVM 19.1.7, bindgen 0.71.1:

- Rust and drivers/gpu/nova-core.o builds passed.
- rusttest: 128 doctests passed, 4 existing examples ignored.
- rustfmtcheck, CLIPPY=1 builds, and rustdoc passed.
- rust/.kunitconfig under arm64 QEMU: 360 tests passed.
- Exhaustive before/after comparison of the actual Nova declarations:
  Architecture 64/64 inputs identical (6 successes, 58 EINVAL), and
  FalconCoreRevSubversion 4/4 identical, including round trips and the
  Infallible error type.
- Separate negative-test probes checked the expected diagnostic for
  missing values, gaps, overflow, and duplicate discriminants.

No NVIDIA hardware testing was performed. checkpatch reports no errors;
the preserved original patches retain a new-file MAINTAINERS reminder
and one 120-column doctest line warning. The five follow-up patches have
no checkpatch warnings. Previous review/test tags dropped by Jesung are
not restored, and no new review, test, or ack tags are claimed.

Base: rust-next d266640c6c760c9bc215bf5a3ece122ca488b6f5

[1] https://github.com/J3m3/linux/tree/2ea456bd5f26bd66c766b462a7d606d9842c208a
[2] https://lore.kernel.org/rust-for-linux/20260129-try-from-into-macro-v5-0-dd011008118c@gmail.com/
[3] https://lore.kernel.org/rust-for-linux/DH939HK0611M.22A8T9BJ3NG8@gmail.com/
[4] https://lore.kernel.org/rust-for-linux/DHHJCEG8BC47.2VC6GLDRRZH1B@nvidia.com/
[5] https://lore.kernel.org/rust-for-linux/DHHK2OJ6O83V.2MZNHRQYK21EU@nvidia.com/

--
This series is based on Jesung's five commits below, followed by five
separate continuation commits.

Jesung Yang (5):
  rust: macros: add derive macro for `Into`
  rust: macros: add derive macro for `TryFrom`
  rust: macros: add `convert` helper attribute
  rust: macros: add private doctests for `Into` derive macro
  rust: macros: add private doctests for `TryFrom` derive macro

Kaiqi Guo (5):
  rust: macros: exercise the intended conversion doctest failures
  rust: macros: derive From for exhaustive enum conversions
  rust: macros: test exhaustive conversion derives
  rust: macros: validate and qualify conversion helper types
  gpu: nova-core: use conversion derives for two register enums

 drivers/gpu/nova-core/falcon.rs |   19 +-
 drivers/gpu/nova-core/gpu.rs    |   22 +-
 rust/macros/convert.rs          | 2093 +++++++++++++++++++++++++++++++
 rust/macros/lib.rs              |  522 +++++++-
 4 files changed, 2633 insertions(+), 23 deletions(-)
 create mode 100644 rust/macros/convert.rs


base-commit: d266640c6c760c9bc215bf5a3ece122ca488b6f5


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

* [PATCH v6 01/10] rust: macros: add derive macro for `Into`
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 02/10] rust: macros: add derive macro for `TryFrom` Kaiqi Guo
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo

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>
Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 rust/macros/convert.rs | 527 +++++++++++++++++++++++++++++++++++++++++
 rust/macros/lib.rs     | 174 +++++++++++++-
 2 files changed, 700 insertions(+), 1 deletion(-)
 create mode 100644 rust/macros/convert.rs

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
new file mode 100644
index 000000000000..6702c2e48c1f
--- /dev/null
+++ b/rust/macros/convert.rs
@@ -0,0 +1,527 @@
+// 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) {
+            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 match_arms = variants.iter().map(|variant| {
+                    let qualified_variant = ::quote::quote! { #enum_ident::#variant };
+                    let expr = parse_quote! { #qualified_variant as #base_ty };
+                    let bounded_new = inner.emit_new(&expr);
+                    ::quote::quote! {
+                        #qualified_variant => #bounded_new,
+                    }
+                });
+                ::quote::quote! {
+                    match #param {
+                        #(#match_arms)*
+                    }
+                }
+            }
+            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_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 }
+    }
+
+    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 24f96feaeb34..1faeebc0de4b 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -15,6 +15,7 @@
 #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
 
 mod concat_idents;
+mod convert;
 mod export;
 mod fmt;
 mod for_lt;
@@ -26,7 +27,10 @@
 
 use proc_macro::TokenStream;
 
-use syn::parse_macro_input;
+use syn::{
+    parse_macro_input,
+    DeriveInput, //
+};
 
 /// Declares a kernel module.
 ///
@@ -537,3 +541,171 @@ pub fn ForLt(input: TokenStream) -> TokenStream {
 pub fn CovariantForLt(input: TokenStream) -> TokenStream {
     for_lt::covariant_for_lt(parse_macro_input!(input)).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)]
+/// #[repr(C)]
+/// 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()
+}


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

* [PATCH v6 02/10] rust: macros: add derive macro for `TryFrom`
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 01/10] rust: macros: add derive macro for `Into` Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 03/10] rust: macros: add `convert` helper attribute Kaiqi Guo
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo

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>
Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 rust/macros/convert.rs |  58 ++++++++++++++
 rust/macros/lib.rs     | 177 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index 6702c2e48c1f..c64e35447859 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 };
@@ -245,6 +253,56 @@ 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 qualified_variant = ::quote::quote! { #enum_ident::#variant };
+            match input_ty {
+                ValidTy::Bounded(inner) => {
+                    let base_ty = inner.emit_qualified_base_ty();
+                    let expr = parse_quote! { #qualified_variant as #base_ty };
+                    inner.emit_new(&expr)
+                }
+                ValidTy::Primitive(ident) if ident == "bool" => {
+                    ::quote::quote! { ((#qualified_variant as #repr_ty) == 1) }
+                }
+                qualified @ ValidTy::Primitive(_) => {
+                    ::quote::quote! { #qualified_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],
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 1faeebc0de4b..8b4f3e9dc860 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -709,3 +709,180 @@ 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)]
+/// #[repr(C)]
+/// 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 related	[flat|nested] 12+ messages in thread

* [PATCH v6 03/10] rust: macros: add `convert` helper attribute
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 01/10] rust: macros: add derive macro for `Into` Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 02/10] rust: macros: add derive macro for `TryFrom` Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 04/10] rust: macros: add private doctests for `Into` derive macro Kaiqi Guo
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo

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

Introduce `#[convert(...)]`, a common helper attribute for the `Into`
and `TryFrom` derive macros. This helps avoid repeating the same types
in `#[into(...)]` and `#[try_from(...)]` when deriving both traits for
the same enum.

For example:

    #[derive(Into, TryFrom)]
    #[try_from(u8)]
    #[into(u8)]
    enum Enum {
        A,
        B,
    }

The above snippet can be shortened to:

    #[derive(Into, TryFrom)]
    #[convert(u8)]
    enum Enum {
        A,
        B,
    }

Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 rust/macros/convert.rs |  7 +++-
 rust/macros/lib.rs     | 78 +++++++++++++++++++++++++++++++++++++++---
 2 files changed, 80 insertions(+), 5 deletions(-)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index c64e35447859..21d79bca16a0 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -150,6 +150,10 @@ fn get_helper_name(&self) -> &'static str {
             Self::TryFrom => "try_from",
         }
     }
+
+    fn get_common_helper_name(&self) -> &'static str {
+        "convert"
+    }
 }
 
 fn parse_attrs(
@@ -157,6 +161,7 @@ fn parse_attrs(
     attrs: &[Attribute],
 ) -> syn::Result<(Vec<Type>, bool, Option<Ident>)> {
     let helper = target.get_helper_name();
+    let common_helper = target.get_common_helper_name();
 
     let mut is_repr_c = false;
     let mut repr_ty = None;
@@ -175,7 +180,7 @@ fn parse_attrs(
                 // Delegate `repr` attribute validation to rustc.
                 Ok(())
             })?;
-        } else if attr.path().is_ident(helper) {
+        } else if attr.path().is_ident(helper) || attr.path().is_ident(common_helper) {
             let args = attr.parse_args_with(Punctuated::<Type, Token![,]>::parse_terminated)?;
             helper_tys.extend(args);
         }
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 8b4f3e9dc860..cc6a96516c93 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -557,6 +557,10 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
 /// - The macro uses the `into` custom attribute or `repr` attribute to generate [`From`]
 ///   implementations. `into` always takes precedence over `repr`.
 ///
+/// - In addition to `into`, the `convert` custom attribute is supported. The purpose of
+///   this attribute is to avoid repetition when deriving both [`Into`] and [`TryFrom`]
+///   for the same enum. See ["With `#[convert(...)]`"](#with-convert) for an example.
+///
 /// - 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]
@@ -568,7 +572,7 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
 /// [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(...)]`
+/// # Supported types in `#[into(...)]` and `#[convert(...)]`
 ///
 /// - [`bool`]
 /// - Primitive integer types (e.g., [`i8`], [`u8`])
@@ -650,6 +654,36 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
 /// assert_eq!(Bounded::<u8, 4>::new::<1>(), foo_b);
 /// ```
 ///
+/// ## With `#[convert(...)]`
+///
+/// The macro implements `From<Foo>` for each `T` specified in `#[into(...)]`,
+/// which always overrides `#[repr(...)]`. `#[convert(...)]` is useful when the same type
+/// needs to be specified in both `#[into(...)]` and `#[try_from(...)]`:
+///
+/// ```rust
+/// # use kernel::prelude::*;
+/// use kernel::{
+///     macros::Into,
+///     macros::TryFrom,
+///     num::Bounded, //
+/// };
+///
+/// #[derive(Debug, Default, Into, PartialEq, TryFrom)]
+/// #[convert(bool)]
+/// #[repr(u8)]
+/// enum Foo {
+///     #[default]
+///     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));
+/// ```
+///
 /// ## Compile-time Overflow Assertion
 ///
 /// The following examples do not compile:
@@ -702,7 +736,7 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
 /// #[repr(C)]
 /// struct Foo(u8);
 /// ```
-#[proc_macro_derive(Into, attributes(into))]
+#[proc_macro_derive(Into, attributes(into, convert))]
 pub fn derive_into(input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
     convert::derive_into(input)
@@ -727,6 +761,11 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
 /// - The macro uses the `try_from` custom attribute or `repr` attribute to generate
 ///   [`TryFrom`] implementations. `try_from` always takes precedence over `repr`.
 ///
+/// - In addition to `try_from`, the `convert` custom attribute is supported. The purpose
+///   of this attribute is to avoid repetition when deriving both [`Into`] and
+///   [`TryFrom`] for the same enum. See ["With `#[convert(...)]`"](#with-convert) for an
+///   example.
+///
 /// - 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]
@@ -739,7 +778,7 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
 /// [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(...)]`
+/// # Supported types in `#[try_from(...)]` and `#[convert(...)]`
 ///
 /// - [`bool`]
 /// - Primitive integer types (e.g., [`i8`], [`u8`])
@@ -827,6 +866,37 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
 /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 4>::new::<1>()));
 /// ```
 ///
+/// ## With `#[convert(...)]`
+///
+/// The macro implements `TryFrom<T>` for each `T` specified in `#[convert(...)]`,
+/// which always overrides `#[repr(...)]`. `#[convert(...)]` is useful when the same type
+/// needs to be specified in both `#[into(...)]` and `#[try_from(...)]`:
+///
+///
+/// ```rust
+/// # use kernel::prelude::*;
+/// use kernel::{
+///     macros::Into,
+///     macros::TryFrom,
+///     num::Bounded, //
+/// };
+///
+/// #[derive(Debug, Default, Into, PartialEq, TryFrom)]
+/// #[convert(bool)]
+/// #[repr(u8)]
+/// enum Foo {
+///     #[default]
+///     A,
+///     B,
+/// }
+///
+/// assert_eq!(Ok(Foo::A), Foo::try_from(false));
+/// assert_eq!(Ok(Foo::B), Foo::try_from(true));
+///
+/// assert_eq!(false, Foo::A.into());
+/// assert_eq!(true, Foo::B.into());
+/// ```
+///
 /// ## Compile-time Overflow Assertion
 ///
 /// The following examples do not compile:
@@ -879,7 +949,7 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
 /// #[repr(C)]
 /// struct Foo(u8)
 /// ```
-#[proc_macro_derive(TryFrom, attributes(try_from))]
+#[proc_macro_derive(TryFrom, attributes(try_from, convert))]
 pub fn derive_try_from(input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
     convert::derive_try_from(input)


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

* [PATCH v6 04/10] rust: macros: add private doctests for `Into` derive macro
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
                   ` (2 preceding siblings ...)
  2026-09-29 13:58 ` [PATCH v6 03/10] rust: macros: add `convert` helper attribute Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 05/10] rust: macros: add private doctests for `TryFrom` " Kaiqi Guo
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo

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>
Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 rust/macros/convert.rs | 436 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 436 insertions(+)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index 21d79bca16a0..31cdf84082e7 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -588,3 +588,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 {}
+}


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

* [PATCH v6 05/10] rust: macros: add private doctests for `TryFrom` derive macro
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
                   ` (3 preceding siblings ...)
  2026-09-29 13:58 ` [PATCH v6 04/10] rust: macros: add private doctests for `Into` derive macro Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 06/10] rust: macros: exercise the intended conversion doctest failures Kaiqi Guo
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo

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>
Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 rust/macros/convert.rs | 578 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 578 insertions(+)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index 31cdf84082e7..c07870914242 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -1024,3 +1024,581 @@ 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)]
+    /// #[convert(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 {}
+}


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

* [PATCH v6 06/10] rust: macros: exercise the intended conversion doctest failures
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
                   ` (4 preceding siblings ...)
  2026-09-29 13:58 ` [PATCH v6 05/10] rust: macros: add private doctests for `TryFrom` " Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  2026-09-29 18:47   ` Miguel Ojeda
  2026-09-29 13:58 ` [PATCH v6 07/10] rust: macros: derive From for exhaustive enum conversions Kaiqi Guo
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo, Charalampos Mitrodimas

Use unit-only enums in the repr(C) compile-fail examples so they
exercise the representation check rather than rejecting a struct. Fix
the missing semicolon incidentally hidden by the TryFrom negative test,
and correct the convert helper documentation.

Give the negative Bounded<u32, 32> examples a signed enum representation
so that -1 is a valid discriminant and the conversion overflow assertion
is what rejects it.

Suggested-by: Charalampos Mitrodimas <charmitro@posteo.net>

Link: https://lore.kernel.org/rust-for-linux/m2qzr81jrd.fsf@ip-192-168-1-196.ap-southeast-1.compute.internal/
Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 rust/macros/convert.rs |  4 ++--
 rust/macros/lib.rs     | 12 +++++++++---
 2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index c07870914242..78efbc7b99d7 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -893,7 +893,7 @@ mod overflow_assert_works_on_signed_bounded {}
     ///
     /// #[derive(Into)]
     /// #[into(Bounded<u32, 32>)]
-    /// #[repr(u64)]
+    /// #[repr(i64)]
     /// enum Foo {
     ///     // `-1` cannot be represented with `Bounded<u32, 32>`.
     ///     A = -1,
@@ -1459,7 +1459,7 @@ mod overflow_assert_works_on_signed_bounded {}
     ///
     /// #[derive(TryFrom)]
     /// #[try_from(Bounded<u32, 32>)]
-    /// #[repr(u64)]
+    /// #[repr(i64)]
     /// enum Foo {
     ///     // `-1` cannot be represented with `Bounded<u32, 32>`.
     ///     A = -1,
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index cc6a96516c93..4d420d34ed6b 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -656,7 +656,7 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
 ///
 /// ## With `#[convert(...)]`
 ///
-/// The macro implements `From<Foo>` for each `T` specified in `#[into(...)]`,
+/// The macro implements `From<Foo>` for each `T` specified in `#[convert(...)]`,
 /// which always overrides `#[repr(...)]`. `#[convert(...)]` is useful when the same type
 /// needs to be specified in both `#[into(...)]` and `#[try_from(...)]`:
 ///
@@ -734,7 +734,10 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
 /// // `repr(C)` enums are not allowed.
 /// #[derive(Into)]
 /// #[repr(C)]
-/// struct Foo(u8);
+/// enum Foo {
+///     A,
+///     B,
+/// }
 /// ```
 #[proc_macro_derive(Into, attributes(into, convert))]
 pub fn derive_into(input: TokenStream) -> TokenStream {
@@ -947,7 +950,10 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
 /// // `repr(C)` enums are not allowed.
 /// #[derive(TryFrom)]
 /// #[repr(C)]
-/// struct Foo(u8)
+/// enum Foo {
+///     A,
+///     B,
+/// }
 /// ```
 #[proc_macro_derive(TryFrom, attributes(try_from, convert))]
 pub fn derive_try_from(input: TokenStream) -> TokenStream {


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

* [PATCH v6 07/10] rust: macros: derive From for exhaustive enum conversions
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
                   ` (5 preceding siblings ...)
  2026-09-29 13:58 ` [PATCH v6 06/10] rust: macros: exercise the intended conversion doctest failures Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 08/10] rust: macros: test exhaustive conversion derives Kaiqi Guo
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo

Nova register fields need From<Bounded<T, N>> when every possible field
value names an enum variant. Add From and its from helper alongside Into
and TryFrom, with the shared convert helper for round trips.

Reuse the existing discriminant range assertions. Rust already rejects
duplicate discriminants, so checking that the number of variants matches
the input range proves exhaustive coverage at compile time. Compare
range spans in u128 with wrapping subtraction to handle signed minima
and full-width ranges without overflowing a cardinality.

After testing all but the final variant, return that variant directly.
Missing values and out-of-range discriminants are compilation errors,
including when no conversion is called; the generated implementation
needs neither unsafe code nor a panic branch. Document the blanket
TryFrom implementation and its conflict with an explicit derive for the
same source.

Suggested-by: Alexandre Courbot <acourbot@nvidia.com>

Link: https://lore.kernel.org/rust-for-linux/DHHK2OJ6O83V.2MZNHRQYK21EU@nvidia.com/
Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 rust/macros/convert.rs | 105 ++++++++++++++++++++++++++++++++++-------
 rust/macros/lib.rs     |  95 +++++++++++++++++++++++++++++++++++++
 2 files changed, 182 insertions(+), 18 deletions(-)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index 78efbc7b99d7..7fa8a22dc913 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -38,6 +38,10 @@ pub(crate) fn derive_try_from(input: DeriveInput) -> syn::Result<TokenStream> {
     derive(DeriveTarget::TryFrom, input)
 }
 
+pub(crate) fn derive_from(input: DeriveInput) -> syn::Result<TokenStream> {
+    derive(DeriveTarget::From, input)
+}
+
 fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result<TokenStream> {
     let data_enum = match input.data {
         Data::Enum(data) => data,
@@ -109,6 +113,13 @@ fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result<TokenStream>
         return Err(errors);
     }
 
+    if matches!(target, DeriveTarget::From) && data_enum.variants.is_empty() {
+        return Err(syn::Error::new(
+            input.ident.span(),
+            "`#[derive(From)]` requires a nonempty enum",
+        ));
+    }
+
     let variants: Vec<_> = data_enum
         .variants
         .into_iter()
@@ -134,6 +145,7 @@ fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result<TokenStream>
 enum DeriveTarget {
     Into,
     TryFrom,
+    From,
 }
 
 impl DeriveTarget {
@@ -141,6 +153,7 @@ fn get_trait_name(&self) -> &'static str {
         match self {
             Self::Into => "Into",
             Self::TryFrom => "TryFrom",
+            Self::From => "From",
         }
     }
 
@@ -148,6 +161,7 @@ fn get_helper_name(&self) -> &'static str {
         match self {
             Self::Into => "into",
             Self::TryFrom => "try_from",
+            Self::From => "from",
         }
     }
 
@@ -199,6 +213,7 @@ fn derive_for_enum(
     let impl_fn = match target {
         DeriveTarget::Into => impl_into,
         DeriveTarget::TryFrom => impl_try_from,
+        DeriveTarget::From => impl_from,
     };
 
     let qualified_repr_ty: syn::Path = parse_quote! { ::core::primitive::#repr_ty };
@@ -267,25 +282,8 @@ fn impl_try_from(
         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 qualified_variant = ::quote::quote! { #enum_ident::#variant };
-            match input_ty {
-                ValidTy::Bounded(inner) => {
-                    let base_ty = inner.emit_qualified_base_ty();
-                    let expr = parse_quote! { #qualified_variant as #base_ty };
-                    inner.emit_new(&expr)
-                }
-                ValidTy::Primitive(ident) if ident == "bool" => {
-                    ::quote::quote! { ((#qualified_variant as #repr_ty) == 1) }
-                }
-                qualified @ ValidTy::Primitive(_) => {
-                    ::quote::quote! { #qualified_variant as #qualified }
-                }
-            }
-        };
-
         let clauses = variants.iter().map(|variant| {
-            let cast = emit_cast(variant);
+            let cast = emit_variant_cast(enum_ident, variant, repr_ty, input_ty);
             ::quote::quote! {
                 if #param == #cast {
                     ::core::result::Result::Ok(#enum_ident::#variant)
@@ -308,6 +306,77 @@ fn try_from(#param: #input_ty) -> Result<#enum_ident, Self::Error> {
         }
     }
 
+    fn impl_from(
+        enum_ident: &Ident,
+        variants: &[Ident],
+        repr_ty: &syn::Path,
+        input_ty: &ValidTy,
+    ) -> TokenStream {
+        let param = Ident::new("value", Span::call_site());
+        let (last, rest) = variants.split_last().expect("nonempty enum checked above");
+        let overflow_assertion = emit_overflow_assert(enum_ident, variants, repr_ty, input_ty);
+        let input_min = input_ty.emit_min();
+        let input_max = input_ty.emit_max();
+        let span = rest.len() as u128;
+        let msg = format!("enum `{enum_ident}` does not cover every value of `{input_ty}`");
+
+        let clauses = rest.iter().map(|variant| {
+            let cast = emit_variant_cast(enum_ident, variant, repr_ty, input_ty);
+            ::quote::quote! {
+                if #param == #cast {
+                    #enum_ident::#variant
+                } else
+            }
+        });
+
+        ::quote::quote! {
+            #[automatically_derived]
+            impl ::core::convert::From<#input_ty> for #enum_ident {
+                fn from(#param: #input_ty) -> Self {
+                    #overflow_assertion
+
+                    const _: () = {
+                        // Rust rejects duplicate discriminants, and the overflow assertion
+                        // places every discriminant in the input range. Equal cardinality
+                        // therefore proves that every possible input has a variant.
+                        let min = (#input_min) as ::core::primitive::u128;
+                        let max = (#input_max) as ::core::primitive::u128;
+                        // Wrapping subtraction also handles negative minima. Compare spans
+                        // rather than lengths to avoid overflowing for a full 128-bit range.
+                        ::core::assert!(max.wrapping_sub(min) == #span, #msg);
+                    };
+
+                    // Exhaustiveness guarantees the final variant when no other one matches.
+                    #(#clauses)* {
+                        #enum_ident::#last
+                    }
+                }
+            }
+        }
+    }
+
+    fn emit_variant_cast(
+        enum_ident: &Ident,
+        variant: &Ident,
+        repr_ty: &syn::Path,
+        input_ty: &ValidTy,
+    ) -> TokenStream {
+        let qualified_variant = ::quote::quote! { #enum_ident::#variant };
+        match input_ty {
+            ValidTy::Bounded(inner) => {
+                let base_ty = inner.emit_qualified_base_ty();
+                let expr = parse_quote! { #qualified_variant as #base_ty };
+                inner.emit_new(&expr)
+            }
+            ValidTy::Primitive(ident) if ident == "bool" => {
+                ::quote::quote! { ((#qualified_variant as #repr_ty) == 1) }
+            }
+            qualified @ ValidTy::Primitive(_) => {
+                ::quote::quote! { #qualified_variant as #qualified }
+            }
+        }
+    }
+
     fn emit_overflow_assert(
         enum_ident: &Ident,
         variants: &[Ident],
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 4d420d34ed6b..f45f80dbf8cb 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -962,3 +962,98 @@ pub fn derive_try_from(input: TokenStream) -> TokenStream {
         .unwrap_or_else(syn::Error::into_compile_error)
         .into()
 }
+
+/// Derives [`core::convert::From`] for a unit-only enum covering every input value.
+///
+/// Supported inputs and representation rules are the same as for [`macro@TryFrom`]:
+/// `bool`, primitive integers, and [`Bounded`]. `#[from(...)]` or `#[convert(...)]`
+/// selects the input types, overriding the default derived from `#[repr(...)]`
+/// (or `isize` when no representation is specified).
+///
+/// Every discriminant must fit each input type, and every input value must have
+/// a corresponding variant. Both conditions are checked at compile time, even
+/// when the conversion is never called. Duplicate discriminants are rejected by
+/// Rust itself. Empty enums and the layouts rejected by [`macro@TryFrom`] are
+/// not supported.
+///
+/// The generated conversion is infallible and contains no panic path. It also
+/// provides `TryFrom` with [`core::convert::Infallible`] through the standard
+/// blanket implementation. Do not additionally derive [`macro@TryFrom`] for
+/// the same input type; use it for inputs the enum does not fully cover.
+///
+/// [`Bounded`]: ../kernel/num/bounded/struct.Bounded.html
+///
+/// # Examples
+///
+/// ```
+/// use kernel::macros::From;
+///
+/// #[derive(Debug, PartialEq, From)]
+/// #[from(bool)]
+/// enum Switch {
+///     Off = 0,
+///     On = 1,
+/// }
+///
+/// assert_eq!(Switch::from(false), Switch::Off);
+/// assert_eq!(Switch::from(true), Switch::On);
+/// ```
+///
+/// Use `#[convert(...)]` with `Into` to derive both directions:
+///
+/// ```
+/// use kernel::{macros::{From, Into}, num::Bounded};
+///
+/// #[derive(Debug, PartialEq, From, Into)]
+/// #[convert(Bounded<u8, 2>)]
+/// enum Mode {
+///     A = 0,
+///     B = 1,
+///     C = 2,
+///     D = 3,
+/// }
+///
+/// for value in 0..=3 {
+///     let bounded = Bounded::<u8, 2>::try_new(value).unwrap();
+///     let mode = Mode::from(bounded);
+///     let round_trip: Bounded<u8, 2> = mode.into();
+///     assert_eq!(round_trip, bounded);
+/// }
+/// ```
+///
+/// Missing values are rejected at compile time:
+///
+/// ```compile_fail,E0080
+/// use kernel::macros::From;
+///
+/// #[derive(From)]
+/// #[from(Bounded<u8, 2>)]
+/// enum Incomplete {
+///     A = 0,
+///     B = 1,
+///     C = 2,
+///     // No variant represents 3.
+/// }
+/// ```
+///
+/// Having the right number of variants does not allow out-of-range values:
+///
+/// ```compile_fail,E0080
+/// use kernel::macros::From;
+///
+/// #[derive(From)]
+/// #[from(Bounded<u8, 2>)]
+/// enum OutOfRange {
+///     A = 0,
+///     B = 1,
+///     C = 2,
+///     D = 4,
+/// }
+/// ```
+#[proc_macro_derive(From, attributes(from, convert))]
+pub fn derive_from(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    convert::derive_from(input)
+        .unwrap_or_else(syn::Error::into_compile_error)
+        .into()
+}


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

* [PATCH v6 08/10] rust: macros: test exhaustive conversion derives
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
                   ` (6 preceding siblings ...)
  2026-09-29 13:58 ` [PATCH v6 07/10] rust: macros: derive From for exhaustive enum conversions Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 09/10] rust: macros: validate and qualify conversion helper types Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 10/10] gpu: nova-core: use conversion derives for two register enums Kaiqi Guo
  9 siblings, 0 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo

Exercise From alongside Into and TryFrom with shared and
direction-specific helpers. Cover signed and unsigned bounded ranges,
bool, all 256 values of full-width u8/i8 inputs, implicit and constant
discriminants, cfg variants, and the standard infallible TryFrom blanket
implementation.

Check E0080 for incomplete domains, gaps, invalid bounded widths, and
out-of-range discriminants even without a call site. Separately check
duplicate discriminants (E0081), overlapping TryFrom implementations
(E0119), unsupported layouts, and large primitive domains including
128-bit spans.

Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 rust/macros/convert.rs | 372 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 372 insertions(+)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index 7fa8a22dc913..c23b8819572e 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -1671,3 +1671,375 @@ mod overflow_assert_works_on_unsigned_int {}
     /// ```
     mod any_try_from_target_overflow_is_rejected {}
 }
+
+mod derive_from_tests {
+    /// ```
+    /// use kernel::{macros::{From, Into}, num::Bounded};
+    ///
+    /// #[derive(Debug, PartialEq, From, Into)]
+    /// #[convert(Bounded<i8, 2>, Bounded<i16, 2>, Bounded<i32, 2>, Bounded<i64, 2>)]
+    /// #[from(Bounded<isize, 2>)]
+    /// #[repr(i8)]
+    /// enum Signed {
+    ///     Zero = 0,
+    ///     Min = -2,
+    ///     Max = 1,
+    ///     Negative = -1,
+    /// }
+    ///
+    /// for value in -2..=1 {
+    ///     let bounded = Bounded::<i8, 2>::try_new(value).unwrap();
+    ///     let result: Bounded<i8, 2> = Signed::from(bounded).into();
+    ///     assert_eq!(result, bounded);
+    ///     assert_eq!(Signed::from(bounded.cast::<i16>()), Signed::from(bounded));
+    ///     assert_eq!(Signed::from(bounded.cast::<i32>()), Signed::from(bounded));
+    ///     assert_eq!(Signed::from(bounded.cast::<i64>()), Signed::from(bounded));
+    ///     assert_eq!(Signed::from(bounded.cast::<isize>()), Signed::from(bounded));
+    /// }
+    /// ```
+    mod signed_and_unordered_discriminants {}
+
+    /// ```
+    /// use kernel::{macros::{From, Into}, num::Bounded};
+    ///
+    /// #[derive(Debug, PartialEq, From, Into)]
+    /// #[convert(Bounded<u8, 2>, Bounded<u16, 2>, Bounded<u32, 2>, Bounded<u64, 2>)]
+    /// #[from(Bounded<usize, 2>)]
+    /// enum Unsigned {
+    ///     A = base(),
+    ///     B,
+    ///     C = 1 << 1,
+    ///     D,
+    /// }
+    /// const fn base() -> isize { 0 }
+    ///
+    /// for value in 0..=3 {
+    ///     let bounded = Bounded::<u8, 2>::try_new(value).unwrap();
+    ///     let result: Bounded<u8, 2> = Unsigned::from(bounded).into();
+    ///     assert_eq!(result, bounded);
+    ///     assert_eq!(Unsigned::from(bounded.cast::<u16>()), Unsigned::from(bounded));
+    ///     assert_eq!(Unsigned::from(bounded.cast::<u32>()), Unsigned::from(bounded));
+    ///     assert_eq!(Unsigned::from(bounded.cast::<u64>()), Unsigned::from(bounded));
+    ///     assert_eq!(Unsigned::from(bounded.cast::<usize>()), Unsigned::from(bounded));
+    /// }
+    /// ```
+    mod constants_and_implicit_discriminants {}
+
+    /// ```
+    /// use kernel::{macros::From, num::Bounded};
+    ///
+    /// #[derive(Debug, PartialEq, From)]
+    /// #[from(Bounded<i8, 1>)]
+    /// enum Signed { Negative = -1, Zero = 0 }
+    /// assert_eq!(Signed::from(Bounded::<i8, 1>::new::<-1>()), Signed::Negative);
+    /// assert_eq!(Signed::from(Bounded::<i8, 1>::new::<0>()), Signed::Zero);
+    ///
+    /// #[derive(Debug, PartialEq, From)]
+    /// #[from(bool, Bounded<u8, 1>)]
+    /// #[repr(u8)]
+    /// enum Unsigned { Zero = 0, One = 1 }
+    /// assert_eq!(Unsigned::from(Bounded::<u8, 1>::new::<0>()), Unsigned::Zero);
+    /// assert_eq!(Unsigned::from(Bounded::<u8, 1>::new::<1>()), Unsigned::One);
+    /// assert_eq!(Unsigned::from(false), Unsigned::Zero);
+    /// assert_eq!(Unsigned::from(true), Unsigned::One);
+    /// ```
+    mod single_bit_ranges {}
+
+    /// ```
+    /// use kernel::{macros::{From, Into, TryFrom}, prelude::*};
+    ///
+    /// #[derive(Debug, PartialEq, From, Into, TryFrom)]
+    /// #[from(bool)]
+    /// #[into(u8)]
+    /// #[try_from(u8)]
+    /// enum Switch { Off, On }
+    ///
+    /// let infallible: core::result::Result<Switch, core::convert::Infallible> =
+    ///     Switch::try_from(true);
+    /// assert_eq!(infallible, Ok(Switch::On));
+    /// assert_eq!(Switch::try_from(2_u8), Err(EINVAL));
+    /// assert_eq!(u8::from(Switch::from(false)), 0);
+    /// ```
+    mod distinct_inputs_and_blanket_try_from {}
+
+    /// ```
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(Debug, PartialEq, From)]
+    /// #[from(bool)]
+    /// enum Switch {
+    ///     Off,
+    ///     On,
+    ///     #[cfg(any())]
+    ///     Unavailable,
+    /// }
+    /// assert_eq!(Switch::from(true), Switch::On);
+    /// ```
+    mod cfg_variants {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(Bounded<u8, 2>)]
+    /// enum Gap { A = 0, C = 2, D = 3 }
+    /// ```
+    ///
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(Bounded<u8, 2>)]
+    /// enum MissingMinimum { B = 1, C = 2, D = 3 }
+    /// ```
+    ///
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(Bounded<i8, 2>)]
+    /// enum MissingNegative { A = -1, B = 0, C = 1 }
+    /// ```
+    ///
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(bool)]
+    /// enum MissingTrue { False = 0 }
+    /// ```
+    mod missing_values_without_calling_conversion {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(Bounded<i8, 2>)]
+    /// enum Overflow { A = -3, B = -1, C = 0, D = 1 }
+    /// ```
+    ///
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(bool)]
+    /// enum Overflow { A = 0, B = 2 }
+    /// ```
+    mod correct_count_with_invalid_discriminant {}
+
+    /// ```compile_fail,E0081
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(bool)]
+    /// enum Duplicate { A = 0, B = 0 }
+    /// ```
+    mod duplicate_discriminants {}
+
+    /// ```compile_fail,E0119
+    /// use kernel::{macros::{From, TryFrom}, prelude::*};
+    ///
+    /// #[derive(From, TryFrom)]
+    /// #[convert(bool)]
+    /// enum Conflict { A, B }
+    /// ```
+    mod conflicting_try_from {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(bool, Bounded<u8, 2>)]
+    /// enum IncompleteForSecondInput { A, B }
+    /// ```
+    mod every_input_must_be_exhaustive {}
+
+    /// ```compile_fail
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(bool)]
+    /// enum Empty {}
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(bool)]
+    /// #[repr(C)]
+    /// enum ReprC { A, B }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(bool)]
+    /// enum Payload { A(u8), B { value: u8 } }
+    /// ```
+    mod unsupported_layouts {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(Bounded<u8, 0>)]
+    /// enum ZeroWidth { A = 0 }
+    /// ```
+    ///
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(Bounded<u8, 9>)]
+    /// enum TooWide { A, B }
+    /// ```
+    mod invalid_bounded_widths {}
+
+    /// ```
+    /// use kernel::{macros::From, num::Bounded};
+    ///
+    /// macro_rules! check_byte {
+    ///     ($name:ident, $ty:ident, $min:expr) => {
+    ///         #[derive(From)]
+    ///         #[from($ty, Bounded<$ty, 8>)]
+    ///         #[repr($ty)]
+    ///         enum $name {
+    ///             V0 = $min,
+    ///             V1, V2, V3, V4, V5, V6, V7, V8,
+    ///             V9, V10, V11, V12, V13, V14, V15, V16,
+    ///             V17, V18, V19, V20, V21, V22, V23, V24,
+    ///             V25, V26, V27, V28, V29, V30, V31, V32,
+    ///             V33, V34, V35, V36, V37, V38, V39, V40,
+    ///             V41, V42, V43, V44, V45, V46, V47, V48,
+    ///             V49, V50, V51, V52, V53, V54, V55, V56,
+    ///             V57, V58, V59, V60, V61, V62, V63, V64,
+    ///             V65, V66, V67, V68, V69, V70, V71, V72,
+    ///             V73, V74, V75, V76, V77, V78, V79, V80,
+    ///             V81, V82, V83, V84, V85, V86, V87, V88,
+    ///             V89, V90, V91, V92, V93, V94, V95, V96,
+    ///             V97, V98, V99, V100, V101, V102, V103, V104,
+    ///             V105, V106, V107, V108, V109, V110, V111, V112,
+    ///             V113, V114, V115, V116, V117, V118, V119, V120,
+    ///             V121, V122, V123, V124, V125, V126, V127,
+    ///             V128 = (($min as i16) + 128) as $ty,
+    ///             V129, V130, V131, V132, V133, V134, V135, V136,
+    ///             V137, V138, V139, V140, V141, V142, V143, V144,
+    ///             V145, V146, V147, V148, V149, V150, V151, V152,
+    ///             V153, V154, V155, V156, V157, V158, V159, V160,
+    ///             V161, V162, V163, V164, V165, V166, V167, V168,
+    ///             V169, V170, V171, V172, V173, V174, V175, V176,
+    ///             V177, V178, V179, V180, V181, V182, V183, V184,
+    ///             V185, V186, V187, V188, V189, V190, V191, V192,
+    ///             V193, V194, V195, V196, V197, V198, V199, V200,
+    ///             V201, V202, V203, V204, V205, V206, V207, V208,
+    ///             V209, V210, V211, V212, V213, V214, V215, V216,
+    ///             V217, V218, V219, V220, V221, V222, V223, V224,
+    ///             V225, V226, V227, V228, V229, V230, V231, V232,
+    ///             V233, V234, V235, V236, V237, V238, V239, V240,
+    ///             V241, V242, V243, V244, V245, V246, V247, V248,
+    ///             V249, V250, V251, V252, V253, V254, V255,
+    ///         }
+    ///         for raw in <$ty>::MIN..=<$ty>::MAX {
+    ///             assert_eq!($name::from(raw) as $ty, raw);
+    ///             let bounded = Bounded::<$ty, 8>::try_new(raw).unwrap();
+    ///             assert_eq!($name::from(bounded) as $ty, raw);
+    ///         }
+    ///     };
+    /// }
+    /// check_byte!(Unsigned, u8, 0);
+    /// check_byte!(Signed, i8, -128);
+    /// ```
+    mod full_width_primitive_and_bounded {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(u16)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_0 {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(u32)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_1 {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(u64)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_2 {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(u128)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_3 {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(usize)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_4 {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(i16)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_5 {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(i32)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_6 {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(i64)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_7 {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(i128)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_8 {}
+
+    /// ```compile_fail,E0080
+    /// use kernel::macros::From;
+    ///
+    /// #[derive(From)]
+    /// #[from(isize)]
+    /// enum Incomplete { A, B }
+    /// ```
+    mod incomplete_wide_input_9 {}
+}


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

* [PATCH v6 09/10] rust: macros: validate and qualify conversion helper types
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
                   ` (7 preceding siblings ...)
  2026-09-29 13:58 ` [PATCH v6 08/10] rust: macros: test exhaustive conversion derives Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  2026-09-29 13:58 ` [PATCH v6 10/10] gpu: nova-core: use conversion derives for two register enums Kaiqi Guo
  9 siblings, 0 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo

Reject generic arguments attached to a primitive helper input.
Previously bool<Undefined> was silently treated as bool because only the
final identifier was validated.

Qualify Result in generated TryFrom signatures and the backing primitive
passed to Bounded::new. Local aliases named Result or u8 must not alter
the implementation after its input was validated as a primitive. Add
compile-pass and compile-fail regressions for each case.

Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 rust/macros/convert.rs | 52 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 50 insertions(+), 2 deletions(-)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index c23b8819572e..0fda475349ef 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -295,7 +295,7 @@ fn impl_try_from(
             #[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> {
+                fn try_from(#param: #input_ty) -> ::core::result::Result<#enum_ident, Self::Error> {
                     #overflow_assertion
 
                     #(#clauses)* {
@@ -492,7 +492,8 @@ impl Bounded {
     const QUALIFIED_NAME: &'static str = "::kernel::num::Bounded";
 
     fn emit_new(&self, expr: &Expr) -> TokenStream {
-        let Self { base_ty, bits, .. } = self;
+        let base_ty = self.emit_qualified_base_ty();
+        let bits = &self.bits;
         let qualified_name: syn::Path = parse_str(Self::QUALIFIED_NAME).expect("valid path");
         ::quote::quote! {
             #qualified_name::<#base_ty, #bits>::new::<{ #expr }>()
@@ -556,6 +557,8 @@ fn validate_type(ty: &Type) -> syn::Result<ValidTy> {
     let segment = &path.segments[0];
     if segment.ident == Bounded::NAME {
         return validate_bounded(segment);
+    } else if !matches!(segment.arguments, PathArguments::None) {
+        return Err(make_err(ty));
     } else {
         return validate_primitive(&segment.ident);
     }
@@ -2043,3 +2046,48 @@ mod incomplete_wide_input_8 {}
     /// ```
     mod incomplete_wide_input_9 {}
 }
+
+mod helper_type_tests {
+    /// ```compile_fail
+    /// use kernel::macros::Into;
+    /// #[derive(Into)]
+    /// #[into(bool<Undefined>)]
+    /// enum Invalid { A, B }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::TryFrom;
+    /// #[derive(TryFrom)]
+    /// #[try_from(bool<Undefined>)]
+    /// enum Invalid { A, B }
+    /// ```
+    ///
+    /// ```compile_fail
+    /// use kernel::macros::From;
+    /// #[derive(From)]
+    /// #[from(bool<Undefined>)]
+    /// enum Invalid { A, B }
+    /// ```
+    mod primitive_arguments_are_not_ignored {}
+
+    /// ```
+    /// #![allow(non_camel_case_types, dead_code)]
+    /// use kernel::{macros::{From, Into, TryFrom}, num::Bounded};
+    ///
+    /// type Result = ();
+    /// type u8 = i8;
+    ///
+    /// #[derive(Debug, PartialEq, From, Into, TryFrom)]
+    /// #[from(Bounded<u8, 1>)]
+    /// #[into(Bounded<u8, 1>)]
+    /// #[try_from(u8, Bounded<u8, 2>)]
+    /// enum Switch { Off, On }
+    ///
+    /// let value = Bounded::<core::primitive::u8, 1>::new::<1>();
+    /// assert_eq!(Switch::from(value), Switch::On);
+    /// assert_eq!(Bounded::<core::primitive::u8, 1>::from(Switch::On), value);
+    /// assert_eq!(Switch::try_from(0_u8), Ok(Switch::Off));
+    /// assert_eq!(Switch::try_from(value.extend::<2>()), Ok(Switch::On));
+    /// ```
+    mod names_in_scope_do_not_change_generated_types {}
+}


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

* [PATCH v6 10/10] gpu: nova-core: use conversion derives for two register enums
  2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
                   ` (8 preceding siblings ...)
  2026-09-29 13:58 ` [PATCH v6 09/10] rust: macros: validate and qualify conversion helper types Kaiqi Guo
@ 2026-09-29 13:58 ` Kaiqi Guo
  9 siblings, 0 replies; 12+ messages in thread
From: Kaiqi Guo @ 2026-09-29 13:58 UTC (permalink / raw)
  To: rust-for-linux, Miguel Ojeda
  Cc: Jesung Yang, Alexandre Courbot, Danilo Krummrich, nova-gpu,
	dri-devel, linux-kernel, Kaiqi Guo, Alice Ryhl, David Airlie,
	Simona Vetter

Replace bounded_enum! for Architecture with TryFrom and Into using the
shared convert helper. Keep the six discriminants, Bounded<u32, 6> input
and output, and Error/EINVAL on invalid inputs.

Use From and Into for FalconCoreRevSubversion, which covers every value
of Bounded<u32, 2>. Its existing infallible conversion and blanket
TryFrom error type remain unchanged. Leave other register enums and
Chipset conversion behavior in place.

This exercises both fallible and exhaustive conversions in actual
register field users without changing register accesses or GPU
initialization logic.

Link: https://lore.kernel.org/rust-for-linux/DHHJCEG8BC47.2VC6GLDRRZH1B@nvidia.com/
Signed-off-by: Kaiqi Guo <chaoji_xinren@163.com>
---
 drivers/gpu/nova-core/falcon.rs | 19 +++++++++----------
 drivers/gpu/nova-core/gpu.rs    | 22 ++++++++++------------
 2 files changed, 19 insertions(+), 22 deletions(-)

diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 65cb12d26e2b..15bbbda7df28 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -59,16 +59,15 @@ pub(crate) enum FalconCoreRev with TryFrom<Bounded<u32, 4>> {
     }
 }
 
-bounded_enum! {
-    /// Revision subversion number of a falcon core, used in the
-    /// [`crate::regs::NV_PFALCON_FALCON_HWCFG1`] register.
-    #[derive(Debug, Copy, Clone)]
-    pub(crate) enum FalconCoreRevSubversion with From<Bounded<u32, 2>> {
-        Subversion0 = 0,
-        Subversion1 = 1,
-        Subversion2 = 2,
-        Subversion3 = 3,
-    }
+/// Revision subversion number of a falcon core, used in the
+/// [`crate::regs::NV_PFALCON_FALCON_HWCFG1`] register.
+#[derive(Debug, Copy, Clone, kernel::macros::From, kernel::macros::Into)]
+#[convert(Bounded<u32, 2>)]
+pub(crate) enum FalconCoreRevSubversion {
+    Subversion0 = 0,
+    Subversion1 = 1,
+    Subversion2 = 2,
+    Subversion3 = 3,
 }
 
 bounded_enum! {
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index fd1414004dd0..66249bb5b369 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -14,7 +14,6 @@
 };
 
 use crate::{
-    bounded_enum,
     driver::Bar0,
     falcon::{
         gsp::Gsp as GspFalcon,
@@ -155,17 +154,16 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     }
 }
 
-bounded_enum! {
-    /// Enum representation of the GPU generation.
-    #[derive(fmt::Debug, Copy, Clone)]
-    pub(crate) enum Architecture with TryFrom<Bounded<u32, 6>> {
-        Turing = 0x16,
-        Ampere = 0x17,
-        Hopper = 0x18,
-        Ada = 0x19,
-        BlackwellGB10x = 0x1a,
-        BlackwellGB20x = 0x1b,
-    }
+/// Enum representation of the GPU generation.
+#[derive(fmt::Debug, Copy, Clone, kernel::macros::TryFrom, kernel::macros::Into)]
+#[convert(Bounded<u32, 6>)]
+pub(crate) enum Architecture {
+    Turing = 0x16,
+    Ampere = 0x17,
+    Hopper = 0x18,
+    Ada = 0x19,
+    BlackwellGB10x = 0x1a,
+    BlackwellGB20x = 0x1b,
 }
 
 #[derive(Clone, Copy)]


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

* Re: [PATCH v6 06/10] rust: macros: exercise the intended conversion doctest failures
  2026-09-29 13:58 ` [PATCH v6 06/10] rust: macros: exercise the intended conversion doctest failures Kaiqi Guo
@ 2026-09-29 18:47   ` Miguel Ojeda
  0 siblings, 0 replies; 12+ messages in thread
From: Miguel Ojeda @ 2026-09-29 18:47 UTC (permalink / raw)
  To: Kaiqi Guo
  Cc: rust-for-linux, Miguel Ojeda, Jesung Yang, Alexandre Courbot,
	Danilo Krummrich, nova-gpu, dri-devel, linux-kernel,
	Charalampos Mitrodimas

On Tue, Sep 29, 2026 at 3:59 PM Kaiqi Guo <chaoji_xinren@163.com> wrote:
>
> Suggested-by: Charalampos Mitrodimas <charmitro@posteo.net>
>
> Link: https://lore.kernel.org/rust-for-linux/m2qzr81jrd.fsf@ip-192-168-1-196.ap-southeast-1.compute.internal/

I have been seeing more and more lately these newlines between tags --
is there perhaps a tutorial out there mentioning to do this or a
script that ends up splitting them or similar?

It is especially strange in cases like this one, because the Link is
for the Suggested-by, no? So logically they should be tied together
(if the kernel allowed newlines to group tags to begin with).

By the way, since I am here: it sounds like this could be split into
different patches, although it is not a big deal.

Thanks!

Cheers,
Miguel

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

end of thread, other threads:[~2026-09-29 18:48 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-29 13:58 [PATCH v6 00/10] rust: add conversion derives and exhaustive From support Kaiqi Guo
2026-09-29 13:58 ` [PATCH v6 01/10] rust: macros: add derive macro for `Into` Kaiqi Guo
2026-09-29 13:58 ` [PATCH v6 02/10] rust: macros: add derive macro for `TryFrom` Kaiqi Guo
2026-09-29 13:58 ` [PATCH v6 03/10] rust: macros: add `convert` helper attribute Kaiqi Guo
2026-09-29 13:58 ` [PATCH v6 04/10] rust: macros: add private doctests for `Into` derive macro Kaiqi Guo
2026-09-29 13:58 ` [PATCH v6 05/10] rust: macros: add private doctests for `TryFrom` " Kaiqi Guo
2026-09-29 13:58 ` [PATCH v6 06/10] rust: macros: exercise the intended conversion doctest failures Kaiqi Guo
2026-09-29 18:47   ` Miguel Ojeda
2026-09-29 13:58 ` [PATCH v6 07/10] rust: macros: derive From for exhaustive enum conversions Kaiqi Guo
2026-09-29 13:58 ` [PATCH v6 08/10] rust: macros: test exhaustive conversion derives Kaiqi Guo
2026-09-29 13:58 ` [PATCH v6 09/10] rust: macros: validate and qualify conversion helper types Kaiqi Guo
2026-09-29 13:58 ` [PATCH v6 10/10] gpu: nova-core: use conversion derives for two register enums Kaiqi Guo

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