aboutsummaryrefslogtreecommitdiff
path: root/rust/qemu-api-macros
diff options
context:
space:
mode:
Diffstat (limited to 'rust/qemu-api-macros')
-rw-r--r--rust/qemu-api-macros/meson.build3
-rw-r--r--rust/qemu-api-macros/src/bits.rs58
-rw-r--r--rust/qemu-api-macros/src/lib.rs93
-rw-r--r--rust/qemu-api-macros/src/tests.rs137
-rw-r--r--rust/qemu-api-macros/src/utils.rs26
5 files changed, 209 insertions, 108 deletions
diff --git a/rust/qemu-api-macros/meson.build b/rust/qemu-api-macros/meson.build
index 8610ce1..2152bcb 100644
--- a/rust/qemu-api-macros/meson.build
+++ b/rust/qemu-api-macros/meson.build
@@ -17,3 +17,6 @@ _qemu_api_macros_rs = rust.proc_macro(
qemu_api_macros = declare_dependency(
link_with: _qemu_api_macros_rs,
)
+
+rust.test('rust-qemu-api-macros-tests', _qemu_api_macros_rs,
+ suite: ['unit', 'rust'])
diff --git a/rust/qemu-api-macros/src/bits.rs b/rust/qemu-api-macros/src/bits.rs
index 5ba8475..a80a3b9 100644
--- a/rust/qemu-api-macros/src/bits.rs
+++ b/rust/qemu-api-macros/src/bits.rs
@@ -6,8 +6,7 @@
use proc_macro2::{
Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree, TokenTree as TT,
};
-
-use crate::utils::MacroError;
+use syn::Error;
pub struct BitsConstInternal {
typ: TokenTree,
@@ -36,27 +35,21 @@ impl BitsConstInternal {
tok: TokenTree,
it: &mut dyn Iterator<Item = TokenTree>,
out: &mut TokenStream,
- ) -> Result<Option<TokenTree>, MacroError> {
+ ) -> Result<Option<TokenTree>, Error> {
let next = match tok {
TT::Group(ref g) => {
if g.delimiter() != Delimiter::Parenthesis && g.delimiter() != Delimiter::None {
- return Err(MacroError::Message("expected parenthesis".into(), g.span()));
+ return Err(Error::new(g.span(), "expected parenthesis"));
}
let mut stream = g.stream().into_iter();
let Some(first_tok) = stream.next() else {
- return Err(MacroError::Message(
- "expected operand, found ')'".into(),
- g.span(),
- ));
+ return Err(Error::new(g.span(), "expected operand, found ')'"));
};
let mut output = TokenStream::new();
// start from the lowest precedence
let next = self.parse_or(first_tok, &mut stream, &mut output)?;
if let Some(tok) = next {
- return Err(MacroError::Message(
- format!("unexpected token {tok}"),
- tok.span(),
- ));
+ return Err(Error::new(tok.span(), format!("unexpected token {tok}")));
}
out.extend(Some(paren(output)));
it.next()
@@ -74,20 +67,17 @@ impl BitsConstInternal {
}
TT::Punct(ref p) => {
if p.as_char() != '!' {
- return Err(MacroError::Message("expected operand".into(), p.span()));
+ return Err(Error::new(p.span(), "expected operand"));
}
let Some(rhs_tok) = it.next() else {
- return Err(MacroError::Message(
- "expected operand at end of input".into(),
- p.span(),
- ));
+ return Err(Error::new(p.span(), "expected operand at end of input"));
};
let next = self.parse_primary(rhs_tok, it, out)?;
out.extend([punct('.'), ident("invert"), paren(TokenStream::new())]);
next
}
_ => {
- return Err(MacroError::Message("unexpected literal".into(), tok.span()));
+ return Err(Error::new(tok.span(), "unexpected literal"));
}
};
Ok(next)
@@ -99,7 +89,7 @@ impl BitsConstInternal {
TokenTree,
&mut dyn Iterator<Item = TokenTree>,
&mut TokenStream,
- ) -> Result<Option<TokenTree>, MacroError>,
+ ) -> Result<Option<TokenTree>, Error>,
>(
&self,
tok: TokenTree,
@@ -108,7 +98,7 @@ impl BitsConstInternal {
ch: char,
f: F,
method: &'static str,
- ) -> Result<Option<TokenTree>, MacroError> {
+ ) -> Result<Option<TokenTree>, Error> {
let mut next = f(self, tok, it, out)?;
while next.is_some() {
let op = next.as_ref().unwrap();
@@ -118,10 +108,7 @@ impl BitsConstInternal {
}
let Some(rhs_tok) = it.next() else {
- return Err(MacroError::Message(
- "expected operand at end of input".into(),
- p.span(),
- ));
+ return Err(Error::new(p.span(), "expected operand at end of input"));
};
let mut rhs = TokenStream::new();
next = f(self, rhs_tok, it, &mut rhs)?;
@@ -136,7 +123,7 @@ impl BitsConstInternal {
tok: TokenTree,
it: &mut dyn Iterator<Item = TokenTree>,
out: &mut TokenStream,
- ) -> Result<Option<TokenTree>, MacroError> {
+ ) -> Result<Option<TokenTree>, Error> {
self.parse_binop(tok, it, out, '-', Self::parse_primary, "difference")
}
@@ -146,7 +133,7 @@ impl BitsConstInternal {
tok: TokenTree,
it: &mut dyn Iterator<Item = TokenTree>,
out: &mut TokenStream,
- ) -> Result<Option<TokenTree>, MacroError> {
+ ) -> Result<Option<TokenTree>, Error> {
self.parse_binop(tok, it, out, '&', Self::parse_sub, "intersection")
}
@@ -156,7 +143,7 @@ impl BitsConstInternal {
tok: TokenTree,
it: &mut dyn Iterator<Item = TokenTree>,
out: &mut TokenStream,
- ) -> Result<Option<TokenTree>, MacroError> {
+ ) -> Result<Option<TokenTree>, Error> {
self.parse_binop(tok, it, out, '^', Self::parse_and, "symmetric_difference")
}
@@ -166,13 +153,13 @@ impl BitsConstInternal {
tok: TokenTree,
it: &mut dyn Iterator<Item = TokenTree>,
out: &mut TokenStream,
- ) -> Result<Option<TokenTree>, MacroError> {
+ ) -> Result<Option<TokenTree>, Error> {
self.parse_binop(tok, it, out, '|', Self::parse_xor, "union")
}
pub fn parse(
it: &mut dyn Iterator<Item = TokenTree>,
- ) -> Result<proc_macro2::TokenStream, MacroError> {
+ ) -> Result<proc_macro2::TokenStream, Error> {
let mut pos = Span::call_site();
let mut typ = proc_macro2::TokenStream::new();
@@ -198,15 +185,15 @@ impl BitsConstInternal {
};
let Some(tok) = next else {
- return Err(MacroError::Message(
- "expected expression, do not call this macro directly".into(),
+ return Err(Error::new(
pos,
+ "expected expression, do not call this macro directly",
));
};
let TT::Group(ref _group) = tok else {
- return Err(MacroError::Message(
- "expected parenthesis, do not call this macro directly".into(),
+ return Err(Error::new(
tok.span(),
+ "expected parenthesis, do not call this macro directly",
));
};
let mut out = TokenStream::new();
@@ -219,10 +206,7 @@ impl BitsConstInternal {
// A parenthesized expression is a single production of the grammar,
// so the input must have reached the last token.
if let Some(tok) = next {
- return Err(MacroError::Message(
- format!("unexpected token {tok}"),
- tok.span(),
- ));
+ return Err(Error::new(tok.span(), format!("unexpected token {tok}")));
}
Ok(out)
}
diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs
index c18bb4e..b525d89 100644
--- a/rust/qemu-api-macros/src/lib.rs
+++ b/rust/qemu-api-macros/src/lib.rs
@@ -6,83 +6,82 @@ use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data,
- DeriveInput, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Variant,
+ DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Variant,
};
-
-mod utils;
-use utils::MacroError;
-
mod bits;
use bits::BitsConstInternal;
+#[cfg(test)]
+mod tests;
+
fn get_fields<'a>(
input: &'a DeriveInput,
msg: &str,
-) -> Result<&'a Punctuated<Field, Comma>, MacroError> {
+) -> Result<&'a Punctuated<Field, Comma>, Error> {
let Data::Struct(ref s) = &input.data else {
- return Err(MacroError::Message(
- format!("Struct required for {msg}"),
+ return Err(Error::new(
input.ident.span(),
+ format!("Struct required for {msg}"),
));
};
let Fields::Named(ref fs) = &s.fields else {
- return Err(MacroError::Message(
- format!("Named fields required for {msg}"),
+ return Err(Error::new(
input.ident.span(),
+ format!("Named fields required for {msg}"),
));
};
Ok(&fs.named)
}
-fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, MacroError> {
+fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, Error> {
let Data::Struct(ref s) = &input.data else {
- return Err(MacroError::Message(
- format!("Struct required for {msg}"),
+ return Err(Error::new(
input.ident.span(),
+ format!("Struct required for {msg}"),
));
};
let Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }) = &s.fields else {
- return Err(MacroError::Message(
- format!("Tuple struct required for {msg}"),
+ return Err(Error::new(
s.fields.span(),
+ format!("Tuple struct required for {msg}"),
));
};
if unnamed.len() != 1 {
- return Err(MacroError::Message(
- format!("A single field is required for {msg}"),
+ return Err(Error::new(
s.fields.span(),
+ format!("A single field is required for {msg}"),
));
}
Ok(&unnamed[0])
}
-fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> {
+fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> {
let expected = parse_quote! { #[repr(C)] };
if input.attrs.iter().any(|attr| attr == &expected) {
Ok(())
} else {
- Err(MacroError::Message(
- format!("#[repr(C)] required for {msg}"),
+ Err(Error::new(
input.ident.span(),
+ format!("#[repr(C)] required for {msg}"),
))
}
}
-fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> {
+fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> {
let expected = parse_quote! { #[repr(transparent)] };
if input.attrs.iter().any(|attr| attr == &expected) {
Ok(())
} else {
- Err(MacroError::Message(
- format!("#[repr(transparent)] required for {msg}"),
+ Err(Error::new(
input.ident.span(),
+ format!("#[repr(transparent)] required for {msg}"),
))
}
}
-fn derive_object_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> {
+fn derive_object_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
is_c_repr(&input, "#[derive(Object)]")?;
let name = &input.ident;
@@ -103,12 +102,13 @@ fn derive_object_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream
#[proc_macro_derive(Object)]
pub fn derive_object(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
- let expanded = derive_object_or_error(input).unwrap_or_else(Into::into);
- TokenStream::from(expanded)
+ derive_object_or_error(input)
+ .unwrap_or_else(syn::Error::into_compile_error)
+ .into()
}
-fn derive_opaque_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> {
+fn derive_opaque_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
is_transparent_repr(&input, "#[derive(Wrapper)]")?;
let name = &input.ident;
@@ -149,13 +149,14 @@ fn derive_opaque_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream
#[proc_macro_derive(Wrapper)]
pub fn derive_opaque(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
- let expanded = derive_opaque_or_error(input).unwrap_or_else(Into::into);
- TokenStream::from(expanded)
+ derive_opaque_or_error(input)
+ .unwrap_or_else(syn::Error::into_compile_error)
+ .into()
}
#[allow(non_snake_case)]
-fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result<Path, MacroError> {
+fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result<Path, Error> {
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)?;
@@ -170,23 +171,23 @@ fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result<Path, MacroError> {
}
}
- Err(MacroError::Message(
- format!("#[repr(u8/u16/u32/u64) required for {msg}"),
+ Err(Error::new(
input.ident.span(),
+ format!("#[repr(u8/u16/u32/u64) required for {msg}"),
))
}
-fn get_variants(input: &DeriveInput) -> Result<&Punctuated<Variant, Comma>, MacroError> {
+fn get_variants(input: &DeriveInput) -> Result<&Punctuated<Variant, Comma>, Error> {
let Data::Enum(ref e) = &input.data else {
- return Err(MacroError::Message(
- "Cannot derive TryInto for union or struct.".to_string(),
+ return Err(Error::new(
input.ident.span(),
+ "Cannot derive TryInto for union or struct.",
));
};
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(),
+ return Err(Error::new(
v.fields.span(),
+ "Cannot derive TryInto for enum with non-unit variants.",
));
}
Ok(&e.variants)
@@ -197,11 +198,11 @@ fn derive_tryinto_body(
name: &Ident,
variants: &Punctuated<Variant, Comma>,
repr: &Path,
-) -> Result<proc_macro2::TokenStream, MacroError> {
+) -> Result<proc_macro2::TokenStream, Error> {
let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect();
Ok(quote! {
- #(const #discriminants: #repr = #name::#discriminants as #repr;)*;
+ #(const #discriminants: #repr = #name::#discriminants as #repr;)*
match value {
#(#discriminants => core::result::Result::Ok(#name::#discriminants),)*
_ => core::result::Result::Err(value),
@@ -210,7 +211,7 @@ fn derive_tryinto_body(
}
#[rustfmt::skip::macros(quote)]
-fn derive_tryinto_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> {
+fn derive_tryinto_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
let repr = get_repr_uN(&input, "#[derive(TryInto)]")?;
let name = &input.ident;
let body = derive_tryinto_body(name, get_variants(&input)?, &repr)?;
@@ -229,7 +230,7 @@ fn derive_tryinto_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStrea
#body
}) {
Ok(x) => x,
- Err(_) => panic!(#errmsg)
+ Err(_) => panic!(#errmsg),
}
}
}
@@ -247,9 +248,10 @@ fn derive_tryinto_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStrea
#[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)
+ derive_tryinto_or_error(input)
+ .unwrap_or_else(syn::Error::into_compile_error)
+ .into()
}
#[proc_macro]
@@ -257,6 +259,7 @@ pub fn bits_const_internal(ts: TokenStream) -> TokenStream {
let ts = proc_macro2::TokenStream::from(ts);
let mut it = ts.into_iter();
- let expanded = BitsConstInternal::parse(&mut it).unwrap_or_else(Into::into);
- TokenStream::from(expanded)
+ BitsConstInternal::parse(&mut it)
+ .unwrap_or_else(syn::Error::into_compile_error)
+ .into()
}
diff --git a/rust/qemu-api-macros/src/tests.rs b/rust/qemu-api-macros/src/tests.rs
new file mode 100644
index 0000000..d6dcd62
--- /dev/null
+++ b/rust/qemu-api-macros/src/tests.rs
@@ -0,0 +1,137 @@
+// Copyright 2025, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use quote::quote;
+
+use super::*;
+
+macro_rules! derive_compile_fail {
+ ($derive_fn:ident, $input:expr, $error_msg:expr) => {{
+ let input: proc_macro2::TokenStream = $input;
+ let error_msg: &str = $error_msg;
+ let derive_fn: fn(input: syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> =
+ $derive_fn;
+
+ let input: syn::DeriveInput = syn::parse2(input).unwrap();
+ let result = derive_fn(input);
+ let err = result.unwrap_err().into_compile_error();
+ assert_eq!(
+ err.to_string(),
+ quote! { ::core::compile_error! { #error_msg } }.to_string()
+ );
+ }};
+}
+
+macro_rules! derive_compile {
+ ($derive_fn:ident, $input:expr, $($expected:tt)*) => {{
+ let input: proc_macro2::TokenStream = $input;
+ let expected: proc_macro2::TokenStream = $($expected)*;
+ let derive_fn: fn(input: syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> =
+ $derive_fn;
+
+ let input: syn::DeriveInput = syn::parse2(input).unwrap();
+ let result = derive_fn(input).unwrap();
+ assert_eq!(result.to_string(), expected.to_string());
+ }};
+}
+
+#[test]
+fn test_derive_object() {
+ derive_compile_fail!(
+ derive_object_or_error,
+ quote! {
+ #[derive(Object)]
+ struct Foo {
+ _unused: [u8; 0],
+ }
+ },
+ "#[repr(C)] required for #[derive(Object)]"
+ );
+ derive_compile!(
+ derive_object_or_error,
+ quote! {
+ #[derive(Object)]
+ #[repr(C)]
+ struct Foo {
+ _unused: [u8; 0],
+ }
+ },
+ quote! {
+ ::qemu_api::assert_field_type!(
+ Foo,
+ _unused,
+ ::qemu_api::qom::ParentField<<Foo as ::qemu_api::qom::ObjectImpl>::ParentType>
+ );
+ ::qemu_api::module_init! {
+ MODULE_INIT_QOM => unsafe {
+ ::qemu_api::bindings::type_register_static(&<Foo as ::qemu_api::qom::ObjectImpl>::TYPE_INFO);
+ }
+ }
+ }
+ );
+}
+
+#[test]
+fn test_derive_tryinto() {
+ derive_compile_fail!(
+ derive_tryinto_or_error,
+ quote! {
+ #[derive(TryInto)]
+ struct Foo {
+ _unused: [u8; 0],
+ }
+ },
+ "#[repr(u8/u16/u32/u64) required for #[derive(TryInto)]"
+ );
+ derive_compile!(
+ derive_tryinto_or_error,
+ quote! {
+ #[derive(TryInto)]
+ #[repr(u8)]
+ enum Foo {
+ First = 0,
+ Second,
+ }
+ },
+ quote! {
+ impl Foo {
+ #[allow(dead_code)]
+ pub const fn into_bits(self) -> u8 {
+ self as u8
+ }
+
+ #[allow(dead_code)]
+ pub const fn from_bits(value: u8) -> Self {
+ match ({
+ const First: u8 = Foo::First as u8;
+ const Second: u8 = Foo::Second as u8;
+ match value {
+ First => core::result::Result::Ok(Foo::First),
+ Second => core::result::Result::Ok(Foo::Second),
+ _ => core::result::Result::Err(value),
+ }
+ }) {
+ Ok(x) => x,
+ Err(_) => panic!("invalid value for Foo"),
+ }
+ }
+ }
+
+ impl core::convert::TryFrom<u8> for Foo {
+ type Error = u8;
+
+ #[allow(ambiguous_associated_items)]
+ fn try_from(value: u8) -> Result<Self, u8> {
+ const First: u8 = Foo::First as u8;
+ const Second: u8 = Foo::Second as u8;
+ match value {
+ First => core::result::Result::Ok(Foo::First),
+ Second => core::result::Result::Ok(Foo::Second),
+ _ => core::result::Result::Err(value),
+ }
+ }
+ }
+ }
+ );
+}
diff --git a/rust/qemu-api-macros/src/utils.rs b/rust/qemu-api-macros/src/utils.rs
deleted file mode 100644
index 02c91ae..0000000
--- a/rust/qemu-api-macros/src/utils.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Procedural macro utilities.
-// Author(s): Paolo Bonzini <pbonzini@redhat.com>
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-use proc_macro2::Span;
-use quote::quote_spanned;
-
-pub enum MacroError {
- Message(String, Span),
- ParseError(syn::Error),
-}
-
-impl From<syn::Error> for MacroError {
- fn from(err: syn::Error) -> Self {
- MacroError::ParseError(err)
- }
-}
-
-impl From<MacroError> for proc_macro2::TokenStream {
- fn from(err: MacroError) -> Self {
- match err {
- MacroError::Message(msg, span) => quote_spanned! { span => compile_error!(#msg); },
- MacroError::ParseError(err) => err.into_compile_error(),
- }
- }
-}