use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Parser, Result};
use syn::{parse_macro_input, parse_quote, DeriveInput, Token};
use super::qbjs;
type QtVersion = u8;
macro_rules! unwrap_parse_error(
($e:expr) => {
match $e {
Ok(x) => x,
Err(e) => { return e.to_compile_error().into() }
}
}
);
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
#[allow(dead_code)]
mod MetaObjectCall {
pub const InvokeMetaMethod: u32 = 0;
pub const ReadProperty: u32 = 1;
pub const WriteProperty: u32 = 2;
pub const ResetProperty: u32 = 3;
pub const QueryPropertyDesignable: u32 = 4;
pub const QueryPropertyScriptable: u32 = 5;
pub const QueryPropertyStored: u32 = 6;
pub const QueryPropertyEditable: u32 = 7;
pub const QueryPropertyUser: u32 = 8;
pub const CreateInstance: u32 = 9;
pub const IndexOfMethod: u32 = 10;
pub const RegisterPropertyMetaType: u32 = 11;
pub const RegisterMethodArgumentMetaType: u32 = 12;
pub const Qt6MetaObjectCallOffset: u32 = QueryPropertyUser - ResetProperty;
}
fn builtin_type(ty: &syn::Type) -> u32 {
if let syn::Type::Tuple(ref tuple) = ty {
return if tuple.elems.is_empty() { 43 } else { 0 };
}
match ty.clone().into_token_stream().to_string().as_ref() {
"bool" => 1,
"i32" => 2,
"u32" => 3,
"i64" => 4,
"u64" => 5,
"f64" => 6,
"i16" => 33,
"i8" => 34,
"u16" => 36,
"u8" => 37,
"f32" => 38,
"QString" => 10,
"QByteArray" => 12,
"QVariant" => 41,
_ => 0,
}
}
trait IsVoid {
fn is_void(&self) -> bool;
}
impl IsVoid for syn::Type {
fn is_void(&self) -> bool {
if let syn::Type::Tuple(tuple) = self {
tuple.elems.is_empty()
} else {
false
}
}
}
fn write_i32(vec: &mut Vec<u8>, val: i32) {
vec.extend_from_slice(&val.to_le_bytes())
}
#[derive(Clone)]
struct MetaMethodParameter {
typ: syn::Type,
name: Option<syn::Ident>,
}
#[derive(Clone)]
struct MetaMethod {
name: syn::Ident,
args: Vec<MetaMethodParameter>,
flags: u32,
ret_type: syn::Type,
}
#[derive(Clone)]
struct MetaProperty {
name: syn::Ident,
typ: syn::Type,
flags: u32,
notify_signal: Option<syn::Ident>,
getter: Option<syn::Ident>,
setter: Option<syn::Ident>,
alias: Option<syn::Ident>,
}
#[derive(Clone)]
struct MetaEnum {
name: syn::Ident,
variants: Vec<syn::Ident>,
}
struct MetaObject {
qt_version: QtVersion,
int_data: Vec<proc_macro2::TokenStream>,
meta_types: Vec<proc_macro2::TokenStream>,
string_data: Vec<String>,
}
impl MetaObject {
fn new_with_qt_version(qt_version: QtVersion) -> Self {
Self {
qt_version,
int_data: Default::default(),
string_data: Default::default(),
meta_types: Default::default(),
}
}
fn build_string_data(&self, target_pointer_width: u32) -> Vec<u8> {
let mut result: Vec<u8> = Vec::new();
let r = &mut result;
if self.qt_version == 5 {
let sizeof_qbytearraydata: i32 = if target_pointer_width == 64 { 24 } else { 16 };
let mut ofs = sizeof_qbytearraydata.checked_mul(self.string_data.len() as i32).unwrap();
for s in self.string_data.iter() {
let len = s.len() as i32;
write_i32(r, -1); write_i32(r, len); write_i32(r, 0); if target_pointer_width == 64 {
write_i32(r, 0); }
write_i32(r, ofs); if target_pointer_width == 64 {
write_i32(r, 0); }
ofs = ofs.checked_add(len).unwrap().checked_add(1).unwrap();
ofs = ofs.checked_sub(sizeof_qbytearraydata).unwrap();
}
} else {
let mut ofs = (self.string_data.len() as i32).checked_mul(2 * 4).unwrap();
for s in self.string_data.iter() {
let len = s.len() as i32;
write_i32(r, ofs);
write_i32(r, len);
ofs = ofs.checked_add(len).unwrap().checked_add(1).unwrap();
}
}
for s in self.string_data.iter() {
r.extend_from_slice(s.as_bytes());
r.push(0); }
result
}
fn push_int(&mut self, i: u32) {
self.int_data.push(quote!(#i));
}
fn add_meta_type(&mut self, ty: &syn::Type) -> u32 {
self.meta_types.push(quote!(#ty));
self.meta_types.len() as u32 - 1
}
fn extend_from_int_slice(&mut self, slice: &[u32]) {
for i in slice {
self.int_data.push(quote!(#i));
}
}
fn compute_int_data(
&mut self,
class_name: String,
properties: &[MetaProperty],
methods: &[MetaMethod],
enums: &[MetaEnum],
signal_count: usize,
) {
let has_notify = properties.iter().any(|p| p.notify_signal.is_some());
self.add_string(class_name);
self.add_string("".to_owned());
let method_size = if self.qt_version == 6 { 6 } else { 5 };
let property_size = if self.qt_version == 6 {
5
} else if has_notify {
4
} else {
3
};
let enum_size = if self.qt_version == 6 { 5 } else { 4 };
let mut offset = 14;
let property_offset = offset + methods.len() as u32 * method_size;
let enum_offset = property_offset + properties.len() as u32 * property_size;
self.extend_from_int_slice(&[
if self.qt_version == 6 { 9 } else { 7 }, 0, 0,
0, methods.len() as u32,
if methods.is_empty() { 0 } else { offset }, properties.len() as u32,
if properties.is_empty() { 0 } else { property_offset }, enums.len() as u32,
if enums.is_empty() { 0 } else { enum_offset }, 0,
0, 0x4, signal_count as u32, ]);
offset = enum_offset + enums.len() as u32 * enum_size;
for p in properties {
self.add_meta_type(&p.typ);
}
for m in methods {
let n = self.add_string(m.name.to_string());
self.extend_from_int_slice(&[n, m.args.len() as u32, offset, 1, m.flags]);
if self.qt_version == 6 {
let r = self.add_meta_type(&m.ret_type);
self.push_int(r);
for a in m.args.iter() {
self.add_meta_type(&a.typ);
}
}
offset += 1 + 2 * m.args.len() as u32;
}
for p in properties {
let n = self.add_string(p.alias.as_ref().unwrap_or(&p.name).to_string());
let type_id = self.add_type(p.typ.clone());
self.extend_from_int_slice(&[n, type_id, p.flags]);
if self.qt_version == 6 {
match p.notify_signal {
None => self.push_int(0 as u32),
Some(ref signal) => self.push_int(
methods
.iter()
.position(|x| x.name == *signal && (x.flags & 0x4) != 0)
.expect("Invalid NOTIFY signal") as u32,
),
};
self.push_int(0); }
}
for e in enums {
let n = self.add_string(e.name.to_string());
if self.qt_version == 5 {
self.extend_from_int_slice(&[n, 0x2, e.variants.len() as u32, offset]);
} else {
self.extend_from_int_slice(&[n, n, 0x2, e.variants.len() as u32, offset]);
}
offset += 2 * e.variants.len() as u32;
}
if self.qt_version == 5 && has_notify {
for p in properties {
match p.notify_signal {
None => self.push_int(0 as u32),
Some(ref signal) => self.push_int(
methods
.iter()
.position(|x| x.name == *signal && (x.flags & 0x4) != 0)
.expect("Invalid NOTIFY signal") as u32,
),
};
}
}
for m in methods {
let ret_type = self.add_type(m.ret_type.clone());
self.push_int(ret_type);
for a in m.args.iter() {
let ty = self.add_type(a.typ.clone());
self.push_int(ty);
}
for a in m.args.iter() {
let n = self.add_string(a.name.clone().into_token_stream().to_string());
self.push_int(n);
}
}
for e in enums {
for v in &e.variants {
let n = self.add_string(v.to_string());
self.push_int(n);
let e_name = &e.name;
self.int_data.push(quote! { #e_name::#v as u32 });
}
}
}
fn add_type(&mut self, ty: syn::Type) -> u32 {
let mut type_id = builtin_type(&ty);
let string = ty.into_token_stream().to_string();
if type_id == 0 {
type_id = self.add_string(string) | 0x80000000 ;
}
type_id
}
fn add_string(&mut self, string: String) -> u32 {
if let Some((pos, _)) = self.string_data.iter().enumerate().find(|(_, val)| *val == &string)
{
return pos as u32;
}
assert!(
self.string_data.len() < i32::MAX as usize,
"String Data: Too many strings registered"
);
assert!(string.len() <= i32::MAX as usize, "String Data: String is too large");
self.string_data.push(string);
self.string_data.len() as u32 - 1
}
}
fn map_method_parameters(
args: &syn::punctuated::Punctuated<syn::FnArg, Token![,]>,
) -> Vec<MetaMethodParameter> {
args.iter()
.filter_map(|x| match x {
syn::FnArg::Typed(cap) => Some(MetaMethodParameter {
name: if let syn::Pat::Ident(ref id) = *cap.pat {
Some(id.ident.clone())
} else {
None
},
typ: (*cap.ty).clone(),
}),
_ => None,
})
.collect()
}
fn map_method_parameters2(
args: &syn::punctuated::Punctuated<syn::BareFnArg, Token![,]>,
) -> Vec<MetaMethodParameter> {
args.iter()
.filter_map(|x| {
if let Some(ref name) = x.name {
Some(MetaMethodParameter { name: Some(name.0.clone()), typ: x.ty.clone() })
} else {
None
}
})
.collect()
}
pub fn generate(input: TokenStream, is_qobject: bool, qt_version: QtVersion) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let mut properties = vec![];
let mut methods = vec![];
let mut signals = vec![];
let mut func_bodies = vec![];
let mut is_plugin = false;
let mut plugin_iid: Option<syn::LitStr> = None;
let crate_ = super::get_crate(&ast);
let mut base: syn::Ident = parse_quote!(QGadget);
let mut base_prop: syn::Ident = parse_quote!(missing_base_class_property);
let mut has_base_property = false;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
if let syn::Data::Struct(ref data) = ast.data {
for f in data.fields.iter() {
use syn::Type::Macro;
if let Macro(ref mac) = f.ty {
if let Some(ref segment) = mac.mac.path.segments.last() {
match segment.ident.to_string().as_ref() {
"qt_property" => {
#[derive(Debug)]
enum Flag {
Notify(syn::Ident),
Read(syn::Ident),
Write(syn::Ident),
Alias(syn::Ident),
Const,
}
impl Parse for Flag {
fn parse(input: ParseStream) -> Result<Self> {
let k = input.parse::<syn::Ident>()?;
if &k == "NOTIFY" {
Ok(Flag::Notify(input.parse()?))
} else if &k == "CONST" {
Ok(Flag::Const)
} else if &k == "READ" {
Ok(Flag::Read(input.parse()?))
} else if &k == "WRITE" {
Ok(Flag::Write(input.parse()?))
} else if &k == "ALIAS" {
Ok(Flag::Alias(input.parse()?))
} else {
Err(input.error("expected a property keyword"))
}
}
}
let property_parser =
|input: ParseStream| -> Result<(syn::Type, Vec<Flag>)> {
Ok((
input.parse()?,
input
.parse::<Option<Token![;]>>()?
.map(|_| -> Result<Vec<Flag>> {
let mut r = Vec::<Flag>::new();
while !input.is_empty() {
r.push(input.parse()?)
}
Ok(r)
})
.unwrap_or_else(|| Ok(Default::default()))?,
))
};
let parsed = unwrap_parse_error!(
property_parser.parse(mac.mac.tokens.clone().into())
);
let mut notify_signal = None;
let mut getter = None;
let mut setter = None;
let mut alias = None;
let mut flags = 1 | 2 | 0x00004000 | 0x00001000 | 0x00010000;
for it in parsed.1 {
match it {
Flag::Notify(i) => {
assert!(
notify_signal.is_none(),
"Duplicate NOTIFY for a property"
);
notify_signal = Some(i);
flags |= 0x00400000;
}
Flag::Const => {
flags |= 0x00000400; flags &= !2; }
Flag::Read(i) => {
assert!(getter.is_none(), "Duplicate READ for a property");
getter = Some(i);
}
Flag::Write(i) => {
assert!(setter.is_none(), "Duplicate READ for a property");
setter = Some(i);
}
Flag::Alias(i) => {
assert!(alias.is_none(), "Duplicate READ for a property");
alias = Some(i);
}
}
}
properties.push(MetaProperty {
name: f.ident.clone().expect("Property does not have a name"),
typ: parsed.0,
flags,
notify_signal,
getter,
setter,
alias,
});
}
"qt_method" => {
let name = f.ident.clone().expect("Method does not have a name");
let (output, args) = if let Ok(method_ast) =
syn::parse::<syn::ItemFn>(mac.mac.tokens.clone().into())
{
assert_eq!(method_ast.sig.ident, name);
let tts = &mac.mac.tokens;
func_bodies.push(quote! { #tts });
let args = map_method_parameters(&method_ast.sig.inputs);
(method_ast.sig.output, args)
} else if let Ok(method_decl) =
syn::parse::<syn::TypeBareFn>(mac.mac.tokens.clone().into())
{
let args = map_method_parameters2(&method_decl.inputs);
(method_decl.output, args)
} else {
panic!("Cannot parse qt_method {}", name);
};
let ret_type = match output {
syn::ReturnType::Default => parse_quote! {()},
syn::ReturnType::Type(_, ref typ) => (**typ).clone(),
};
methods.push(MetaMethod { name, args, flags: 0x2, ret_type });
}
"qt_signal" => {
let parser = syn::punctuated::Punctuated::<syn::FnArg, Token![,]>::parse_terminated;
let args_list =
unwrap_parse_error!(parser.parse(mac.mac.tokens.clone().into()));
let args = map_method_parameters(&args_list);
signals.push(MetaMethod {
name: f.ident.clone().expect("Signal does not have a name"),
args,
flags: 0x2 | 0x4,
ret_type: parse_quote! {()},
});
}
"qt_base_class" => {
let parser = |input: ParseStream| -> Result<syn::Ident> {
input.parse::<Token![trait]>()?;
input.parse()
};
base = unwrap_parse_error!(parser.parse(mac.mac.tokens.clone().into()));
base_prop = f.ident.clone().expect("base prop needs a name");
has_base_property = true;
}
"qt_plugin" => {
is_plugin = true;
let iid: syn::LitStr =
unwrap_parse_error!(syn::parse(mac.mac.tokens.clone().into()));
plugin_iid = Some(iid);
}
_ => {}
}
}
}
for i in f.attrs.iter() {
if let Ok(x) = i.parse_meta() {
if x.path().is_ident("qt_base_class") {
if let syn::Meta::NameValue(mnv) = x {
if let syn::Lit::Str(s) = mnv.lit {
base = unwrap_parse_error!(syn::parse_str(&s.value()));
base_prop = f.ident.clone().expect("base prop needs a name");
has_base_property = true;
} else {
panic!("Can't parse qt_base_class");
}
} else {
panic!("Can't parse qt_base_class");
}
}
}
}
}
} else {
panic!("#[derive(QObject)] is only defined for structs, not for enums!");
}
if is_qobject && !has_base_property {
panic!("#[derive(QObject)] needs at least one field of type qt_base_class!");
}
let mut methods2 = signals.clone();
methods2.extend(methods);
let methods = methods2;
let mut meta_obj = MetaObject::new_with_qt_version(qt_version);
meta_obj.compute_int_data(name.to_string(), &properties, &methods, &[], signals.len());
let str_data = if qt_version == 6 {
let str_data = meta_obj.build_string_data(32);
quote! {
static STRING_DATA : &'static [u8] = & [ #(#str_data),* ];
}
} else {
let str_data32 = meta_obj.build_string_data(32);
let str_data64 = meta_obj.build_string_data(64);
quote! {
#[cfg(target_pointer_width = "64")]
static STRING_DATA : &'static [u8] = & [ #(#str_data64),* ];
#[cfg(target_pointer_width = "32")]
static STRING_DATA : &'static [u8] = & [ #(#str_data32),* ];
}
};
let int_data = meta_obj.int_data;
use self::MetaObjectCall::*;
let (meta_types_init, super_data_getter) = if qt_version == 6 {
let len = meta_obj.meta_types.len();
let meta_types = meta_obj.meta_types;
(
quote!({
#crate_::qmetaobject_lazy_static! {
static ref ARRAY : [usize; #len] = [
#(#crate_::qmetatype_interface_ptr::<#meta_types>(
&::std::ffi::CString::new(stringify!(#meta_types)).unwrap()) as usize),*
];
}
ARRAY.as_ptr() as *const ::std::os::raw::c_void
}),
quote!(
#[cfg(target_os = "windows")]
super_data_getter: None,
),
)
} else {
(quote!(::std::ptr::null()), quote!())
};
let get_object = if is_qobject {
quote! {
let pinned = <#name #ty_generics as #crate_::QObject>::get_from_cpp(o);
#[allow(unused_variables)]
let mut obj = &mut *pinned.as_ptr();
assert_eq!(o, obj.get_cpp_object(), "Internal pointer invalid");
struct Check<'check>(
*mut ::std::os::raw::c_void,
*const (#crate_::QObject + 'check)
);
impl<'check> ::std::ops::Drop for Check<'check> {
fn drop(&mut self) {
assert_eq!(self.0, unsafe { &*self.1 }.get_cpp_object(),
"Internal pointer changed while borrowed");
}
}
let _check = Check(o, obj as *const #crate_::QObject);
}
} else {
quote! {
#[allow(unused_variables)]
let mut obj = ::std::mem::transmute::<*mut ::std::os::raw::c_void, &mut #name #ty_generics>(o);
}
};
let property_meta_call: Vec<_> = properties
.iter()
.enumerate()
.map(|(i, prop)| {
let i = i as u32;
let property_name = &prop.name;
let typ = &prop.typ;
let mut notify = quote!{};
if let Some(ref signal) = prop.notify_signal {
let args_count = methods.iter()
.find(|x| x.name == *signal && (x.flags & 0x4) != 0)
.map_or(0, |s| s.args.len());
let signal: syn::Ident = signal.clone();
notify = match args_count {
0 => quote!{ obj.#signal() },
1 => quote!{ obj.#signal(obj.#property_name.clone()) },
_ => panic!("NOTIFY signal {} for property {} has too many arguments",
signal, property_name),
};
}
let getter = if let Some(ref getter) = prop.getter {
let getter_ident: syn::Ident = getter.clone();
quote!{
let mut tmp : #typ = obj.#getter_ident();
<#typ as #crate_::PropertyType>::pass_to_qt(&mut tmp, *a);
}
} else {
quote!{ <#typ as #crate_::PropertyType>::pass_to_qt(&mut obj.#property_name, *a); }
};
let setter = if let Some(ref setter) = prop.setter {
let setter_ident: syn::Ident = setter.clone();
quote!{
obj.#setter_ident(<#typ as #crate_::PropertyType>::read_from_qt(*a));
}
} else {
quote! {
obj.#property_name = <#typ as #crate_::PropertyType>::read_from_qt(*a);
#notify
}
};
let register_type = if builtin_type(typ) == 0 {
let typ_str = typ.clone().into_token_stream().to_string();
let typ_bytes = typ_str.as_bytes();
quote! {
let name = unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(&[#(#typ_bytes ,)* 0u8]) };
*register_result = <#typ as #crate_::PropertyType>::register_type(name);
}
} else {
quote! {}
};
quote! {
#i => match c {
#ReadProperty => unsafe {
#get_object
#getter
},
#WriteProperty => unsafe {
#get_object
#setter
},
#ResetProperty => {},
#RegisterPropertyMetaType => {
let register_result: &mut i32 = unsafe { &mut *((*a.offset(0)) as *mut i32) };
#register_type
},
_ => {}
}
}
})
.collect();
let method_meta_call: Vec<_> = methods
.iter()
.enumerate()
.map(|(i, method)| {
let i = i as u32;
let method_name: syn::Ident = method.name.clone();
let args_call: Vec<_> = method
.args
.iter()
.enumerate()
.map(|(i, arg)| {
let i = i as isize;
let ty = &arg.typ;
quote! {
(*(*(a.offset(#i + 1)) as *const #ty)).clone()
}
})
.collect();
let call = quote! { obj.#method_name(#(#args_call),*) };
if method.ret_type.is_void() {
quote! { #i => #call, }
} else {
let ret_type = &method.ret_type;
quote! {
#i => {
let r = #call;
let return_value = unsafe { (*a.offset(0)) as *mut #ret_type };
if let Some(return_ref) = unsafe { return_value.as_mut() } {
*return_ref = r;
}
}
}
}
})
.collect();
let register_arguments: Vec<_> = methods
.iter()
.enumerate()
.map(|(fn_i, method)| {
let fn_i = fn_i as u32;
let args: Vec<_> = method
.args
.iter()
.enumerate()
.map(|(arg_i, arg)| {
let arg_i = arg_i as u32;
let typ = &arg.typ;
if builtin_type(&typ) == 0 {
let typ_str = typ.clone().into_token_stream().to_string();
let typ_str = typ_str.as_bytes();
quote! {
#arg_i => {
let name = unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(&[#(#typ_str ,)* 0u8]) };
let ty = <#typ as #crate_::QMetaType>::register(Some(name));
ty
}
}
} else {
quote! {}
}
})
.collect();
quote! {
#fn_i => {
match arg_index {
#(#args)*
_ => -1, }
}
}
})
.collect();
func_bodies.extend(signals.iter().enumerate().map(|(i, signal)| {
let sig_name = &signal.name;
let i = i as u32;
let args_decl: Vec<_> = signal
.args
.iter()
.map(|arg| {
let n = &arg.name;
let ty = &arg.typ;
quote! { #n : #ty }
})
.collect();
let args_ptr: Vec<_> = signal
.args
.iter()
.map(|arg| {
let n = &arg.name;
let ty = &arg.typ;
quote! { unsafe { ::std::mem::transmute::<& #ty, *mut ::std::os::raw::c_void>(& #n) } }
})
.collect();
let array_size = signal.args.len() + 1;
quote! {
#[allow(non_snake_case)]
fn #sig_name(&self #(, #args_decl)*) {
let a: [*mut ::std::os::raw::c_void; #array_size] = [ ::std::ptr::null_mut() #(, #args_ptr)* ];
unsafe {
#crate_::invoke_signal(
(self as &#crate_::QObject).get_cpp_object(),
#name::static_meta_object(),
#i,
&a
)
}
}
}
}));
let index_of_method = signals.iter().enumerate().map(|(i, signal)| {
let sig_name = &signal.name;
quote! {
let offset = unsafe {
let base = ::std::ptr::null() as *const #name #ty_generics;
let field = (&(*base).#sig_name) as *const _ as usize;
field - (base as usize)
};
if signal == offset {
*result = #i as i32;
return;
}
}
});
let base_meta_object = if is_qobject {
quote! { <#name #ty_generics as #base>::get_object_description().meta_object }
} else {
quote! { ::std::ptr::null() }
};
let mo = if ast.generics.params.is_empty() {
quote! {
#crate_::qmetaobject_lazy_static! {
static ref MO: #crate_::QMetaObject = #crate_::QMetaObject {
super_data: #base_meta_object,
#super_data_getter
string_data: STRING_DATA.as_ptr(),
data: INT_DATA.as_ptr(),
static_metacall: Some(static_metacall),
related_meta_objects: ::std::ptr::null(),
meta_types: #meta_types_init,
extra_data: ::std::ptr::null(),
};
};
return &*MO;
}
} else {
let turbo_generics = ty_generics.as_turbofish();
let (ty_generics, turbo_generics) = if ast.generics.type_params().count() != 0 {
(quote!(#ty_generics), quote!(#turbo_generics))
} else {
(quote!(), quote!())
};
quote! {
use ::std::sync::Mutex;
use ::std::collections::HashMap;
use ::std::any::TypeId;
#crate_::qmetaobject_lazy_static! {
static ref HASHMAP: Mutex<HashMap<TypeId, Box<#crate_::QMetaObject>>> =
Mutex::new(HashMap::new());
};
let mut h = HASHMAP.lock().unwrap();
let mo = h.entry(TypeId::of::<#name #ty_generics>()).or_insert_with(
|| Box::new(#crate_::QMetaObject {
super_data: #base_meta_object,
#super_data_getter
string_data: STRING_DATA.as_ptr(),
data: INT_DATA.as_ptr(),
static_metacall: Some(static_metacall #turbo_generics),
related_meta_objects: ::std::ptr::null(),
meta_types: #meta_types_init,
extra_data: ::std::ptr::null(),
}));
return &**mo;
}
};
let qobject_spec_func = if is_qobject {
quote! {
fn get_cpp_object(&self) -> *mut ::std::os::raw::c_void {
self.#base_prop.get()
}
unsafe fn get_from_cpp<'pinned_ref>(
ptr: *mut ::std::os::raw::c_void
) -> #crate_::QObjectPinned<'pinned_ref, Self>
{
let refcell_qobject: *const ::std::cell::RefCell<#crate_::QObject> = (<#name #ty_generics as #base>::get_object_description().get_rust_refcell)(ptr);
let refcell_type: &::std::cell::RefCell<#name #ty_generics> = ::std::mem::transmute::<_, (&::std::cell::RefCell<#name #ty_generics>, *const())>(refcell_qobject).0;
return #crate_::QObjectPinned::new(refcell_type);
}
unsafe fn cpp_construct(
pinned: &::std::cell::RefCell<Self>
) -> *mut ::std::os::raw::c_void
{
assert!(pinned.borrow().#base_prop.get().is_null());
let object_ptr = #crate_::QObjectPinned::<#crate_::QObject>::new(pinned as &::std::cell::RefCell<#crate_::QObject>);
let object_ptr_ptr : *const #crate_::QObjectPinned<#crate_::QObject> = &object_ptr;
let rust_pinned = #crate_::QObjectPinned::<dyn #base>::new(pinned as &::std::cell::RefCell<dyn #base>);
let rust_pinned_ptr : *const #crate_::QObjectPinned<dyn #base> = &rust_pinned;
let n = (<#name #ty_generics as #base>::get_object_description().create)(
rust_pinned_ptr as *const ::std::os::raw::c_void,
object_ptr_ptr as *const ::std::os::raw::c_void,
);
pinned.borrow_mut().#base_prop.set(n);
n
}
unsafe fn qml_construct(
pinned: &::std::cell::RefCell<Self>,
mem: *mut ::std::os::raw::c_void,
extra_destruct: extern fn(*mut ::std::os::raw::c_void)
) {
let object_ptr = #crate_::QObjectPinned::<#crate_::QObject>::new(pinned as &::std::cell::RefCell<#crate_::QObject>);
let object_ptr_ptr : *const #crate_::QObjectPinned<#crate_::QObject> = &object_ptr;
let rust_pinned = #crate_::QObjectPinned::<dyn #base>::new(pinned as &::std::cell::RefCell<dyn #base>);
let rust_pinned_ptr : *const #crate_::QObjectPinned<dyn #base> = &rust_pinned;
pinned.borrow_mut().#base_prop.set(mem);
(<#name #ty_generics as #base>::get_object_description().qml_construct)(
mem,
rust_pinned_ptr as *const ::std::os::raw::c_void,
object_ptr_ptr as *const ::std::os::raw::c_void,
extra_destruct
);
}
fn cpp_size() -> usize {
<#name #ty_generics as #base>::get_object_description().size
}
}
} else {
quote! {}
};
let trait_name = if is_qobject {
quote! { QObject }
} else {
quote! { QGadget }
};
let qt6_offset = if qt_version == 6 { Qt6MetaObjectCallOffset } else { 0 };
let mut body = quote! {
#[allow(non_snake_case)]
impl #impl_generics #name #ty_generics #where_clause {
#(#func_bodies)*
}
impl #impl_generics #crate_::#trait_name for #name #ty_generics #where_clause {
fn meta_object(&self) -> *const #crate_::QMetaObject {
Self::static_meta_object()
}
fn static_meta_object() -> *const #crate_::QMetaObject {
#str_data
static INT_DATA : &'static [u32] = & [ #(#int_data),* ];
#[allow(unused_variables)]
extern "C" fn static_metacall #impl_generics (
o: *mut ::std::os::raw::c_void,
c: u32,
idx: u32,
a: *const *mut ::std::os::raw::c_void
) #where_clause
{
if c == #InvokeMetaMethod {
unsafe {
#get_object
match idx {
#(#method_meta_call)*
_ => {}
}
}
} else if c == #IndexOfMethod - #qt6_offset {
let result: &mut i32 = unsafe { &mut *((*a.offset(0)) as *mut i32) };
let signal: usize = unsafe { *((*a.offset(1)) as *const usize) };
#(#index_of_method)*
} else if c == #RegisterMethodArgumentMetaType - #qt6_offset {
let arg_type: &mut i32 = unsafe { &mut *((*a.offset(0)) as *mut i32) };
let arg_index: u32 = unsafe { *((*a.offset(1)) as *const u32) };
*arg_type = match idx {
#(#register_arguments)*
_ => -1, }
} else {
match idx {
#(#property_meta_call)*
_ => {}
}
}
}
#mo
}
#qobject_spec_func
}
};
if is_plugin {
use crate::qbjs::Value;
let mut object_data: Vec<(&'static str, Value)> = vec![
("IID", Value::String(plugin_iid.unwrap().value())),
("className", Value::String(name.to_string())),
("version", Value::Double(f64::from(0x050100))),
("debug", Value::Bool(cfg!(debug_assertions))),
];
object_data.sort_by(|a, b| a.0.cmp(b.0));
let plugin_data = qbjs::serialize(&object_data);
let plugin_data_size = plugin_data.len();
body = quote! {
#body
#[cfg_attr(target_os = "macos", link_section = "__TEXT,qtmetadata")]
#[cfg_attr(not(target_os = "macos"), link_section = ".qtmetadata")]
#[no_mangle]
#[allow(non_upper_case_globals)]
pub static qt_pluginMetaData: [u8 ; 20 + #plugin_data_size] = [
b'Q', b'T', b'M', b'E', b'T', b'A', b'D', b'A', b'T', b'A', b' ', b' ',
b'q', b'b', b'j', b's', 1, 0, 0, 0,
#(#plugin_data),*
];
#[no_mangle]
pub extern fn qt_plugin_query_metadata() -> *const u8 {
qt_pluginMetaData.as_ptr()
}
#[no_mangle]
pub extern fn qt_plugin_instance() -> *mut ::std::os::raw::c_void {
#crate_::into_leaked_cpp_ptr(#name::default())
}
}
}
body.into()
}
fn is_valid_repr_attribute(attribute: &syn::Attribute) -> bool {
match attribute.parse_meta() {
Ok(syn::Meta::List(list)) => {
if list.path.is_ident("repr") && list.nested.len() == 1 {
match &list.nested[0] {
syn::NestedMeta::Meta(syn::Meta::Path(word)) => {
const ACCEPTABLE_REPRESENTATIONS: &[&str] =
&["u8", "u16", "u32", "i8", "i16", "i32", "C"];
ACCEPTABLE_REPRESENTATIONS.iter().any(|w| word.is_ident(w))
}
_ => false,
}
} else {
false
}
}
_ => false,
}
}
pub fn generate_enum(input: TokenStream, qt_version: QtVersion) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let mut is_repr_explicit = false;
for attr in &ast.attrs {
is_repr_explicit |= is_valid_repr_attribute(attr);
}
if !is_repr_explicit {
panic!(
"#[derive(QEnum)] only support enum with explicit #[repr(*)], \
possible representations are u8, u16, u32, i8, i16, i32, C"
)
}
let crate_ = super::get_crate(&ast);
let mut meta_enum = MetaEnum { name: name.clone(), variants: Vec::new() };
if let syn::Data::Enum(ref data) = ast.data {
for variant in data.variants.iter() {
match &variant.fields {
syn::Fields::Unit => {}
_ => panic!("#[derive(QEnum)] only support field-less enum"),
}
let var_name = &variant.ident;
meta_enum.variants.push(var_name.clone());
}
} else {
panic!("#[derive(QEnum)] is only defined for enums, not for structs!");
}
let enums = vec![meta_enum];
let mut meta_obj = MetaObject::new_with_qt_version(qt_version);
meta_obj.compute_int_data(name.to_string(), &[], &[], &enums, 0);
let str_data = if qt_version == 6 {
let str_data = meta_obj.build_string_data(32);
quote! {
static STRING_DATA : &'static [u8] = & [ #(#str_data),* ];
}
} else {
let str_data32 = meta_obj.build_string_data(32);
let str_data64 = meta_obj.build_string_data(64);
quote! {
#[cfg(target_pointer_width = "64")]
static STRING_DATA : &'static [u8] = & [ #(#str_data64),* ];
#[cfg(target_pointer_width = "32")]
static STRING_DATA : &'static [u8] = & [ #(#str_data32),* ];
}
};
let int_data = meta_obj.int_data;
let super_data_getter = if qt_version == 6 {
quote!(
#[cfg(target_os = "windows")]
super_data_getter: None,
)
} else {
quote!()
};
let mo = if ast.generics.params.is_empty() {
quote! {
#crate_::qmetaobject_lazy_static! {
static ref MO: #crate_::QMetaObject = #crate_::QMetaObject {
super_data: ::std::ptr::null(),
#super_data_getter
string_data: STRING_DATA.as_ptr(),
data: INT_DATA.as_ptr(),
static_metacall: None,
related_meta_objects: ::std::ptr::null(),
meta_types: ::std::ptr::null(),
extra_data: ::std::ptr::null(),
};
};
return &*MO;
}
} else {
panic!("#[derive(QEnum)] is only defined for C enums, doesn't support generics");
};
let body = quote! {
impl #crate_::QEnum for #name {
fn static_meta_object() -> *const #crate_::QMetaObject {
#str_data
static INT_DATA : &'static [u32] = &[ #(#int_data),* ];
#mo
}
}
};
body.into()
}