From nobody Sun Feb 8 00:34:51 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 151EA3770B; Thu, 29 Jan 2026 14:32:48 +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=1769697168; cv=none; b=JZoJ5FjyyuXrbV5jnkeHtZKXEivi8BcU6rlMxPwR9O8S4UU4WwBjcJu5attF4d255iesaMgzGo5KgeFB1z3VqZGQD6oDfm0tC5gcSUtkhqd8KXvFHY2dNoEJG/oum6WSPEH29ZitFIs+k7iCAQuz4CmDXG+4oJhVCDWY0K1pB8g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769697168; c=relaxed/simple; bh=mbXTFja8+DxV4klQIc4NdQlfUSgrh/zwUK5PbaGMJ9A=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=XznU+rfBjaUvil34fKv24HMNFovX9d7trQXG/v8H92PimngMuqhjbO6oI+qJMi1BdoWvWKVorRP3gr6T9IJGVMY2rIlou2r3d131XrVotleWiEN7O3B41KMaJuToJGxMwkhXji5u2L6gnL+ILVS9f76MQyhYtq5MJJBHZJBcong= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=PAPrJ60e; 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="PAPrJ60e" Received: by smtp.kernel.org (Postfix) with ESMTPS id E8A17C19422; Thu, 29 Jan 2026 14:32:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1769697168; bh=mbXTFja8+DxV4klQIc4NdQlfUSgrh/zwUK5PbaGMJ9A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=PAPrJ60eB8bo4F5SLNjcFMgsZMAiIRtHZYRqiBPTr6qZkzv7ShWzR9aeW+9UItUqo YRwIaSQKQrOeQ+Eba4Limxk0bngUW9nyDc/GCpMMfGoRy8WgFXM8tpPWBz/S9YXOqL ZpLRoznmde7chZLpKA/r90KWl4M0Vr/uvY7JGFZvQWDAFlZuZgP+7mVVLlCw038X8N UpVpQoButkrc1FsoKvfFOGqz2iyo8wsOgN84VrORMyYkKspvfWaUV/5RI4FyqgKpwf vpZIekqwS+qka/lnEAy39erVdHP3FMoaSP++6mUPLJFQiORTDYNDCsUDhAsGOm5/u0 EtkpSZHlMNeZA== 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 D7FF2D6101E; Thu, 29 Jan 2026 14:32:47 +0000 (UTC) From: Jesung Yang via B4 Relay Date: Thu, 29 Jan 2026 23:32:45 +0900 Subject: [PATCH v5 1/4] rust: macros: add derive macro for `Into` 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: <20260129-try-from-into-macro-v5-1-dd011008118c@gmail.com> References: <20260129-try-from-into-macro-v5-0-dd011008118c@gmail.com> In-Reply-To: <20260129-try-from-into-macro-v5-0-dd011008118c@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.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=24349; i=y.j3ms.n@gmail.com; h=from:subject:message-id; bh=fqd8zOaitcBRxKU24wAifnhgZPjcG5QtkTIKJmcPkkg=; b=owJ4nAFtApL9kA0DAAoBhnBYoOPQ2LoByyZiAGl7b45zTZmAB6KJu94Bc57Sr4Fe3d9388dbF SH8SKpWPPmVGYkCMwQAAQoAHRYhBNJuBqTTLsbEgOaQ0IZwWKDj0Ni6BQJpe2+OAAoJEIZwWKDj 0Ni6v3UQALmtw/64MU3340vnFxo84DcQUimTss1gOBHzSQeDPVtD/YH7wR42KvMeeXdlSGejAvc ZV/sZg8uAMf/9zPWr7ETznmYWrt39dCLmbOWBgCvJYXB6Gz4KMkacokd2vANPyFTjlYfndA6iBD TeCoW3awhaycfx8RidKLvXsDikxEm4oX+gAdurrMki4nq4OZ/3cXVWB0m8tH+amAuqmxtKBJGgn zizPPyGPmsKWLhqKUjo0FCpsDEKoKjcosQxJSf60QiNXuS3P2uuh/QX+CVXvYBw48a00XVv68UH kHmRJxeUDDRRwfRvENxO6VHWFVieCYXYjMCjd1YBj7aTUCWDJUocKr0IyheOrZxfnk2hziLK7XD mpIdspnaQiYVIIi0Jo24Dw/J82sC5X1cpAsBTGW5+LIWoxcBY4yce2Gi8cpy+G55wdQ67AXz+eS Y9dITs4A5w66WMr6uaaGb4TQQja79sMqwGaPzVpDVW7AlcyuaqQ7e300/nwzqKpFnI2HXkpUGTs 56TYzapLwuYKk1bpHkFkDn+Zxulz2gQaRVHn6XBhP/fbVJ2VLXpBpCFqLCbY4UIWLaqnKY6nlVQ tbbSqDoNnM67TgHTYaAKgtFOCSCzRBUSSqrbQJTTeV8VdnW9CgHiFlwtrm8eF+P0Bjc/EQ4wWZf +N6+V5HICbcjzQBDbt3cqAA== X-Developer-Key: i=y.j3ms.n@gmail.com; a=openpgp; fpr=D26E06A4D32EC6C480E690D0867058A0E3D0D8BA X-Endpoint-Received: by B4 Relay for y.j3ms.n@gmail.com/default with auth_id=602 X-Original-From: Jesung Yang Reply-To: y.j3ms.n@gmail.com From: Jesung Yang Introduce a procedural macro `Into` to automatically implement the `Into` trait for unit-only enums. This reduces boilerplate in cases where enum variants need to be interpreted as relevant numeric values. A concrete example can be found in nova-core, where the `register!()` macro requires enum types used within it to be convertible via `u32::from()` [1]. 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 for T` implementations, where `E` is an enum identifier and `T` is an arbitrary integer type. This automatically provides the corresponding `Into for E` implementations through the blanket implementation. Link: https://lore.kernel.org/rust-for-linux/20250624132337.2242-1-dakr@ker= nel.org/ [1] Signed-off-by: Jesung Yang Tested-by: Shivam Kalra --- rust/macros/convert.rs | 520 +++++++++++++++++++++++++++++++++++++++++++++= ++++ rust/macros/lib.rs | 173 +++++++++++++++- 2 files changed, 692 insertions(+), 1 deletion(-) diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs new file mode 100644 index 000000000000..096e3c9fdc1b --- /dev/null +++ b/rust/macros/convert.rs @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0 + +use proc_macro2::{ + Span, + TokenStream, // +}; + +use std::fmt; + +use syn::{ + parse_quote, + parse_str, + punctuated::Punctuated, + spanned::Spanned, + AngleBracketedGenericArguments, + Attribute, + Data, + DeriveInput, + Expr, + ExprLit, + Fields, + GenericArgument, + Ident, + Lit, + LitInt, + PathArguments, + PathSegment, + Token, + Type, + TypePath, // +}; + +pub(crate) fn derive_into(input: DeriveInput) -> syn::Result { + derive(DeriveTarget::Into, input) +} + +fn derive(target: DeriveTarget, input: DeriveInput) -> syn::Result { + let data_enum =3D match input.data { + Data::Enum(data) =3D> data, + Data::Struct(data) =3D> { + let msg =3D 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) =3D> { + let msg =3D 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 =3D None; + let mut combine_error =3D |err| match errors.as_mut() { + Some(errors) =3D> errors.combine(err), + None =3D> errors =3D Some(err), + }; + + let (helper_tys, is_repr_c, repr_ty) =3D parse_attrs(target, &input.at= trs)?; + + let mut valid_helper_tys =3D Vec::with_capacity(helper_tys.len()); + for ty in helper_tys { + match validate_type(&ty) { + Ok(valid_ty) =3D> valid_helper_tys.push(valid_ty), + Err(err) =3D> combine_error(err), + } + } + + let mut is_unit_only =3D true; + for variant in &data_enum.variants { + match &variant.fields { + Fields::Unit =3D> continue, + Fields::Named(_) =3D> { + let msg =3D format!( + "expected unit-like variant, found struct-like variant= ; \ + `#[derive({})]` can only be applied to a unit-only enu= m", + target.get_trait_name(), + ); + combine_error(syn::Error::new_spanned(variant, msg)); + } + Fields::Unnamed(_) =3D> { + let msg =3D format!( + "expected unit-like variant, found tuple-like variant;= \ + `#[derive({})]` can only be applied to a unit-only enu= m", + target.get_trait_name(), + ); + combine_error(syn::Error::new_spanned(variant, msg)); + } + } + + is_unit_only =3D false; + } + + if is_repr_c && is_unit_only && repr_ty.is_none() { + let msg =3D "`#[repr(C)]` fieldless enums are not supported"; + return Err(syn::Error::new(input.ident.span(), msg)); + } + + if let Some(errors) =3D errors { + return Err(errors); + } + + let variants: Vec<_> =3D 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 =3D repr_ty.unwrap_or_else(|| Ident::new("isize", Span::ca= ll_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 =3D> "Into", + } + } + + fn get_helper_name(&self) -> &'static str { + match self { + Self::Into =3D> "into", + } + } +} + +fn parse_attrs( + target: DeriveTarget, + attrs: &[Attribute], +) -> syn::Result<(Vec, bool, Option)> { + let helper =3D target.get_helper_name(); + + let mut is_repr_c =3D false; + let mut repr_ty =3D None; + let mut helper_tys =3D Vec::new(); + for attr in attrs { + if attr.path().is_ident("repr") { + attr.parse_nested_meta(|meta| { + let ident =3D meta.path.get_ident(); + if let Some(i) =3D ident { + if is_valid_primitive(i) { + repr_ty =3D ident.cloned(); + } else if i =3D=3D "C" { + is_repr_c =3D true; + } + } + // Delegate `repr` attribute validation to rustc. + Ok(()) + })?; + } else if attr.path().is_ident(helper) && helper_tys.is_empty() { + let args =3D attr.parse_args_with(Punctuated::::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, +) -> TokenStream { + let impl_fn =3D match target { + DeriveTarget::Into =3D> impl_into, + }; + + let qualified_repr_ty: syn::Path =3D parse_quote! { ::core::primitive:= :#repr_ty }; + + return if helper_tys.is_empty() { + let ty =3D ValidTy::Primitive(repr_ty); + let implementation =3D impl_fn(enum_ident, variants, &qualified_re= pr_ty, &ty); + ::quote::quote! { #implementation } + } else { + let impls =3D helper_tys + .into_iter() + .map(|ty| impl_fn(enum_ident, variants, &qualified_repr_ty, &t= y)); + ::quote::quote! { #(#impls)* } + }; + + fn impl_into( + 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 cast =3D match input_ty { + ValidTy::Bounded(inner) =3D> { + let base_ty =3D inner.emit_qualified_base_ty(); + let expr =3D parse_quote! { #param as #base_ty }; + // Since the discriminant of `#param`, an enum variant, is= determined + // at compile-time, we can rely on `Bounded::from_expr()`.= It requires + // the provided expression to be verifiable at compile-tim= e to avoid + // triggering a build error. + inner.emit_from_expr(&expr) + } + ValidTy::Primitive(ident) if ident =3D=3D "bool" =3D> { + ::quote::quote! { (#param as #repr_ty) =3D=3D 1 } + } + qualified @ ValidTy::Primitive(_) =3D> ::quote::quote! { #para= m 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 =3D parse_quote! { ::core::primitive= ::i128 }; + let qualified_u128: syn::Path =3D parse_quote! { ::core::primitive= ::u128 }; + + let input_min =3D input_ty.emit_min(); + let input_max =3D input_ty.emit_max(); + + let variant_fits =3D variants.iter().map(|variant| { + let msg =3D 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 _: () =3D { + const fn fits(d: #repr_ty) -> ::core::primitive::bool { + // For every integer type, its minimum value always fi= ts in `i128`. + let dst_min =3D #input_min; + // For every integer type, its maximum value always fi= ts in `u128`. + let dst_max =3D #input_max; + + #[allow(unused_comparisons)] + let is_src_signed =3D #repr_ty::MIN < 0; + #[allow(unused_comparisons)] + let is_dst_signed =3D 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) >=3D (dst_min as #qualified= _i128) + && (d as #qualified_i128) <=3D (dst_max as #qu= alified_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 `i12= 8::MAX`. + d >=3D 0 && (d as #qualified_u128) <=3D (dst_max a= s #qualified_u128) + } else { + // Casting from an unsigned value to `u128` does n= ot overflow since + // `u128` is the largest unsigned primitive intege= r type. + (d as #qualified_u128) <=3D (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) =3D> inner.emit_min(), + Self::Primitive(ident) if ident =3D=3D "bool" =3D> { + ::quote::quote! { 0 } + } + qualified @ Self::Primitive(_) =3D> ::quote::quote! { #qualifi= ed::MIN }, + } + } + + fn emit_max(&self) -> TokenStream { + match self { + Self::Bounded(inner) =3D> inner.emit_max(), + Self::Primitive(ident) if ident =3D=3D "bool" =3D> { + ::quote::quote! { 1 } + } + qualified @ Self::Primitive(_) =3D> ::quote::quote! { #qualifi= ed::MAX }, + } + } +} + +impl ::quote::ToTokens for ValidTy { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Bounded(inner) =3D> inner.to_tokens(tokens), + Self::Primitive(ident) =3D> { + let qualified_name: syn::Path =3D parse_quote! { ::core::p= rimitive::#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) =3D> inner.fmt(f), + Self::Primitive(ident) =3D> ident.fmt(f), + } + } +} + +struct Bounded { + base_ty: Ident, + bits: LitInt, +} + +impl Bounded { + const NAME: &'static str =3D "Bounded"; + const QUALIFIED_NAME: &'static str =3D "::kernel::num::Bounded"; + + fn emit_from_expr(&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>::from_expr(#expr) + } + } + + fn emit_qualified_base_ty(&self) -> TokenStream { + let base_ty =3D &self.base_ty; + ::quote::quote! { ::core::primitive::#base_ty } + } + + fn emit_min(&self) -> TokenStream { + let bits =3D &self.bits; + let base_ty =3D self.emit_qualified_base_ty(); + ::quote::quote! { #base_ty::MIN >> (#base_ty::BITS - #bits) } + } + + fn emit_max(&self) -> TokenStream { + let bits =3D &self.bits; + let base_ty =3D 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 =3D &self.bits; + let base_ty =3D self.emit_qualified_base_ty(); + let qualified_name: syn::Path =3D 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 { + let Type::Path(type_path) =3D ty else { + return Err(make_err(ty)); + }; + + let TypePath { qself, path } =3D type_path; + if qself.is_some() { + return Err(make_err(ty)); + } + + let syn::Path { + leading_colon, + segments, + } =3D path; + if leading_colon.is_some() || segments.len() !=3D 1 { + return Err(make_err(ty)); + } + + let segment =3D &path.segments[0]; + if segment.ident =3D=3D Bounded::NAME { + return validate_bounded(segment); + } else { + return validate_primitive(&segment.ident); + } + + fn make_err(ty: &Type) -> syn::Error { + let msg =3D format!( + "expected unqualified form of `bool`, primitive integer type, = or `{}`", + Bounded::NAME, + ); + syn::Error::new_spanned(ty, msg) + } +} + +fn validate_bounded(path_segment: &PathSegment) -> syn::Result { + let PathSegment { ident, arguments } =3D path_segment; + return match arguments { + PathArguments::AngleBracketed(inner) if ident =3D=3D Bounded::NAME= =3D> { + let AngleBracketedGenericArguments { + colon2_token, args, .. + } =3D inner; + + if colon2_token.is_some() { + return Err(make_outer_err(path_segment)); + } + + if args.len() !=3D 2 { + return Err(make_outer_err(path_segment)); + } + + let (base_ty, bits) =3D (&args[0], &args[1]); + let GenericArgument::Type(Type::Path(base_ty_lowered)) =3D bas= e_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) =3D base_ty_lowered.path.get_ident() e= lse { + 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), + .. + })) =3D bits + else { + return Err(syn::Error::new_spanned(bits, "expected integer= literal")); + }; + + let bounded =3D Bounded { + base_ty: base_ty_ident.clone(), + bits: bits.clone(), + }; + Ok(ValidTy::Bounded(bounded)) + } + _ =3D> Err(make_outer_err(path_segment)), + }; + + fn make_outer_err(path_segment: &PathSegment) -> syn::Error { + let msg =3D format!("expected `{0}` (e.g., {0})", Bou= nded::NAME); + syn::Error::new_spanned(path_segment, msg) + } + + fn make_base_ty_err(base_ty: &GenericArgument) -> syn::Error { + let msg =3D "expected unqualified form of primitive integer type"; + syn::Error::new_spanned(base_ty, msg) + } +} + +fn validate_primitive(ident: &Ident) -> syn::Result { + if is_valid_primitive(ident) { + return Ok(ValidTy::Primitive(ident.clone())); + } + let msg =3D + format!("expected `bool` or primitive integer type (e.g., `u8`, `i= 8`), found {ident}"); + Err(syn::Error::new(ident.span(), msg)) +} + +fn is_valid_primitive(ident: &Ident) -> bool { + matches!( + ident.to_string().as_str(), + "bool" + | "u8" + | "u16" + | "u32" + | "u64" + | "u128" + | "usize" + | "i8" + | "i16" + | "i32" + | "i64" + | "i128" + | "isize" + ) +} diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 85b7938c08e5..8842067d1017 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -12,6 +12,7 @@ #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))] =20 mod concat_idents; +mod convert; mod export; mod fmt; mod helpers; @@ -22,7 +23,10 @@ =20 use proc_macro::TokenStream; =20 -use syn::parse_macro_input; +use syn::{ + parse_macro_input, + DeriveInput, // +}; =20 /// Declares a kernel module. /// @@ -486,3 +490,170 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStr= eam) -> TokenStream { .unwrap_or_else(|e| e.into_compile_error()) .into() } + +/// A derive macro for providing an implementation of the [`Into`] trait. +/// +/// This macro automatically derives the [`Into`] trait for a given enum b= y generating +/// the relevant [`From`] implementation. Currently, it only supports [uni= t-only enum]s. +/// +/// [unit-only enum]: https://doc.rust-lang.org/reference/items/enumeratio= ns.html#r-items.enum.unit-only +/// +/// # Notes +/// +/// - Unlike its name suggests, the macro actually generates [`From`] impl= ementations +/// which automatically provide corresponding [`Into`] implementations. +/// +/// - The macro uses the `into` custom attribute or `repr` attribute to ge= nerate [`From`] +/// implementations. `into` always takes precedence over `repr`. +/// +/// - Currently, the macro does not support `repr(C)` fieldless enums sinc= e 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 en= sure 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 discrimi= nant type][repr-rust], +/// the macro implements `From` for `isize`: +/// +/// [repr-rust]: https://doc.rust-lang.org/reference/items/enumerations.ht= ml#r-items.enum.discriminant.repr-rust +/// +/// ``` +/// use kernel::macros::Into; +/// +/// #[derive(Debug, Default, Into)] +/// enum Foo { +/// #[default] +/// A, +/// B =3D 0x7, +/// } +/// +/// assert_eq!(0_isize, Foo::A.into()); +/// assert_eq!(0x7_isize, Foo::B.into()); +/// ``` +/// +/// ## With `#[repr(T)]` +/// +/// The macro implements `From` for `T`: +/// +/// ``` +/// use kernel::macros::Into; +/// +/// #[derive(Debug, Default, Into)] +/// #[repr(u8)] +/// enum Foo { +/// #[default] +/// A, +/// B =3D 0x7, +/// } +/// +/// assert_eq!(0_u8, Foo::A.into()); +/// assert_eq!(0x7_u8, Foo::B.into()); +/// ``` +/// +/// ## With `#[into(...)]` +/// +/// The macro implements `From` for each `T` specified in `#[into(...= )]`, +/// which always overrides `#[repr(...)]`: +/// +/// ``` +/// use kernel::{ +/// macros::Into, +/// num::Bounded, // +/// }; +/// +/// #[derive(Debug, Default, Into)] +/// #[into(bool, i16, Bounded)] +/// #[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 =3D Foo::A.into(); +/// let foo_b: Bounded =3D Foo::B.into(); +/// assert_eq!(Bounded::::new::<0>(), foo_a); +/// assert_eq!(Bounded::::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 =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::Into; +/// // Tuple-like enums or struct-like enums are not allowed. +/// #[derive(Into)] +/// enum Foo { +/// A(u8), +/// B { inner: u8 }, +/// } +/// ``` +/// +/// ```compile_fail +/// # use kernel::macros::Into; +/// // Structs are not allowed. +/// #[derive(Into)] +/// struct Foo(u8); +/// ``` +/// +/// ```compile_fail +/// # use kernel::macros::Into; +/// // `repr(C)` enums are not allowed. +/// #[derive(Into)] +/// struct Foo(u8); +/// ``` +#[proc_macro_derive(Into, attributes(into))] +pub fn derive_into(input: TokenStream) -> TokenStream { + let input =3D parse_macro_input!(input as DeriveInput); + convert::derive_into(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} --=20 2.52.0 From nobody Sun Feb 8 00:34:51 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 3B3AF2874F5; Thu, 29 Jan 2026 14:32:48 +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=1769697168; cv=none; b=pQG8Z0y0JxGV2CEmN4EN1tHZcldRxVIuZWVKrJ90kjEUd9ur12N4VxwFVB00P/HVeNqkkeXxczPTMNfBcsmCxdxgXhKKpfJ57JTd/6BgOhsRupDdxtGvGGKQJ+5/pGoC6KZrsg60VXtRxSc2Lpf52YOFd2dsDFrua28xxCSNGrY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769697168; c=relaxed/simple; bh=zsJKLouslNIMhVTvOdw5iCz2jJoutLn9tl1l10pprrw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=T4RDNhQglEG4GG0A1PofWPbIAmx8g+3LbfZ9ZLOOzc6GCO88f/ZjYYoDdDRjY2W2gLGa9FzLJGaPmS5k0+g0iEuU//LWDjTlMH8pjxwK5dUdMw3s0JTdabHnpsuoSd30GEfcNhindjKd5FGoSGJQzuJYkdlP0rMH/lzKXjf/Jys= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ijzwrdW/; 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="ijzwrdW/" Received: by smtp.kernel.org (Postfix) with ESMTPS id 021F3C4AF0B; Thu, 29 Jan 2026 14:32:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1769697168; bh=zsJKLouslNIMhVTvOdw5iCz2jJoutLn9tl1l10pprrw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ijzwrdW/sFAA6DIn8UJ6Y1yU5jLhaqOMk8LePZU0AtU3kAOsCZhy7Mrh4y+TlllAX 4MP8fCrGNombPay1ja6SwYH8mH8f8gMc+D/ZDpec9sujK+bZOftGfQWxofXtvRumzG rh6NbhBHSF7g5LwUVqv9XKwhd+JDqubMIziqpPuohXI1YyriscPNDIvuiJnwUGvr/x JS8WgJeGM6JPhSpCeoTLKnTkdqx4M7fvMPzZ3W6xcrxOLK2bbIm3ENt3FjXBtR/kFW 6XFQyV5o2wUfdtKGRj10juxheir7MBnhQA795806PLxwjeAjw7w4XHvVu71xV4agy1 WPI8+EhTjGawQ== 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 E7E0CD61020; Thu, 29 Jan 2026 14:32:47 +0000 (UTC) From: Jesung Yang via B4 Relay Date: Thu, 29 Jan 2026 23:32:46 +0900 Subject: [PATCH v5 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: <20260129-try-from-into-macro-v5-2-dd011008118c@gmail.com> References: <20260129-try-from-into-macro-v5-0-dd011008118c@gmail.com> In-Reply-To: <20260129-try-from-into-macro-v5-0-dd011008118c@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.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=10808; i=y.j3ms.n@gmail.com; h=from:subject:message-id; bh=o0ZOJ2u6fulpd6euQbCwZeiZqbQpSA+Y1ClbiBC4Hqs=; b=owJ4nAFtApL9kA0DAAoBhnBYoOPQ2LoByyZiAGl7b47aA5WH7vO27Eax/xs4+3OV8/xJrrBLu hHqXotpVMg9EIkCMwQAAQoAHRYhBNJuBqTTLsbEgOaQ0IZwWKDj0Ni6BQJpe2+OAAoJEIZwWKDj 0Ni6a3UP/3v9YccMDx0NI+IfLLnZQdKyC6kjqKkTFuVFtmyU6+abCyBRfivWFQYEKwv/C2PIOCu 5GWkMvGyD+iHz4VyhYgOd9JBoNL+KjQXI7uF5zQpzWFCA9zvt97gw76K94El2Kwl1SHAtoBsF4e oRHmUAMvwHwoc+Bvq9GIwiAuEcyafDMX8ktwYxUb9U61fqBPKZKpH7jPf2Rc4/bd3OMO/Yw55Pa ivdiSFO7wmXSI3r0OJdja66PWAiga0URV97XZEVTlJZkRgNScrbCxF5nARxrI1DAIddchIZUHb0 +UcaMrnW1s7GoBP0N4kHVPl95jVAgFZiBMTkrk/q0erR3suHOpYYmPkOcAN0wu5xidpfi5io66q TvKnfY/WmGvZGEwYA1zy6/X9XPQEd4U+F8GVsybDP7KebzrbbONMVyNeMw6m7fDTiVMKYrxBDXh Cd/k58ErBtHwAPJaVI4o8P12+diboiAPNUmlPVqEKugfktgftNS/krbpA0Yx1G6NjZD1L/EHD3J k/3ieugATWzJKLNajrchxDi44HIgYfS4TXc0wOUINjBE0kfYoJZSjuO7bB8QSaNxTg1+IHOQv51 YYmCwkQiaPHH4eCGX7Mwe1bM3SIrZFDJUh11uWRHLYo1MANAzrqky2dCCAc6HWvb5hxg2pIW4hs DSgBGXklSMMUG6xxt8fspUw== X-Developer-Key: i=y.j3ms.n@gmail.com; a=openpgp; fpr=D26E06A4D32EC6C480E690D0867058A0E3D0D8BA X-Endpoint-Received: by B4 Relay for y.j3ms.n@gmail.com/default with auth_id=602 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 Tested-by: Shivam Kalra --- rust/macros/convert.rs | 64 ++++++++++++++++++ rust/macros/lib.rs | 176 +++++++++++++++++++++++++++++++++++++++++++++= ++++ 2 files changed, 240 insertions(+) diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs index 096e3c9fdc1b..a7a43b1a2caf 100644 --- a/rust/macros/convert.rs +++ b/rust/macros/convert.rs @@ -34,6 +34,10 @@ pub(crate) fn derive_into(input: DeriveInput) -> syn::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 data_enum =3D match input.data { Data::Enum(data) =3D> data, @@ -129,18 +133,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", } } } @@ -186,6 +193,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 }; @@ -238,6 +246,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], @@ -360,6 +416,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 8842067d1017..893adecb9080 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -657,3 +657,179 @@ pub fn derive_into(input: TokenStream) -> TokenStream= { .unwrap_or_else(syn::Error::into_compile_error) .into() } + +/// A derive macro for generating an implementation of the [`TryFrom`] 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`. +/// +/// - Currently, the macro does not support `repr(C)` fieldless enums sinc= e 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 en= sure 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 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::TryFrom; +/// #[derive(TryFrom)] +/// #[try_from(u8)] +/// enum Foo { +/// // `256` is larger than `u8::MAX`. +/// A =3D 256, +/// } +/// ``` +/// +/// ```compile_fail +/// # use kernel::macros::TryFrom; +/// #[derive(TryFrom)] +/// #[try_from(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); +/// ``` +/// +/// ```compile_fail +/// # use kernel::macros::TryFrom; +/// // `repr(C)` enums are not allowed. +/// #[derive(TryFrom)] +/// struct Foo(u8) +/// ``` +#[proc_macro_derive(TryFrom, attributes(try_from))] +pub fn derive_try_from(input: TokenStream) -> TokenStream { + let input =3D parse_macro_input!(input as DeriveInput); + convert::derive_try_from(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} --=20 2.52.0 From nobody Sun Feb 8 00:34:51 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 486E3288CA3; Thu, 29 Jan 2026 14:32:48 +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=1769697168; cv=none; b=njWzowoWbdx7lYeQKIN50yV6OnTkVAWfglIn9ESs/32EvMJLldfkm6+uS4ASNz08h5w/Wprbb8Pzh+/BYmzySVJinBNbpq6mifdXE1hNMg2WAyJ5egYiyTtlAfB+rmuZoq11pSJublMCRXApUqcMnry6FF0oC2v8ICJEEMOhZhw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769697168; c=relaxed/simple; bh=h2db5JHcfX358xoYjzNWg69N/TJi6crM+c2XE9YuwFA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=q2PVr+JeaVKI0tag8SyP/KDW7N+4iQWwUlfgWPIdYmYaLhVW6mnVPHJfkjQbOno2VqHHPkHlPkKr5QktLt8ZHwZqU8MCbbGL0qMN2DYUJwdaS5zoVqRUBM9GIn0I09N/pIFFNd6dlSz6fxUKZOhzXrIJiB6L5V7VyaMwgoHqz3o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Z89Dnhc3; 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="Z89Dnhc3" Received: by smtp.kernel.org (Postfix) with ESMTPS id 0CCE8C2BC87; Thu, 29 Jan 2026 14:32:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1769697168; bh=h2db5JHcfX358xoYjzNWg69N/TJi6crM+c2XE9YuwFA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=Z89Dnhc3WkiXIxaMu+lpUw+nOS3B6WcdcON5PLk4rLfptRVme7pJZp2037SibNYJW of7FmWGo71CuBhFpmaDWcuKvPMRCcNlFp/weRPw58svW1LAqfppWZzpnB5jw6x1ga3 pUs2qTYEUPema9XZ3AkjJhCCwj6Z8lkRCMlkn5Q+gqtzlFk0Gcqn499WNdkAoBXgNq Ij2oHl78FVPXLROelg1ur4FZEtGoaYo3srksI+A9GcZq5seiwfW8lT/PbcFZg5IlDQ gr+Lg9vzrzR170x9RgOPYYMWRaS/7yzFIvX1D2nUfqiZRDkofkiXoLBmDeUrtJREkX 3vd9B+gEGHZjw== 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 03C6AD6101C; Thu, 29 Jan 2026 14:32:48 +0000 (UTC) From: Jesung Yang via B4 Relay Date: Thu, 29 Jan 2026 23:32:47 +0900 Subject: [PATCH v5 3/4] rust: macros: add private doctests for `Into` derive macro 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: <20260129-try-from-into-macro-v5-3-dd011008118c@gmail.com> References: <20260129-try-from-into-macro-v5-0-dd011008118c@gmail.com> In-Reply-To: <20260129-try-from-into-macro-v5-0-dd011008118c@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.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=12271; i=y.j3ms.n@gmail.com; h=from:subject:message-id; bh=c1q9CgkXNiNNpfbtgu2COPiP5pBsQJknnBTq9ePsn4g=; b=owJ4nAFtApL9kA0DAAoBhnBYoOPQ2LoByyZiAGl7b45XM/k3hZ+u/lh5G4jGp1ZkwgQR5FMYK T7No+6EXVYP0okCMwQAAQoAHRYhBNJuBqTTLsbEgOaQ0IZwWKDj0Ni6BQJpe2+OAAoJEIZwWKDj 0Ni6SyIP/1JbkqVr20g1d1JYWzXHPRY2PHXALyMzrRflJmxjM4wlIo8SPKdimlBJiirQp7igodp 3zvrRhqeuUN7UFsfS2tktKrnPJlpUFGrg3FSUnINdkDOdHBi5n5cmHq0vZ37LFgyd8K81Vil9Rg VXk7nGAz6UCZ1io4x28aSsU5temMphoTD4urkNl7Vmx6sNWRQAQTY3QsJJjyRueF8VCENQ0Xn5z GLwuXKjq0s5Ch+eGmGwUWd8e7KZYR7lA8OzPuZDIjZCTwAFnZGFT5U65YMasK0eBIChOYrGY8bE OaP0a/zwRqZ/8EHocqIuoWLDVTfzJZAbY/JJgqMv7G864/cpQlB74rf4oJVXxnulUL+qen0uSa9 1yigjc91geN1kszcIIcNDJ9Znt5YP2uQpP6s7TnntxgkY0fZIvcPRbT1mDAxBcUuGTK8KgQC4Rp J0R0+DYhcEVgWzUHdfjnEWpq3vF2mBl6FXvHraRRBdC/vg3RCQgZzE/SUk2INaUatp4c+y8gK+p rAkz0hSTbVJqNbrw/YLQe4FX+OT3a36MyQFY7psdN97txu/lojyS8WMKkpWqXACx7lQYP/OtXem ZYtG7VAi+HnWKs7bEhVH/DJgY2F8exqnY5o+3ByT3mb/cNRsXYA/0BXYOzW42KkZwE7cYagxYWY uC1ZUw/deWjuGekgggY4i9g== X-Developer-Key: i=y.j3ms.n@gmail.com; a=openpgp; fpr=D26E06A4D32EC6C480E690D0867058A0E3D0D8BA X-Endpoint-Received: by B4 Relay for y.j3ms.n@gmail.com/default with auth_id=602 X-Original-From: Jesung Yang Reply-To: y.j3ms.n@gmail.com From: Jesung Yang 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 Tested-by: Shivam Kalra --- rust/macros/convert.rs | 436 +++++++++++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 436 insertions(+) diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs index a7a43b1a2caf..41ccbd849348 100644 --- a/rust/macros/convert.rs +++ b/rust/macros/convert.rs @@ -582,3 +582,439 @@ fn is_valid_primitive(ident: &Ident) -> bool { | "isize" ) } + +mod derive_into_tests { + /// ``` + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(u8)] + /// enum Foo { + /// // Works with const expressions. + /// A =3D add(0, 0), + /// B =3D 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 =3D -1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(bool)] + /// enum Foo { + /// // `2` cannot be represented with `bool`. + /// A =3D 2, + /// } + /// ``` + mod overflow_assert_works_on_bool {} + + /// ``` + /// use kernel::{ + /// macros::Into, + /// num::Bounded, // + /// }; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// A =3D -1 << 6, // The minimum value of `Bounded`. + /// B =3D (1 << 6) - 1, // The maximum value of `Bounded`. + /// } + /// + /// let foo_a: Bounded =3D Foo::A.into(); + /// let foo_b: Bounded =3D Foo::B.into(); + /// assert_eq!(Bounded::::new::<{ -1_i8 << 6 }>(), foo_a); + /// assert_eq!(Bounded::::new::<{ (1_i8 << 6) - 1 }>(), foo_b); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// // `1 << 6` cannot be represented with `Bounded`. + /// A =3D 1 << 6, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// // `(-1 << 6) - 1` cannot be represented with `Bounded`. + /// A =3D (-1 << 6) - 1, + /// } + /// ``` + /// + /// ``` + /// use kernel::{ + /// macros::Into, + /// num::Bounded, // + /// }; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// A =3D -1, // The minimum value of `Bounded`. + /// B, // The maximum value of `Bounded`. + /// } + /// + /// let foo_a: Bounded =3D Foo::A.into(); + /// let foo_b: Bounded =3D Foo::B.into(); + /// assert_eq!(Bounded::::new::<{ -1_i8 }>(), foo_a); + /// assert_eq!(Bounded::::new::<{ 0_i8 } >(), foo_b); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// // `1` cannot be represented with `Bounded`. + /// A =3D 1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// // `-2` cannot be represented with `Bounded`. + /// A =3D -2, + /// } + /// ``` + /// + /// ``` + /// use kernel::{ + /// macros::Into, + /// num::Bounded, // + /// }; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D i32::MIN as i64, + /// B =3D i32::MAX as i64, + /// } + /// + /// let foo_a: Bounded =3D Foo::A.into(); + /// let foo_b: Bounded =3D Foo::B.into(); + /// assert_eq!(Bounded::::new::<{ i32::MIN }>(), foo_a); + /// assert_eq!(Bounded::::new::<{ i32::MAX }>(), foo_b); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// #[repr(i64)] + /// enum Foo { + /// // `1 << 31` cannot be represented with `Bounded`. + /// A =3D 1 << 31, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// #[repr(i64)] + /// enum Foo { + /// // `(-1 << 31) - 1` cannot be represented with `Bounded`. + /// A =3D (-1 << 31) - 1, + /// } + /// ``` + mod overflow_assert_works_on_signed_bounded {} + + /// ``` + /// use kernel::{ + /// macros::Into, + /// num::Bounded, // + /// }; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// A, // The minimum value of `Bounded`. + /// B =3D (1 << 7) - 1, // The maximum value of `Bounded`. + /// } + /// + /// let foo_a: Bounded =3D Foo::A.into(); + /// let foo_b: Bounded =3D Foo::B.into(); + /// assert_eq!(Bounded::::new::<{ 0 }>(), foo_a); + /// assert_eq!(Bounded::::new::<{ (1_u8 << 7) - 1 }>(), foo_b); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// // `1 << 7` cannot be represented with `Bounded`. + /// A =3D 1 << 7, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// // `-1` cannot be represented with `Bounded`. + /// A =3D -1, + /// } + /// ``` + /// + /// ``` + /// use kernel::{ + /// macros::Into, + /// num::Bounded, // + /// }; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// A, // The minimum value of `Bounded`. + /// B, // The maximum value of `Bounded`. + /// } + /// + /// let foo_a: Bounded =3D Foo::A.into(); + /// let foo_b: Bounded =3D Foo::B.into(); + /// assert_eq!(Bounded::::new::<{ 0 }>(), foo_a); + /// assert_eq!(Bounded::::new::<{ 1 }>(), foo_b); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// // `2` cannot be represented with `Bounded`. + /// A =3D 2, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// enum Foo { + /// // `-1` cannot be represented with `Bounded`. + /// A =3D -1, + /// } + /// ``` + /// + /// ``` + /// use kernel::{ + /// macros::Into, + /// num::Bounded, // + /// }; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// #[repr(u64)] + /// enum Foo { + /// A =3D u32::MIN as u64, + /// B =3D u32::MAX as u64, + /// } + /// + /// let foo_a: Bounded =3D Foo::A.into(); + /// let foo_b: Bounded =3D Foo::B.into(); + /// assert_eq!(Bounded::::new::<{ u32::MIN }>(), foo_a); + /// assert_eq!(Bounded::::new::<{ u32::MAX }>(), foo_b); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// #[repr(u64)] + /// enum Foo { + /// // `1 << 32` cannot be represented with `Bounded`. + /// A =3D 1 << 32, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded)] + /// #[repr(u64)] + /// enum Foo { + /// // `-1` cannot be represented with `Bounded`. + /// A =3D -1, + /// } + /// ``` + mod overflow_assert_works_on_unsigned_bounded {} + + /// ``` + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(isize)] + /// #[repr(isize)] + /// enum Foo { + /// A =3D isize::MIN, + /// B =3D 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 =3D (isize::MAX as usize) + 1 + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(i32)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D (i32::MIN as i64) - 1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(i32)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D (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 =3D usize::MIN, + /// B =3D 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 =3D -1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(u32)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D (u32::MIN as i64) - 1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(u32)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D (u32::MAX as i64) + 1, + /// } + /// ``` + mod overflow_assert_works_on_unsigned_int {} + + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(Bounded, i8, i16, i32, i64)] + /// #[repr(i8)] + /// enum Foo { + /// // `i8::MAX` cannot be represented with `Bounded`. + /// A =3D i8::MAX, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::Into; + /// + /// #[derive(Into)] + /// #[into(i8, i16, i32, i64, Bounded)] + /// #[repr(i8)] + /// enum Foo { + /// // `i8::MAX` cannot be represented with `Bounded`. + /// A =3D i8::MAX, + /// } + /// ``` + mod any_into_target_overflow_is_rejected {} +} --=20 2.52.0 From nobody Sun Feb 8 00:34:51 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 568292BDC0A; Thu, 29 Jan 2026 14:32:48 +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=1769697168; cv=none; b=TZMMrlhalMzxOwL4qwTtHVIRh26gtWfbkqEU6OjZy+5Y6YRphwqZL4nR8fam/UgHNmNbRltIO4kVeXWR+jEodCH/Ze2vqtVrZItXhw5GKDYbwWpXeHRI9xsXO2K84/hPUpRnb9ooSGvvDMuPgziUvzy5+hhUGEgsnHILx+Oy7Lg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769697168; c=relaxed/simple; bh=0lh49/+EaZNzY8ezIGgV9EAGMJtOWAtE+x9jSqtUzo0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Y45e1ZbrB3WN+zguyD23Qm3K/FgAxHU3RF5HmASNv6UabkDEENuaCbQR/kCMausa+ZiAoF6YSnoXWaLmCqUQEHr52AsAAlRbtyy5a1L9G78Q2zxHITIihFyzhVHWHxXxuINGiuKGEY8Z+b451i5UoeV5rKek8XKxkrh0wdrIhcg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=mR6/wS9D; 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="mR6/wS9D" Received: by smtp.kernel.org (Postfix) with ESMTPS id 1CA90C2BC9E; Thu, 29 Jan 2026 14:32:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1769697168; bh=0lh49/+EaZNzY8ezIGgV9EAGMJtOWAtE+x9jSqtUzo0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=mR6/wS9DhOrbU2Lw9F2HNfbt8tbTicCszsQaHim2jXgci+/e1jW4cmf1GYcgWf49Q 8aWUJWZ99CjZ6DeOX8tbbQnXAOQKtRCX/ZP+TC3MtbERG/E9+vSC5BTDZMPU30qvwV etLOaYHZGpMpCT/1aJ9veDJ/2pBxMczJp/6avRO24WxBLC+Kl1ZJsyHrrh7z0LRbzS 4F998vtUSIzIpgG/cNZ5DUNaVlxren8Uvc/d8s1eqOXz7SQ1RfVrg2YxQYUtvz8b5s JaCnzmDWP7cb5ol/Otl2X9oA4xNpXnXJmvADMMrhjd8PTyvKyQVisS0q2sz4bToc1u ug+Deqy4vPkDA== 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 13BF9D61021; Thu, 29 Jan 2026 14:32:48 +0000 (UTC) From: Jesung Yang via B4 Relay Date: Thu, 29 Jan 2026 23:32:48 +0900 Subject: [PATCH v5 4/4] rust: macros: add private doctests for `TryFrom` derive macro 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: <20260129-try-from-into-macro-v5-4-dd011008118c@gmail.com> References: <20260129-try-from-into-macro-v5-0-dd011008118c@gmail.com> In-Reply-To: <20260129-try-from-into-macro-v5-0-dd011008118c@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.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=19107; i=y.j3ms.n@gmail.com; h=from:subject:message-id; bh=l34mus63yQ1c1i6SvdeCR7UhR8dIQzbjvW9rVOFt/Qc=; b=owJ4nAFtApL9kA0DAAoBhnBYoOPQ2LoByyZiAGl7b477vpjupbGfMb6dBtpzSyrwTfCz+OyIN nLFLHyvPfubQYkCMwQAAQoAHRYhBNJuBqTTLsbEgOaQ0IZwWKDj0Ni6BQJpe2+OAAoJEIZwWKDj 0Ni6S7EQAME7j04Zsp3njqnFsdB5ZALVoad+aAD11/OBIGf9+fJyHBYvQMD3e0WZpWDe/VUoDgp OfF88eJ+ekngEDnsERzMWoVCGhuus8KKCZEPOu34BurcbvziFslfTAbU3f0qMFsQcnN9ABBO0CX x8n6DebbIfG/0TOdG5y+UaDkYvYUU5njVAnPhpdAo25E1f+XfD3e4fnxkTQwvYy0evNhr22+q5t G4NNj2wLQMsCbKMpgrTgU3N+LoV1ZbWkULj1Xp/8JF90WMP2Uli47Q1R0APgjrd9mborBOPKyMo 7DWW+mebzKISlf8zzDc+sFVObHMHzg7dUymcWNTMHdbogRxulAB/fyx4ZwJcAMrOXL4ndSxxezH 6eszoBrqWT9KsdnLlbUaYgMwKjE+HUkz3IRlNphTKzbQgser2eKmkzOidQvYOlNPwPF5O1Lv0Le vbkKHpOvD7tZURUr3rJuuOJF+8L0Rb2LOqQ+UmQLUlRQaROrCwWU3ZbzGwawqzhn00IdedzBWVt 2vsVXV+0dSBvX7Pu48LqB+ejgA5UUPz4jRKez7s2eF5WBCdah4u5W2herq+IN21+6mQS9ut0W/q 7IU6ZffrkrQ7aLjZXmR98uVUadgAHVNeGL+8taoAKyMuvVQqx1aCxYg0Gcd3S+GUN+sK6oK3a0c 7hbvJxLLD8G67tJK7UC4zYQ== X-Developer-Key: i=y.j3ms.n@gmail.com; a=openpgp; fpr=D26E06A4D32EC6C480E690D0867058A0E3D0D8BA X-Endpoint-Received: by B4 Relay for y.j3ms.n@gmail.com/default with auth_id=602 X-Original-From: Jesung Yang Reply-To: y.j3ms.n@gmail.com From: Jesung Yang 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 Tested-by: Shivam Kalra --- rust/macros/convert.rs | 579 +++++++++++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 579 insertions(+) diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs index 41ccbd849348..2acaafb58e93 100644 --- a/rust/macros/convert.rs +++ b/rust/macros/convert.rs @@ -1018,3 +1018,582 @@ mod overflow_assert_works_on_unsigned_int {} /// ``` mod any_into_target_overflow_is_rejected {} } + +mod derive_try_from_tests { + /// ``` + /// use kernel::{ + /// macros::{ + /// Into, + /// TryFrom, // + /// }, + /// num::Bounded, + /// prelude::*, // + /// }; + /// + /// #[derive(Debug, Into, PartialEq, TryFrom)] + /// #[into(bool, Bounded, Bounded, i8, i16, i32, i64, i1= 28, isize, u8, u16, u32, u64, u128, usize)] + /// #[try_from(bool, Bounded, Bounded, 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 =3D Foo::A.into(); + /// let foo_b: Bounded =3D Foo::B.into(); + /// assert_eq!(Bounded::::new::<0>(), foo_a); + /// assert_eq!(Bounded::::new::<1>(), foo_b); + /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::::new::<0>())= ); + /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::::new::<1>())= ); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<-1>(= ))); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<2>()= )); + /// + /// let foo_a: Bounded =3D Foo::A.into(); + /// let foo_b: Bounded =3D Foo::B.into(); + /// assert_eq!(Bounded::::new::<0>(), foo_a); + /// assert_eq!(Bounded::::new::<1>(), foo_b); + /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::::new::<0>())= ); + /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::::new::<1>())= ); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<2>()= )); + /// + /// macro_rules! gen_signed_tests { + /// ($($type:ty),*) =3D> { + /// $( + /// 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),*) =3D> { + /// $( + /// 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 =3D add(0, 0), + /// B =3D 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 =3D 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 =3D -1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(bool)] + /// enum Foo { + /// // `2` cannot be represented with `bool`. + /// A =3D 2, + /// } + /// ``` + mod overflow_assert_works_on_bool {} + + /// ``` + /// use kernel::{ + /// macros::TryFrom, + /// num::Bounded, + /// prelude::*, // + /// }; + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// A =3D -1 << 6, // The minimum value of `Bounded`. + /// B =3D (1 << 6) - 1, // The maximum value of `Bounded`. + /// } + /// + /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::::new::<{ -1_= i8 << 6 }>())); + /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::::new::<{ (1_= i8 << 6) - 1 }>())); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<{ (-= 1_i8 << 6) + 1 }>())); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<{ (1= _i8 << 6) - 2 }>())); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// // `1 << 6` cannot be represented with `Bounded`. + /// A =3D 1 << 6, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// // `(-1 << 6) - 1` cannot be represented with `Bounded`. + /// A =3D (-1 << 6) - 1, + /// } + /// ``` + /// + /// ``` + /// use kernel::{ + /// macros::TryFrom, + /// num::Bounded, + /// prelude::*, // + /// }; + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// A =3D -1, // The minimum value of `Bounded`. + /// B, // The maximum value of `Bounded`. + /// } + /// + /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::::new::<{ -1_= i8 }>())); + /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::::new::<{ 0_i= 8 } >())); + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// enum Bar { + /// A =3D -1, // The minimum value of `Bounded`. + /// } + /// + /// assert_eq!(Ok(Bar::A), Bar::try_from(Bounded::::new::<{ -1_= i8 }>())); + /// assert_eq!(Err(EINVAL), Bar::try_from(Bounded::::new::<{ 0_= i8 } >())); + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// enum Baz { + /// A, // The maximum value of `Bounded`. + /// } + /// + /// assert_eq!(Err(EINVAL), Baz::try_from(Bounded::::new::<{ -1= _i8 }>())); + /// assert_eq!(Ok(Baz::A), Baz::try_from(Bounded::::new::<{ 0_i= 8 } >())); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// // `1` cannot be represented with `Bounded`. + /// A =3D 1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// // `-2` cannot be represented with `Bounded`. + /// A =3D -2, + /// } + /// ``` + /// + /// ``` + /// use kernel::{ + /// macros::TryFrom, + /// num::Bounded, + /// prelude::*, // + /// }; + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D i32::MIN as i64, + /// B =3D i32::MAX as i64, + /// } + /// + /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::::new::<{ i= 32::MIN }>())); + /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::::new::<{ i= 32::MAX }>())); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<{ = i32::MIN + 1 }>())); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<{ = i32::MAX - 1 }>())); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// #[repr(i64)] + /// enum Foo { + /// // `1 << 31` cannot be represented with `Bounded`. + /// A =3D 1 << 31, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// #[repr(i64)] + /// enum Foo { + /// // `(-1 << 31) - 1` cannot be represented with `Bounded`. + /// A =3D (-1 << 31) - 1, + /// } + /// ``` + mod overflow_assert_works_on_signed_bounded {} + + /// ``` + /// use kernel::{ + /// macros::TryFrom, + /// num::Bounded, + /// prelude::*, // + /// }; + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// A, // The minimum value of `Bounded`. + /// B =3D (1 << 7) - 1, // The maximum value of `Bounded`. + /// } + /// + /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::::new::<{ 0 }= >())); + /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::::new::<{ (1_= u8 << 7) - 1 }>())); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<{ 1 = }>())); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<{ (1= _u8 << 7) - 2 }>())); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// // `1 << 7` cannot be represented with `Bounded`. + /// A =3D 1 << 7, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// // `-1` cannot be represented with `Bounded`. + /// A =3D -1, + /// } + /// ``` + /// + /// ``` + /// use kernel::{ + /// macros::TryFrom, + /// num::Bounded, + /// prelude::*, // + /// }; + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// A, // The minimum value of `Bounded`. + /// B, // The maximum value of `Bounded`. + /// } + /// + /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::::new::<{ 0 }= >())); + /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::::new::<{ 1 }= >())); + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// enum Bar { + /// A, // The minimum value of `Bounded`. + /// } + /// + /// assert_eq!(Ok(Bar::A), Bar::try_from(Bounded::::new::<{ 0 }= >())); + /// assert_eq!(Err(EINVAL), Bar::try_from(Bounded::::new::<{ 1 = }>())); + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// enum Baz { + /// A =3D 1, // The maximum value of `Bounded`. + /// } + /// + /// assert_eq!(Err(EINVAL), Baz::try_from(Bounded::::new::<{ 0 = }>())); + /// assert_eq!(Ok(Baz::A), Baz::try_from(Bounded::::new::<{ 1 }= >())); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// // `2` cannot be represented with `Bounded`. + /// A =3D 2, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// enum Foo { + /// // `-1` cannot be represented with `Bounded`. + /// A =3D -1, + /// } + /// ``` + /// + /// ``` + /// use kernel::{ + /// macros::TryFrom, + /// num::Bounded, + /// prelude::*, // + /// }; + /// + /// #[derive(Debug, PartialEq, TryFrom)] + /// #[try_from(Bounded)] + /// #[repr(u64)] + /// enum Foo { + /// A =3D u32::MIN as u64, + /// B =3D u32::MAX as u64, + /// } + /// + /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::::new::<{ u= 32::MIN }>())); + /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::::new::<{ u= 32::MAX }>())); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<{ = u32::MIN + 1 }>())); + /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::::new::<{ = u32::MAX - 1 }>())); + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// #[repr(u64)] + /// enum Foo { + /// // `1 << 32` cannot be represented with `Bounded`. + /// A =3D 1 << 32, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded)] + /// #[repr(u64)] + /// enum Foo { + /// // `-1` cannot be represented with `Bounded`. + /// A =3D -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 =3D isize::MIN, + /// B =3D 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 =3D (isize::MAX as usize) + 1 + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(i32)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D (i32::MIN as i64) - 1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(i32)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D (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 =3D usize::MIN, + /// B =3D 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 =3D -1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(u32)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D (u32::MIN as i64) - 1, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(u32)] + /// #[repr(i64)] + /// enum Foo { + /// A =3D (u32::MAX as i64) + 1, + /// } + /// ``` + mod overflow_assert_works_on_unsigned_int {} + + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(Bounded, i8, i16, i32, i64)] + /// #[repr(i8)] + /// enum Foo { + /// // `i8::MAX` cannot be represented with `Bounded`. + /// A =3D i8::MAX, + /// } + /// ``` + /// + /// ```compile_fail + /// use kernel::macros::TryFrom; + /// + /// #[derive(TryFrom)] + /// #[try_from(i8, i16, i32, i64, Bounded)] + /// #[repr(i8)] + /// enum Foo { + /// // `i8::MAX` cannot be represented with `Bounded`. + /// A =3D i8::MAX, + /// } + /// ``` + mod any_try_from_target_overflow_is_rejected {} +} --=20 2.52.0