mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-02 07:13:54 -06:00
rust: qemu-api-macros: add automatic TryFrom/TryInto derivation
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>
This commit is contained in:
parent
a3b620fff7
commit
809c703a60
2 changed files with 73 additions and 29 deletions
|
@ -45,7 +45,7 @@ pub const TYPE_PL011_LUMINARY: &::std::ffi::CStr = c_str!("pl011_luminary");
|
|||
#[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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue