[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PULL 12/38] rust: qemu-api-macros: add automatic TryFrom/TryInto deriva
From: |
Paolo Bonzini |
Subject: |
[PULL 12/38] rust: qemu-api-macros: add automatic TryFrom/TryInto derivation |
Date: |
Fri, 10 Jan 2025 19:45:53 +0100 |
This is going to be fairly common. Using a custom procedural macro
provides better error messages and automatically finds the right
type.
Note that this is different from the same-named macro in the
derive_more crate. That one provides conversion from e.g. tuples
to enums with tuple variants, not from integers to enums.
Reviewed-by: Zhao Liu <zhao1.liu@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
rust/hw/char/pl011/src/lib.rs | 28 +------------
rust/qemu-api-macros/src/lib.rs | 74 ++++++++++++++++++++++++++++++++-
2 files changed, 73 insertions(+), 29 deletions(-)
diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs
index 69064d6929b..0a89d393e0f 100644
--- a/rust/hw/char/pl011/src/lib.rs
+++ b/rust/hw/char/pl011/src/lib.rs
@@ -45,7 +45,7 @@
#[doc(alias = "offset")]
#[allow(non_camel_case_types)]
#[repr(u64)]
-#[derive(Debug)]
+#[derive(Debug, qemu_api_macros::TryInto)]
pub enum RegisterOffset {
/// Data Register
///
@@ -102,32 +102,6 @@ pub enum RegisterOffset {
//Reserved = 0x04C,
}
-impl core::convert::TryFrom<u64> for RegisterOffset {
- type Error = u64;
-
- fn try_from(value: u64) -> Result<Self, Self::Error> {
- macro_rules! case {
- ($($discriminant:ident),*$(,)*) => {
- /* check that matching on all macro arguments compiles, which
means we are not
- * missing any enum value; if the type definition ever changes
this will stop
- * compiling.
- */
- const fn _assert_exhaustive(val: RegisterOffset) {
- match val {
- $(RegisterOffset::$discriminant => (),)*
- }
- }
-
- match value {
- $(x if x == Self::$discriminant as u64 =>
Ok(Self::$discriminant),)*
- _ => Err(value),
- }
- }
- }
- case! { DR, RSR, FR, FBRD, ILPR, IBRD, LCR_H, CR, FLS, IMSC, RIS, MIS,
ICR, DMACR }
- }
-}
-
pub mod registers {
//! Device registers exposed as typed structs which are backed by arbitrary
//! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc.
diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs
index 539c48df298..7ec218202f4 100644
--- a/rust/qemu-api-macros/src/lib.rs
+++ b/rust/qemu-api-macros/src/lib.rs
@@ -5,8 +5,8 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{
- parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma,
Data, DeriveInput, Field,
- Fields, Ident, Type, Visibility,
+ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned,
token::Comma, Data,
+ DeriveInput, Field, Fields, Ident, Meta, Path, Token, Type, Variant,
Visibility,
};
mod utils;
@@ -98,3 +98,73 @@ pub fn derive_offsets(input: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}
+
+#[allow(non_snake_case)]
+fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result<Path, MacroError> {
+ let repr = input.attrs.iter().find(|attr| attr.path().is_ident("repr"));
+ if let Some(repr) = repr {
+ let nested = repr.parse_args_with(Punctuated::<Meta,
Token![,]>::parse_terminated)?;
+ for meta in nested {
+ match meta {
+ Meta::Path(path) if path.is_ident("u8") => return Ok(path),
+ Meta::Path(path) if path.is_ident("u16") => return Ok(path),
+ Meta::Path(path) if path.is_ident("u32") => return Ok(path),
+ Meta::Path(path) if path.is_ident("u64") => return Ok(path),
+ _ => {}
+ }
+ }
+ }
+
+ Err(MacroError::Message(
+ format!("#[repr(u8/u16/u32/u64) required for {}", msg),
+ input.ident.span(),
+ ))
+}
+
+fn get_variants(input: &DeriveInput) -> Result<&Punctuated<Variant, Comma>,
MacroError> {
+ if let Data::Enum(e) = &input.data {
+ if let Some(v) = e.variants.iter().find(|v| v.fields != Fields::Unit) {
+ return Err(MacroError::Message(
+ "Cannot derive TryInto for enum with non-unit
variants.".to_string(),
+ v.fields.span(),
+ ));
+ }
+ Ok(&e.variants)
+ } else {
+ Err(MacroError::Message(
+ "Cannot derive TryInto for union or struct.".to_string(),
+ input.ident.span(),
+ ))
+ }
+}
+
+#[rustfmt::skip::macros(quote)]
+fn derive_tryinto_or_error(input: DeriveInput) ->
Result<proc_macro2::TokenStream, MacroError> {
+ let repr = get_repr_uN(&input, "#[derive(TryInto)]")?;
+
+ let name = &input.ident;
+ let variants = get_variants(&input)?;
+ let discriminants: Vec<&Ident> = variants.iter().map(|f|
&f.ident).collect();
+
+ Ok(quote! {
+ impl core::convert::TryFrom<#repr> for #name {
+ type Error = #repr;
+
+ fn try_from(value: #repr) -> Result<Self, Self::Error> {
+ #(const #discriminants: #repr = #name::#discriminants as
#repr;)*;
+ match value {
+ #(#discriminants => Ok(Self::#discriminants),)*
+ _ => Err(value),
+ }
+ }
+ }
+ })
+}
+
+#[proc_macro_derive(TryInto)]
+pub fn derive_tryinto(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ let expanded = derive_tryinto_or_error(input).unwrap_or_else(Into::into);
+
+ TokenStream::from(expanded)
+}
--
2.47.1
- [PULL 18/38] target/i386: use shr to load high-byte registers into T0/T1, (continued)
- [PULL 18/38] target/i386: use shr to load high-byte registers into T0/T1, Paolo Bonzini, 2025/01/10
- [PULL 19/38] i386/cpu: Mark avx10_version filtered when prefix is NULL, Paolo Bonzini, 2025/01/10
- [PULL 22/38] target/i386/kvm: Only save/load kvmclock MSRs when kvmclock enabled, Paolo Bonzini, 2025/01/10
- [PULL 23/38] target/i386/kvm: Drop workaround for KVM_X86_DISABLE_EXITS_HTL typo, Paolo Bonzini, 2025/01/10
- [PULL 25/38] target/i386/kvm: Clean up return values of MSR filter related functions, Paolo Bonzini, 2025/01/10
- [PULL 26/38] target/i386/kvm: Return -1 when kvm_msr_energy_thread_init() fails, Paolo Bonzini, 2025/01/10
- [PULL 27/38] target/i386/kvm: Clean up error handling in kvm_arch_init(), Paolo Bonzini, 2025/01/10
- [PULL 32/38] i386/topology: Update the comment of x86_apicid_from_topo_ids(), Paolo Bonzini, 2025/01/10
- [PULL 20/38] target/i386/kvm: Add feature bit definitions for KVM CPUID, Paolo Bonzini, 2025/01/10
- [PULL 37/38] i386/cpu: Set up CPUID_HT in x86_cpu_expand_features() instead of cpu_x86_cpuid(), Paolo Bonzini, 2025/01/10
- [PULL 12/38] rust: qemu-api-macros: add automatic TryFrom/TryInto derivation,
Paolo Bonzini <=
- [PULL 31/38] i386/cpu: Drop cores_per_pkg in cpu_x86_cpuid(), Paolo Bonzini, 2025/01/10
- [PULL 35/38] i386/cpu: Hoist check of CPUID_EXT3_TOPOEXT against threads_per_core, Paolo Bonzini, 2025/01/10
- [PULL 38/38] i386/cpu: Set and track CPUID_EXT3_CMP_LEG in env->features[FEAT_8000_0001_ECX], Paolo Bonzini, 2025/01/10
- [PULL 36/38] cpu: Remove nr_cores from struct CPUState, Paolo Bonzini, 2025/01/10