From nobody Sat Feb 7 10:15:18 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9756A2F0C48; Thu, 25 Dec 2025 08:37:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766651875; cv=none; b=XyIJET3AI6DdVV93iBJ2U9iAsZs8U3YpxeWXhdCS1YpLJvgxEvymVnRcsBFufYQo+nKBs5/0QCwk29yeteyn5NKHfN21OYc5MRYNwrgS5giM/EiRrntJrZgRUAGvZILk/ens4cUujDBEo91JU3WL0DfG88L4RRdpzbfm1xfAvfk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766651875; c=relaxed/simple; bh=/uKl1PFp+5/UbX3th8IeCZMAt9/LNq8Ebt1s0AOGn6I=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=EWV+GGHZIB31OYYJJ7wSdWwXZElwKJ+5mZoNBM+Li6uBu/QaQdsZnIVxnueor0iw7D8Re9FkM8bswkYhX9niyHlJ/JGTdJyFJnwREyiuXMkDOoBOvyi30UtylAY4zc6RFj4vcMzmANStnGyy2ZZiDKTRMYdjXu4/1N8KhLFXLE4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=u7K7YT1f; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="u7K7YT1f" Received: by smtp.kernel.org (Postfix) with ESMTPS id 3859AC16AAE; Thu, 25 Dec 2025 08:37:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1766651875; bh=/uKl1PFp+5/UbX3th8IeCZMAt9/LNq8Ebt1s0AOGn6I=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=u7K7YT1fLjoQJ66NL1+awwatHkrbRwEiyN3ve7dozucfeBZAkCXR289dwu91hugkx xWtXMplFYqPiDKRV8D4Pn5hvhDutyUPlG9yAzpxXT3SRk/yiXX+m/DefbRcIwHExsk HbVG4ZvmuWWPIBSdIYhxKvcccSHG6prkyKlY+4FqTvp+7rsfhhQ9cbBvZXLPxPZO7b VIbLS86pqRnrjvopBf6C8zDk9GaETsQHfoDYhZjpq5H27Ilwr98M3hp2inBuO/8RB6 ohPA7taWT9VQ0Si978UhdTjp2crolhkJOVRY2uKES3pbD8KlIOERVcAj/dG23xjnG8 O7St0f7gqBLiw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2D464E784A6; Thu, 25 Dec 2025 08:37:55 +0000 (UTC) From: Jesung Yang via B4 Relay Date: Thu, 25 Dec 2025 08:37:48 +0000 Subject: [PATCH v4 2/4] rust: macros: add derive macro for `TryFrom` Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20251225-try-from-into-macro-v4-2-4a563d597836@gmail.com> References: <20251225-try-from-into-macro-v4-0-4a563d597836@gmail.com> In-Reply-To: <20251225-try-from-into-macro-v4-0-4a563d597836@gmail.com> To: Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Alexandre Courbot Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, nouveau@lists.freedesktop.org, Jesung Yang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1766651873; l=10323; i=y.j3ms.n@gmail.com; s=20251225; h=from:subject:message-id; bh=r7sp35OD69C2rrD2B8/6PQ+AdErp08gK/fQ9WuVo77U=; b=OATMTspRpfvOCzDCQGHDIpegplqChFKkXHkFhcvjfPp9VsBz+K727vqzgsBgkqvenTcUvdCZK Gi1+XU4A7E1DNyIEGYufy+cQPVwsbxxqA+zoXh2RXT9YIszmOqz9ZtF X-Developer-Key: i=y.j3ms.n@gmail.com; a=ed25519; pk=2yVgO1I+y7kkFSF2Dc/Dckj4L2FgRnvmERHFt4bspbI= X-Endpoint-Received: by B4 Relay for y.j3ms.n@gmail.com/20251225 with auth_id=586 X-Original-From: Jesung Yang Reply-To: y.j3ms.n@gmail.com From: Jesung Yang Introduce a procedural macro `TryFrom` to automatically implement the `TryFrom` trait for unit-only enums. This reduces boilerplate in cases where numeric values need to be interpreted as relevant enum variants. This situation often 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 --- rust/macros/convert.rs | 64 +++++++++++++++++++ rust/macros/lib.rs | 162 +++++++++++++++++++++++++++++++++++++++++++++= ++++ 2 files changed, 226 insertions(+) diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs index 3e623cc894bff279482dd9daeaa7054937357ec6..d49a58e85de16d13ce9a51cafa3= 1940e42c5840f 100644 --- a/rust/macros/convert.rs +++ b/rust/macros/convert.rs @@ -30,6 +30,10 @@ pub(crate) fn derive_into(input: DeriveInput) -> syn::Re= sult { derive(DeriveTarget::Into, input) } =20 +pub(crate) fn derive_try_from(input: DeriveInput) -> syn::Result { + derive(DeriveTarget::TryFrom, input) +} + fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result { let mut errors: Option =3D None; let mut combine_error =3D |err| match errors.as_mut() { @@ -108,18 +112,21 @@ fn derive(target: DeriveTarget, input: DeriveInput) -= > syn::Result #[derive(Clone, Copy, Debug)] enum DeriveTarget { Into, + TryFrom, } =20 impl DeriveTarget { fn get_trait_name(&self) -> &'static str { match self { Self::Into =3D> "Into", + Self::TryFrom =3D> "TryFrom", } } =20 fn get_helper_name(&self) -> &'static str { match self { Self::Into =3D> "into", + Self::TryFrom =3D> "try_from", } } } @@ -184,6 +191,7 @@ fn derive_for_enum( ) -> TokenStream { let impl_fn =3D match target { DeriveTarget::Into =3D> impl_into, + DeriveTarget::TryFrom =3D> impl_try_from, }; =20 let qualified_repr_ty: syn::Path =3D parse_quote! { ::core::primitive:= :#repr_ty }; @@ -237,6 +245,54 @@ fn from(#param: #enum_ident) -> #input_ty { } } =20 + fn impl_try_from( + enum_ident: &Ident, + variants: &[Ident], + repr_ty: &syn::Path, + input_ty: &ValidTy, + ) -> TokenStream { + let param =3D Ident::new("value", Span::call_site()); + + let overflow_assertion =3D emit_overflow_assert(enum_ident, varian= ts, repr_ty, input_ty); + let emit_cast =3D |variant| { + let variant =3D ::quote::quote! { #enum_ident::#variant }; + match input_ty { + ValidTy::Bounded(inner) =3D> { + let base_ty =3D inner.emit_qualified_base_ty(); + let expr =3D parse_quote! { #variant as #base_ty }; + inner.emit_new(&expr) + } + ValidTy::Primitive(ident) if ident =3D=3D "bool" =3D> { + ::quote::quote! { ((#variant as #repr_ty) =3D=3D 1) } + } + qualified @ ValidTy::Primitive(_) =3D> ::quote::quote! { #= variant as #qualified }, + } + }; + + let clauses =3D variants.iter().map(|variant| { + let cast =3D emit_cast(variant); + ::quote::quote! { + if #param =3D=3D #cast { + ::core::result::Result::Ok(#enum_ident::#variant) + } else + } + }); + + ::quote::quote! { + #[automatically_derived] + impl ::core::convert::TryFrom<#input_ty> for #enum_ident { + type Error =3D ::kernel::prelude::Error; + fn try_from(#param: #input_ty) -> Result<#enum_ident, Self= ::Error> { + #overflow_assertion + + #(#clauses)* { + ::core::result::Result::Err(::kernel::prelude::EIN= VAL) + } + } + } + } + } + fn emit_overflow_assert( enum_ident: &Ident, variants: &[Ident], @@ -389,6 +445,14 @@ fn emit_from_expr(&self, expr: &Expr) -> TokenStream { } } =20 + fn emit_new(&self, expr: &Expr) -> TokenStream { + let Self { base_ty, bits, .. } =3D self; + let qualified_name: syn::Path =3D 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 =3D &self.base_ty; ::quote::quote! { ::core::primitive::#base_ty } diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 02528d7212b75d28788f0c33479edb272fa12e27..4dc7de0167a53b778562e24cd14= 5cece50555d91 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -632,3 +632,165 @@ 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`] tra= it. +/// +/// This macro automatically derives the [`TryFrom`] trait for a given enu= m. Currently, +/// it only supports [unit-only enum]s. +/// +/// [unit-only enum]: https://doc.rust-lang.org/reference/items/enumeratio= ns.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 var= iant. +/// (where `EINVAL` is from [`kernel::error::code`]). +/// +/// - The macro uses the `try_from` custom attribute or `repr` attribute t= o generate +/// [`TryFrom`] implementations. `try_from` always takes precedence over= `repr`. +/// +/// - The macro generates a compile-time assertion for every variant to en= sure its +/// discriminant value fits within the type being converted from. +/// +/// [`kernel::error::code`]: ../kernel/error/code/index.html +/// +/// # 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 discrimi= nant type][repr-rust], +/// the macro implements `TryFrom`: +/// +/// [repr-rust]: https://doc.rust-lang.org/reference/items/enumerations.ht= ml#r-items.enum.discriminant.repr-rust +/// +/// ```rust +/// # use kernel::prelude::*; +/// use kernel::macros::TryFrom; +/// +/// #[derive(Debug, Default, PartialEq, TryFrom)] +/// enum Foo { +/// #[default] +/// A, +/// B =3D 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`: +/// +/// ```rust +/// # use kernel::prelude::*; +/// use kernel::macros::TryFrom; +/// +/// #[derive(Debug, Default, PartialEq, TryFrom)] +/// #[repr(u8)] +/// enum Foo { +/// #[default] +/// A, +/// B =3D 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` for each `T` specified in `#[try_fro= m(...)]`, +/// which always overrides `#[repr(...)]`: +/// +/// ```rust +/// # use kernel::prelude::*; +/// use kernel::{ +/// macros::TryFrom, +/// num::Bounded, // +/// }; +/// +/// #[derive(Debug, Default, PartialEq, TryFrom)] +/// #[try_from(bool, i16, Bounded)] +/// #[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::::new::<0>())); +/// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::::new::<1>())); +/// ``` +/// +/// ## 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 =3D 256, +/// } +/// ``` +/// +/// ```compile_fail +/// # use kernel::macros::Into; +/// #[derive(Into)] +/// #[into(u8)] +/// enum Foo { +/// // `-1` cannot be represented with `u8`. +/// A =3D -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); +/// ``` +#[proc_macro_derive(TryFrom, attributes(try_from))] +pub fn derive_try_from(input: TokenStream) -> TokenStream { + let input =3D parse_macro_input!(input as DeriveInput); + convert::derive_try_from(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} --=20 2.47.3