Skip to content

Commit ad20a3f

Browse files
committed
Add #[derive(QEnum)]
see rust-lang/rust#8730
1 parent 7603e50 commit ad20a3f

File tree

5 files changed

+227
-23
lines changed

5 files changed

+227
-23
lines changed

qmetaobject/src/lib.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,16 @@ pub trait QGadget {
409409
Self: Sized;
410410
}
411411

412+
/// Trait that is implemented by the QEnum custom derive macro
413+
///
414+
/// Do not implement this trait yourself, use `#[derive(QEnum)]`.
415+
pub trait QEnum {
416+
/// Returns a pointer to a meta object
417+
fn static_meta_object() -> *const QMetaObject
418+
where
419+
Self: Sized;
420+
}
421+
412422
#[doc(hidden)]
413423
#[no_mangle]
414424
pub unsafe extern "C" fn RustObject_metaObject(p: *mut RefCell<QObject>) -> *const QMetaObject {
@@ -445,7 +455,8 @@ pub struct QMetaObject {
445455
pub superdata: *const QMetaObject,
446456
pub string_data: *const u8,
447457
pub data: *const u32,
448-
pub static_metacall: extern "C" fn(o: *mut c_void, c: u32, idx: u32, a: *const *mut c_void),
458+
pub static_metacall:
459+
Option<extern "C" fn(o: *mut c_void, c: u32, idx: u32, a: *const *mut c_void)>,
449460
pub r: *const c_void,
450461
pub e: *const c_void,
451462
}

qmetaobject/src/qtdeclarative.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,27 @@ pub fn qml_register_type<T: QObject + Default + Sized>(
239239
})}
240240
}
241241

242+
/// Register the given enum as a QML type
243+
///
244+
/// Refer to the Qt documentation for qmlRegisterUncreatableMetaObject.
245+
pub fn qml_register_enum<T: QEnum>(
246+
uri: &std::ffi::CStr,
247+
version_major: u32,
248+
version_minor: u32,
249+
qml_name: &std::ffi::CStr,
250+
)
251+
{
252+
let uri_ptr = uri.as_ptr();
253+
let qml_name_ptr = qml_name.as_ptr();
254+
let meta_object = T::static_meta_object();
255+
256+
unsafe { cpp!([qml_name_ptr as "char*", uri_ptr as "char*", version_major as "int",
257+
version_minor as "int", meta_object as "const QMetaObject *"]{
258+
qmlRegisterUncreatableMetaObject(*meta_object, uri_ptr, version_major,
259+
version_minor, qml_name_ptr, "Access to enums & flags only");
260+
})}
261+
}
262+
242263
/// A QObject-like trait to inherit from QQuickItem.
243264
///
244265
/// Work in progress

qmetaobject/tests/tests.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,3 +606,44 @@ fn panic_when_moved_setter() {
606606
do_test(my_obj, "Item { function doTest() { _obj.prop_y = 45; } }");
607607
}
608608
*/
609+
610+
#[derive(QEnum)]
611+
#[repr(u8)]
612+
enum MyEnum {
613+
None,
614+
First = 1,
615+
Four = 4,
616+
}
617+
618+
#[derive(QObject, Default)]
619+
struct MyEnumObject {
620+
base: qt_base_class!(trait QObject),
621+
}
622+
623+
#[test]
624+
fn enum_properties() {
625+
qml_register_enum::<MyEnum>(
626+
CStr::from_bytes_with_nul(b"MyEnumLib\0").unwrap(),
627+
1,
628+
0,
629+
CStr::from_bytes_with_nul(b"MyEnum\0").unwrap(),
630+
);
631+
let my_obj = MyObject::default();
632+
assert!(do_test(
633+
my_obj,
634+
"import MyEnumLib 1.0
635+
Item {
636+
function doTest() {
637+
if(MyEnum.None != 0) {
638+
return false;
639+
}
640+
if(MyEnum.First != 1) {
641+
return false;
642+
}
643+
if(MyEnum.Four != 4) {
644+
return false;
645+
}
646+
return true;
647+
}}"
648+
));
649+
}

qmetaobject_impl/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ pub fn qgadget_impl(input: TokenStream) -> TokenStream {
7070
qobject_impl::generate(input, false)
7171
}
7272

