diff options
Diffstat (limited to 'rust/qemu-macros')
-rw-r--r-- | rust/qemu-macros/Cargo.toml | 25 | ||||
-rw-r--r-- | rust/qemu-macros/meson.build | 23 | ||||
-rw-r--r-- | rust/qemu-macros/src/bits.rs | 213 | ||||
-rw-r--r-- | rust/qemu-macros/src/lib.rs | 407 | ||||
-rw-r--r-- | rust/qemu-macros/src/tests.rs | 347 |
5 files changed, 1015 insertions, 0 deletions
diff --git a/rust/qemu-macros/Cargo.toml b/rust/qemu-macros/Cargo.toml new file mode 100644 index 0000000..c25b6c0 --- /dev/null +++ b/rust/qemu-macros/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "qemu_macros" +version = "0.1.0" +authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"] +description = "Rust bindings for QEMU - Utility macros" +resolver = "2" +publish = false + +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[lib] +proc-macro = true + +[dependencies] +attrs = "0.2.9" +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["extra-traits"] } + +[lints] +workspace = true diff --git a/rust/qemu-macros/meson.build b/rust/qemu-macros/meson.build new file mode 100644 index 0000000..0f27e0d --- /dev/null +++ b/rust/qemu-macros/meson.build @@ -0,0 +1,23 @@ +_qemu_macros_rs = rust.proc_macro( + 'qemu_macros', + files('src/lib.rs'), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_args: [ + '--cfg', 'use_fallback', + '--cfg', 'feature="syn-error"', + '--cfg', 'feature="proc-macro"', + ], + dependencies: [ + attrs_rs_native, + proc_macro2_rs_native, + quote_rs_native, + syn_rs_native, + ], +) + +qemu_macros = declare_dependency( + link_with: _qemu_macros_rs, +) + +rust.test('rust-qemu-macros-tests', _qemu_macros_rs, + suite: ['unit', 'rust']) diff --git a/rust/qemu-macros/src/bits.rs b/rust/qemu-macros/src/bits.rs new file mode 100644 index 0000000..a80a3b9 --- /dev/null +++ b/rust/qemu-macros/src/bits.rs @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: MIT or Apache-2.0 or GPL-2.0-or-later + +// shadowing is useful together with "if let" +#![allow(clippy::shadow_unrelated)] + +use proc_macro2::{ + Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree, TokenTree as TT, +}; +use syn::Error; + +pub struct BitsConstInternal { + typ: TokenTree, +} + +fn paren(ts: TokenStream) -> TokenTree { + TT::Group(Group::new(Delimiter::Parenthesis, ts)) +} + +fn ident(s: &'static str) -> TokenTree { + TT::Ident(Ident::new(s, Span::call_site())) +} + +fn punct(ch: char) -> TokenTree { + TT::Punct(Punct::new(ch, Spacing::Alone)) +} + +/// Implements a recursive-descent parser that translates Boolean expressions on +/// bitmasks to invocations of `const` functions defined by the `bits!` macro. +impl BitsConstInternal { + // primary ::= '(' or ')' + // | ident + // | '!' ident + fn parse_primary( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> Result<Option<TokenTree>, Error> { + let next = match tok { + TT::Group(ref g) => { + if g.delimiter() != Delimiter::Parenthesis && g.delimiter() != Delimiter::None { + return Err(Error::new(g.span(), "expected parenthesis")); + } + let mut stream = g.stream().into_iter(); + let Some(first_tok) = stream.next() else { + 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(Error::new(tok.span(), format!("unexpected token {tok}"))); + } + out.extend(Some(paren(output))); + it.next() + } + TT::Ident(_) => { + let mut output = TokenStream::new(); + output.extend([ + self.typ.clone(), + TT::Punct(Punct::new(':', Spacing::Joint)), + TT::Punct(Punct::new(':', Spacing::Joint)), + tok, + ]); + out.extend(Some(paren(output))); + it.next() + } + TT::Punct(ref p) => { + if p.as_char() != '!' { + return Err(Error::new(p.span(), "expected operand")); + } + let Some(rhs_tok) = it.next() else { + 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(Error::new(tok.span(), "unexpected literal")); + } + }; + Ok(next) + } + + fn parse_binop< + F: Fn( + &Self, + TokenTree, + &mut dyn Iterator<Item = TokenTree>, + &mut TokenStream, + ) -> Result<Option<TokenTree>, Error>, + >( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ch: char, + f: F, + method: &'static str, + ) -> Result<Option<TokenTree>, Error> { + let mut next = f(self, tok, it, out)?; + while next.is_some() { + let op = next.as_ref().unwrap(); + let TT::Punct(ref p) = op else { break }; + if p.as_char() != ch { + break; + } + + let Some(rhs_tok) = it.next() else { + 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)?; + out.extend([punct('.'), ident(method), paren(rhs)]); + } + Ok(next) + } + + // sub ::= primary ('-' primary)* + pub fn parse_sub( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> Result<Option<TokenTree>, Error> { + self.parse_binop(tok, it, out, '-', Self::parse_primary, "difference") + } + + // and ::= sub ('&' sub)* + fn parse_and( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> Result<Option<TokenTree>, Error> { + self.parse_binop(tok, it, out, '&', Self::parse_sub, "intersection") + } + + // xor ::= and ('&' and)* + fn parse_xor( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> Result<Option<TokenTree>, Error> { + self.parse_binop(tok, it, out, '^', Self::parse_and, "symmetric_difference") + } + + // or ::= xor ('|' xor)* + pub fn parse_or( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> 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, Error> { + let mut pos = Span::call_site(); + let mut typ = proc_macro2::TokenStream::new(); + + // Gobble everything up to an `@` sign, which is followed by a + // parenthesized expression; that is, all token trees except the + // last two form the type. + let next = loop { + let tok = it.next(); + if let Some(ref t) = tok { + pos = t.span(); + } + match tok { + None => break None, + Some(TT::Punct(ref p)) if p.as_char() == '@' => { + let tok = it.next(); + if let Some(ref t) = tok { + pos = t.span(); + } + break tok; + } + Some(x) => typ.extend(Some(x)), + } + }; + + let Some(tok) = next else { + return Err(Error::new( + pos, + "expected expression, do not call this macro directly", + )); + }; + let TT::Group(ref _group) = tok else { + return Err(Error::new( + tok.span(), + "expected parenthesis, do not call this macro directly", + )); + }; + let mut out = TokenStream::new(); + let state = Self { + typ: TT::Group(Group::new(Delimiter::None, typ)), + }; + + let next = state.parse_primary(tok, it, &mut out)?; + + // 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(Error::new(tok.span(), format!("unexpected token {tok}"))); + } + Ok(out) + } +} diff --git a/rust/qemu-macros/src/lib.rs b/rust/qemu-macros/src/lib.rs new file mode 100644 index 0000000..3e21b67 --- /dev/null +++ b/rust/qemu-macros/src/lib.rs @@ -0,0 +1,407 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> +// SPDX-License-Identifier: GPL-2.0-or-later + +use proc_macro::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, parse_quote, + punctuated::Punctuated, + spanned::Spanned, + token::Comma, + Attribute, Data, DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, + Variant, +}; +mod bits; +use bits::BitsConstInternal; + +#[cfg(test)] +mod tests; + +fn get_fields<'a>( + input: &'a DeriveInput, + msg: &str, +) -> Result<&'a Punctuated<Field, Comma>, Error> { + let Data::Struct(ref s) = &input.data else { + return Err(Error::new( + input.ident.span(), + format!("Struct required for {msg}"), + )); + }; + let Fields::Named(ref fs) = &s.fields else { + 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, Error> { + let Data::Struct(ref s) = &input.data else { + return Err(Error::new( + input.ident.span(), + format!("Struct required for {msg}"), + )); + }; + let Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }) = &s.fields else { + return Err(Error::new( + s.fields.span(), + format!("Tuple struct required for {msg}"), + )); + }; + if unnamed.len() != 1 { + 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<(), Error> { + let expected = parse_quote! { #[repr(C)] }; + + if input.attrs.iter().any(|attr| attr == &expected) { + Ok(()) + } else { + Err(Error::new( + input.ident.span(), + format!("#[repr(C)] required for {msg}"), + )) + } +} + +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(Error::new( + input.ident.span(), + format!("#[repr(transparent)] required for {msg}"), + )) + } +} + +fn derive_object_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> { + is_c_repr(&input, "#[derive(Object)]")?; + + let name = &input.ident; + let parent = &get_fields(&input, "#[derive(Object)]")? + .get(0) + .ok_or_else(|| { + Error::new( + input.ident.span(), + "#[derive(Object)] requires a parent field", + ) + })? + .ident; + + Ok(quote! { + ::common::assert_field_type!(#name, #parent, + ::qom::ParentField<<#name as ::qom::ObjectImpl>::ParentType>); + + ::util::module_init! { + MODULE_INIT_QOM => unsafe { + ::qom::type_register_static(&<#name as ::qom::ObjectImpl>::TYPE_INFO); + } + } + }) +} + +#[proc_macro_derive(Object)] +pub fn derive_object(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + 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, Error> { + is_transparent_repr(&input, "#[derive(Wrapper)]")?; + + let name = &input.ident; + let field = &get_unnamed_field(&input, "#[derive(Wrapper)]")?; + let typ = &field.ty; + + Ok(quote! { + unsafe impl ::common::opaque::Wrapper for #name { + type Wrapped = <#typ as ::common::opaque::Wrapper>::Wrapped; + } + impl #name { + pub unsafe fn from_raw<'a>(ptr: *mut <Self as ::common::opaque::Wrapper>::Wrapped) -> &'a Self { + let ptr = ::std::ptr::NonNull::new(ptr).unwrap().cast::<Self>(); + unsafe { ptr.as_ref() } + } + + pub const fn as_mut_ptr(&self) -> *mut <Self as ::common::opaque::Wrapper>::Wrapped { + self.0.as_mut_ptr() + } + + pub const fn as_ptr(&self) -> *const <Self as ::common::opaque::Wrapper>::Wrapped { + self.0.as_ptr() + } + + pub const fn as_void_ptr(&self) -> *mut ::core::ffi::c_void { + self.0.as_void_ptr() + } + + pub const fn raw_get(slot: *mut Self) -> *mut <Self as ::common::opaque::Wrapper>::Wrapped { + slot.cast() + } + } + }) +} + +#[derive(Debug)] +enum DevicePropertyName { + CStr(syn::LitCStr), + Str(syn::LitStr), +} + +impl Parse for DevicePropertyName { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let lo = input.lookahead1(); + if lo.peek(syn::LitStr) { + Ok(Self::Str(input.parse()?)) + } else if lo.peek(syn::LitCStr) { + Ok(Self::CStr(input.parse()?)) + } else { + Err(lo.error()) + } + } +} + +#[derive(Default, Debug)] +struct DeviceProperty { + rename: Option<DevicePropertyName>, + bitnr: Option<syn::Expr>, + defval: Option<syn::Expr>, +} + +impl DeviceProperty { + fn parse_from(&mut self, a: &Attribute) -> syn::Result<()> { + use attrs::{set, with, Attrs}; + let mut parser = Attrs::new(); + parser.once("rename", with::eq(set::parse(&mut self.rename))); + parser.once("bit", with::eq(set::parse(&mut self.bitnr))); + parser.once("default", with::eq(set::parse(&mut self.defval))); + a.parse_args_with(&mut parser) + } + + fn parse(a: &Attribute) -> syn::Result<Self> { + let mut retval = Self::default(); + retval.parse_from(a)?; + Ok(retval) + } +} + +#[proc_macro_derive(Device, attributes(property))] +pub fn derive_device(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + derive_device_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +fn derive_device_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> { + is_c_repr(&input, "#[derive(Device)]")?; + let properties: Vec<(syn::Field, DeviceProperty)> = get_fields(&input, "#[derive(Device)]")? + .iter() + .flat_map(|f| { + f.attrs + .iter() + .filter(|a| a.path().is_ident("property")) + .map(|a| Ok((f.clone(), DeviceProperty::parse(a)?))) + }) + .collect::<Result<Vec<_>, Error>>()?; + let name = &input.ident; + let mut properties_expanded = vec![]; + + for (field, prop) in properties { + let DeviceProperty { + rename, + bitnr, + defval, + } = prop; + let field_name = field.ident.unwrap(); + macro_rules! str_to_c_str { + ($value:expr, $span:expr) => {{ + let (value, span) = ($value, $span); + let cstr = std::ffi::CString::new(value.as_str()).map_err(|err| { + Error::new( + span, + format!( + "Property name `{value}` cannot be represented as a C string: {err}" + ), + ) + })?; + let cstr_lit = syn::LitCStr::new(&cstr, span); + Ok(quote! { #cstr_lit }) + }}; + } + + let prop_name = rename.map_or_else( + || str_to_c_str!(field_name.to_string(), field_name.span()), + |prop_rename| -> Result<proc_macro2::TokenStream, Error> { + match prop_rename { + DevicePropertyName::CStr(cstr_lit) => Ok(quote! { #cstr_lit }), + DevicePropertyName::Str(str_lit) => { + str_to_c_str!(str_lit.value(), str_lit.span()) + } + } + }, + )?; + let field_ty = field.ty.clone(); + let qdev_prop = if bitnr.is_none() { + quote! { <#field_ty as ::hwcore::QDevProp>::BASE_INFO } + } else { + quote! { <#field_ty as ::hwcore::QDevProp>::BIT_INFO } + }; + let bitnr = bitnr.unwrap_or(syn::Expr::Verbatim(quote! { 0 })); + let set_default = defval.is_some(); + let defval = defval.unwrap_or(syn::Expr::Verbatim(quote! { 0 })); + properties_expanded.push(quote! { + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(#prop_name), + info: #qdev_prop, + offset: ::core::mem::offset_of!(#name, #field_name) as isize, + bitnr: #bitnr, + set_default: #set_default, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: #defval as u64 }, + ..::common::Zeroable::ZERO + } + }); + } + + Ok(quote_spanned! {input.span() => + unsafe impl ::hwcore::DevicePropertiesImpl for #name { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + #(#properties_expanded),* + ]; + } + }) +} + +#[proc_macro_derive(Wrapper)] +pub fn derive_opaque(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + 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, 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)?; + 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(Error::new( + input.ident.span(), + format!("#[repr(u8/u16/u32/u64) required for {msg}"), + )) +} + +fn get_variants(input: &DeriveInput) -> Result<&Punctuated<Variant, Comma>, Error> { + let Data::Enum(ref e) = &input.data else { + 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(Error::new( + v.fields.span(), + "Cannot derive TryInto for enum with non-unit variants.", + )); + } + Ok(&e.variants) +} + +#[rustfmt::skip::macros(quote)] +fn derive_tryinto_body( + name: &Ident, + variants: &Punctuated<Variant, Comma>, + repr: &Path, +) -> 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;)* + match value { + #(#discriminants => core::result::Result::Ok(#name::#discriminants),)* + _ => core::result::Result::Err(value), + } + }) +} + +#[rustfmt::skip::macros(quote)] +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)?; + let errmsg = format!("invalid value for {name}"); + + Ok(quote! { + impl #name { + #[allow(dead_code)] + pub const fn into_bits(self) -> #repr { + self as #repr + } + + #[allow(dead_code)] + pub const fn from_bits(value: #repr) -> Self { + match ({ + #body + }) { + Ok(x) => x, + Err(_) => panic!(#errmsg), + } + } + } + impl core::convert::TryFrom<#repr> for #name { + type Error = #repr; + + #[allow(ambiguous_associated_items)] + fn try_from(value: #repr) -> Result<Self, #repr> { + #body + } + } + }) +} + +#[proc_macro_derive(TryInto)] +pub fn derive_tryinto(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + derive_tryinto_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro] +pub fn bits_const_internal(ts: TokenStream) -> TokenStream { + let ts = proc_macro2::TokenStream::from(ts); + let mut it = ts.into_iter(); + + BitsConstInternal::parse(&mut it) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/rust/qemu-macros/src/tests.rs b/rust/qemu-macros/src/tests.rs new file mode 100644 index 0000000..ac998d2 --- /dev/null +++ b/rust/qemu-macros/src/tests.rs @@ -0,0 +1,347 @@ +// 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 = &[$( quote! { ::core::compile_error! { $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! { #(#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_device() { + // Check that repr(C) is used + derive_compile_fail!( + derive_device_or_error, + quote! { + #[derive(Device)] + struct Foo { + _unused: [u8; 0], + } + }, + "#[repr(C)] required for #[derive(Device)]" + ); + // Check that invalid/misspelled attributes raise an error + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(defalt = true)] + migrate_clock: bool, + } + }, + "Expected one of `bit`, `default` or `rename`" + ); + // Check that repeated attributes are not allowed: + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(rename = "migrate-clk", rename = "migrate-clk", default = true)] + migrate_clock: bool, + } + }, + "Duplicate argument", + "Already used here", + ); + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(default = true, default = true)] + migrate_clock: bool, + } + }, + "Duplicate argument", + "Already used here", + ); + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(bit = 0, bit = 1)] + flags: u32, + } + }, + "Duplicate argument", + "Already used here", + ); + // Check that the field name is preserved when `rename` isn't used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(default = true)] + migrate_clock: bool, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"migrate_clock"), + info: <bool as ::hwcore::QDevProp>::BASE_INFO, + offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, + bitnr: 0, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); + // Check that `rename` value is used for the property name when used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(rename = "migrate-clk", default = true)] + migrate_clock: bool, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"migrate-clk"), + info: <bool as ::hwcore::QDevProp>::BASE_INFO, + offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, + bitnr: 0, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); + // Check that `bit` value is used for the bit property without default + // value (note: though C macro (e.g., DEFINE_PROP_BIT) always requires + // default value, Rust side allows to default this field to "0"): + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(bit = 3)] + flags: u32, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"flags"), + info: <u32 as ::hwcore::QDevProp>::BIT_INFO, + offset: ::core::mem::offset_of!(DummyState, flags) as isize, + bitnr: 3, + set_default: false, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: 0 as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); + // Check that `bit` value is used for the bit property when used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(bit = 3, default = true)] + flags: u32, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"flags"), + info: <u32 as ::hwcore::QDevProp>::BIT_INFO, + offset: ::core::mem::offset_of!(DummyState, flags) as isize, + bitnr: 3, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); + // Check that `bit` value is used for the bit property with rename when used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(rename = "msi", bit = 3, default = false)] + flags: u64, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"msi"), + info: <u64 as ::hwcore::QDevProp>::BIT_INFO, + offset: ::core::mem::offset_of!(DummyState, flags) as isize, + bitnr: 3, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: false as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); +} + +#[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! { + ::common::assert_field_type!( + Foo, + _unused, + ::qom::ParentField<<Foo as ::qom::ObjectImpl>::ParentType> + ); + ::util::module_init! { + MODULE_INIT_QOM => unsafe { + ::qom::type_register_static(&<Foo as ::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), + } + } + } + } + ); +} |