73+
/// Implementation of #[derive(QEnum)]
74+
#[proc_macro_derive(QEnum, attributes(QMetaObjectCrate))]
75+
pub fn qenum_impl(input: TokenStream) -> TokenStream {
76+
qobject_impl::generate_enum(input)
77+
}
78+
7379
/// Implementation of the qmetaobject::qrc! macro
7480
#[proc_macro]
7581
pub fn qrc_internal(input: TokenStream) -> TokenStream {

qmetaobject_impl/src/qobject_impl.rs

Lines changed: 147 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,15 @@ struct MetaProperty {
110110
alias: Option<syn::Ident>,
111111
}
112112

113+
#[derive(Clone)]
114+
struct MetaEnum {
115+
name: syn::Ident,
116+
variants: Vec<syn::Ident>,
117+
}
118+
113119
#[derive(Default)]
114120
struct MetaObject {
115-
int_data: Vec<u32>,
121+
int_data: Vec<proc_macro2::TokenStream>,
116122
string_data: Vec<String>,
117123
}
118124
impl MetaObject {
@@ -144,58 +150,75 @@ impl MetaObject {
144150
result
145151
}
146152

153+
fn push_int(&mut self, i: u32) {
154+
self.int_data.push(quote!(#i));
155+
}
156+
157+
fn extend_from_int_slice(&mut self, slice: &[u32]) {
158+
for i in slice {
159+
self.int_data.push(quote!(#i));
160+
}
161+
}
162+
147163
fn compute_int_data(
148164
&mut self,
149165
class_name: String,
150166
properties: &[MetaProperty],
151167
methods: &[MetaMethod],
168+
enums: &[MetaEnum],
152169
signal_count: usize,
153170
) {
154-
let has_notify = properties.iter().any(|p| p.notify_signal.is_some());
155-
171+
let has_notify = properties.iter().any(|p| p.notify_signal.is_some());
156172
self.add_string(class_name);
157173
self.add_string("".to_owned());
158174

159175
let mut offset = 14;
160176
let property_offset = offset + methods.len() as u32 * 5;
161-
//...
177+
let enum_offset = property_offset + properties.len() as u32 * (if has_notify { 4 } else { 3 });
162178

163-
self.int_data.extend_from_slice(&[
164-
7, // revision
179+
self.extend_from_int_slice(&[
180+
8, // revision
165181
0, // classname
166182
0,
167183
0, // class info count and offset
168184
methods.len() as u32,
169-
offset, // method count and offset
185+
if methods.len() == 0 { 0 } else { offset }, // method count and offset
170186
properties.len() as u32,
171-
property_offset, // properties count and offset
172-
0,
173-
0, // enum count and offset
187+
if properties.len() == 0 { 0 } else { property_offset }, // properties count and offset
188+
enums.len() as u32,
189+
if enums.len() == 0 { 0 } else { enum_offset }, // enum count and offset
174190
0,
175191
0, // constructor count and offset
176192
0x4, // flags (PropertyAccessInStaticMetaCall)
177193
signal_count as u32, // signalCount
178194
]);
179195

180-
offset = property_offset + properties.len() as u32 * (if has_notify { 4 } else { 3 });
196+
offset = enum_offset + enums.len() as u32 * 5;
181197

182198
for m in methods {
183199
let n = self.add_string(m.name.to_string());
184-
self.int_data
185-
.extend_from_slice(&[n, m.args.len() as u32, offset, 1, m.flags]);
200+
self.extend_from_int_slice(&[n, m.args.len() as u32, offset, 1, m.flags]);
186201
offset += 1 + 2 * m.args.len() as u32;
187202
}
188203

189204
for p in properties {
190205
let n = self.add_string(p.alias.as_ref().unwrap_or(&p.name).to_string());
191206
let type_id = self.add_type(p.typ.clone());
192-
self.int_data.extend_from_slice(&[n, type_id, p.flags]);
207+
self.extend_from_int_slice(&[n, type_id, p.flags]);
193208
}
209+
210+
for e in enums {
211+
let n = self.add_string(e.name.to_string());
212+
// name, alias, flag, count, data offset
213+
self.extend_from_int_slice(&[n, n, 0x2, e.variants.len() as u32, offset]);
214+
offset += 2 * e.variants.len() as u32;
215+
}
216+
194217
if has_notify {
195218
for p in properties {
196219
match p.notify_signal {
197-
None => self.int_data.push(0 as u32),
198-
Some(ref signal) => self.int_data.push(
220+
None => self.push_int(0 as u32),
221+
Some(ref signal) => self.push_int(
199222
methods
200223
.iter()
201224
.position(|x| x.name == *signal && (x.flags & 0x4) != 0)
@@ -208,16 +231,26 @@ impl MetaObject {
208231
for m in methods {
209232
// return type
210233
let ret_type = self.add_type(m.ret_type.clone());
211-
self.int_data.push(ret_type);
234+
self.push_int(ret_type);
212235
// types
213236
for a in m.args.iter() {
214237
let ty = self.add_type(a.typ.clone());
215-
self.int_data.push(ty);
238+
self.push_int(ty);
216239
}
217240
// names
218241
for a in m.args.iter() {
219242
let n = self.add_string(a.name.clone().into_token_stream().to_string());
220-
self.int_data.push(n);
243+
self.push_int(n);
244+
}
245+
}
246+
247+
for e in enums {
248+
for v in &e.variants {
249+
let n = self.add_string(v.to_string());
250+
// name, value
251+
self.push_int(n);
252+
let e_name = &e.name;
253+
self.int_data.push(quote!{ #e_name::#v as u32 });
221254
}
222255
}
223256
}
@@ -487,7 +520,7 @@ pub fn generate(input: TokenStream, is_qobject: bool) -> TokenStream {
487520
let methods = methods2;
488521

489522
let mut mo: MetaObject = Default::default();
490-
mo.compute_int_data(name.to_string(), &properties, &methods, signals.len());
523+
mo.compute_int_data(name.to_string(), &properties, &methods, &[], signals.len());
491524

492525
let str_data32 = mo.build_string_data(32);
493526
let str_data64 = mo.build_string_data(64);
@@ -710,7 +743,7 @@ pub fn generate(input: TokenStream, is_qobject: bool) -> TokenStream {
710743
superdata: #base_meta_object,
711744
string_data: STRING_DATA.as_ptr(),
712745
data: INT_DATA.as_ptr(),
713-
static_metacall: static_metacall,
746+
static_metacall: Some(static_metacall),
714747
r: ::std::ptr::null(),
715748
e: ::std::ptr::null(),
716749
};};
@@ -739,7 +772,7 @@ pub fn generate(input: TokenStream, is_qobject: bool) -> TokenStream {
739772
superdata: #base_meta_object,
740773
string_data: STRING_DATA.as_ptr(),
741774
data: INT_DATA.as_ptr(),
742-
static_metacall: static_metacall #turbo_generics,
775+
static_metacall: Some(static_metacall #turbo_generics),
743776
r: ::std::ptr::null(),
744777
e: ::std::ptr::null(),
745778
}));
@@ -885,3 +918,95 @@ pub fn generate(input: TokenStream, is_qobject: bool) -> TokenStream {
885918
}
886919
body.into()
887920
}
921+
922+
fn is_valid_repr_attribute(attribute: &syn::Attribute) -> bool {
923+
match attribute.parse_meta() {
924+
Ok(syn::Meta::List(list)) => {
925+
if list.ident.to_string() == "repr" && list.nested.len() == 1 {
926+
match &list.nested[0] {
927+
syn::NestedMeta::Meta(syn::Meta::Word(word)) => {
928+
let acceptables = vec!["u8", "u16", "u32", "i8", "i16", "i32"];
929+
let word = word.to_string();
930+
acceptables.iter().any(|x| *x == word.to_string())
931+
}
932+
_ => false,
933+
}
934+
} else {
935+
false
936+
}
937+
}
938+
_ => false
939+
}
940+
}
941+
942+
pub fn generate_enum(input: TokenStream) -> TokenStream {
943+
let ast = parse_macro_input!(input as syn::DeriveInput);
944+
945+
let name = &ast.ident;
946+
947+
let mut is_repr_explicit = false;
948+
for attr in &ast.attrs {
949+
is_repr_explicit |= is_valid_repr_attribute(attr);
950+
}
951+
if !is_repr_explicit {
952+
panic!("#[derive(QEnum)] only support enum with explicit #[repr(*)], possible integer type are u8, u16, u32, i8, i16, i32.")
953+
}
954+
955+
let crate_ = super::get_crate(&ast);
956+
let mut meta_enum = MetaEnum {
957+
name: name.clone(),
958+
variants: Vec::new()
959+
};
960+
961+
if let syn::Data::Enum(ref data) = ast.data {
962+
for variant in data.variants.iter() {
963+
match &variant.fields {
964+
syn::Fields::Unit => {}
965+
// TODO report error with span
966+
_ => panic!("#[derive(QEnum)] only support field-less enum")
967+
}
968+
969+
let var_name = &variant.ident;
970+
meta_enum.variants.push(var_name.clone());
971+
}
972+
} else {
973+
panic!("#[derive(QEnum)] is only defined for enums, not for structs!");
974+
}
975+
976+
let enums = vec![meta_enum];
977+
let mut mo: MetaObject = Default::default();
978+
mo.compute_int_data(name.to_string(), &[], &[], &enums, 0);
979+
let str_data32 = mo.build_string_data(32);
980+
let str_data64 = mo.build_string_data(64);
981+
let int_data = mo.int_data;
982+
983+
let mo = if ast.generics.params.is_empty() {
984+
quote! {
985+
qmetaobject_lazy_static! { static ref MO: #crate_::QMetaObject = #crate_::QMetaObject {
986+
superdata: ::std::ptr::null(),
987+
string_data: STRING_DATA.as_ptr(),
988+
data: INT_DATA.as_ptr(),
989+
static_metacall: None,
990+
r: ::std::ptr::null(),
991+
e: ::std::ptr::null(),
992+
};};
993+
return &*MO;
994+
}
995+
} else {
996+
panic!("#[derive(QEnum)] is only defined for C enums, doesn't support generics");
997+
};
998+
999+
let body = quote!{
1000+
impl #crate_::QEnum for #name {
1001+
fn static_meta_object()->*const #crate_::QMetaObject {
1002+
#[cfg(target_pointer_width = "64")]
1003+
static STRING_DATA : &'static [u8] = & [ #(#str_data64),* ];
1004+
#[cfg(target_pointer_width = "32")]
1005+
static STRING_DATA : &'static [u8] = & [ #(#str_data32),* ];
1006+
static INT_DATA : &'static [u32] = & [ #(#int_data),* ];
1007+
#mo
1008+
}
1009+
}
1010+
};
1011+
body.into()
1012+
}

0 commit comments

Comments
 (0)