Skip to content

Commit 51ff12f

Browse files
bors[bot]chitoyuu
andauthored
Merge #970
970: Generate accessor methods for indexed properties r=Bromeon a=chitoyuu Getters and setters are now generated for indexed properties that are naturally accessible in GDScript (i.e. do not have '/' in the name), like `SpatialMaterial::albedo_texture`. Doc generation for the underlying accessors has also been improved. Close #689 Co-authored-by: Chitose Yuuzaki <chitoyuu@potatoes.gay>
2 parents 0b3341c + 7102813 commit 51ff12f

File tree

4 files changed

+195
-10
lines changed

4 files changed

+195
-10
lines changed

bindings-generator/src/class_docs.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,17 @@ impl GodotXmlDocs {
4444
.find(|node| node.tag_name().name() == "class")
4545
{
4646
if let Some(class_name) = class.attribute("name") {
47-
let methods_node = class
48-
.descendants()
49-
.find(|node| node.tag_name().name() == "methods");
50-
self.parse_methods(class_name, methods_node);
51-
47+
// Parse members first, so more general docs for indexed accessors can be used
48+
// if they exist.
5249
let members_node = class
5350
.descendants()
5451
.find(|node| node.tag_name().name() == "members");
5552
self.parse_members(class_name, members_node);
53+
54+
let methods_node = class
55+
.descendants()
56+
.find(|node| node.tag_name().name() == "methods");
57+
self.parse_methods(class_name, methods_node);
5658
}
5759
}
5860
}
@@ -62,6 +64,26 @@ impl GodotXmlDocs {
6264
for node in members.descendants() {
6365
if node.tag_name().name() == "member" {
6466
if let Some(desc) = node.text() {
67+
if let Some(property_name) = node.attribute("name") {
68+
if !property_name.contains('/') {
69+
if node.has_attribute("setter") {
70+
self.add_fn(
71+
class,
72+
&format!("set_{}", property_name),
73+
desc,
74+
&[],
75+
);
76+
}
77+
if node.has_attribute("getter") {
78+
self.add_fn(
79+
class,
80+
&format!("get_{}", property_name),
81+
desc,
82+
&[],
83+
);
84+
}
85+
}
86+
}
6587
if let Some(func) = node.attribute("setter") {
6688
self.add_fn(class, func, desc, &[]);
6789
}

bindings-generator/src/methods.rs

Lines changed: 133 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use proc_macro2::TokenStream;
66
use quote::{format_ident, quote};
77

88
use std::collections::HashMap;
9-
use std::collections::HashSet;
109

1110
/// Types of icalls.
11+
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
1212
pub(crate) enum IcallType {
1313
Ptr,
1414
Varargs,
@@ -265,6 +265,14 @@ pub(crate) fn generate_methods(
265265
icalls: &mut HashMap<String, MethodSig>,
266266
docs: Option<&GodotXmlDocs>,
267267
) -> TokenStream {
268+
/// Memorized information about generated methods. Used to generate indexed property accessors.
269+
struct Generated {
270+
icall: proc_macro2::Ident,
271+
icall_ty: IcallType,
272+
maybe_unsafe: TokenStream,
273+
maybe_unsafe_reason: &'static str,
274+
}
275+
268276
// Brings values of some types to a type with less information.
269277
fn arg_erase(ty: &Ty, name: &proc_macro2::Ident) -> TokenStream {
270278
match ty {
@@ -280,11 +288,14 @@ pub(crate) fn generate_methods(
280288

281289
Ty::Object(_) => quote! { #name.as_arg_ptr() },
282290

291+
// Allow lossy casting of numeric types into similar primitives, see also to_return_post
292+
Ty::I64 | Ty::F64 | Ty::Bool => quote! { #name as _ },
293+
283294
_ => quote! { #name },
284295
}
285296
}
286297

287-
let mut method_set = HashSet::new();
298+
let mut generated = HashMap::new();
288299
let mut result = TokenStream::new();
289300

290301
for method in &class.methods {
@@ -301,11 +312,9 @@ pub(crate) fn generate_methods(
301312
let mut rust_ret_type = ret_type.to_rust();
302313

303314
// Ensure that methods are not injected several times.
304-
let method_name_string = method_name.to_string();
305-
if method_set.contains(&method_name_string) {
315+
if generated.contains_key(method_name) {
306316
continue;
307317
}
308-
method_set.insert(method_name_string);
309318

310319
let mut params_decl = TokenStream::new();
311320
let mut params_use = TokenStream::new();
@@ -387,7 +396,126 @@ pub(crate) fn generate_methods(
387396
}
388397
}
389398
};
399+
390400
result.extend(output);
401+
402+
generated.insert(
403+
method_name.to_string(),
404+
Generated {
405+
icall,
406+
icall_ty,
407+
maybe_unsafe,
408+
maybe_unsafe_reason,
409+
},
410+
);
411+
}
412+
413+
for property in &class.properties {
414+
if property.index < 0 || property.name.contains('/') {
415+
continue;
416+
}
417+
418+
let property_index = property.index;
419+
let ty = Ty::from_src(&property.type_);
420+
421+
if let Some(Generated {
422+
icall,
423+
icall_ty,
424+
maybe_unsafe,
425+
maybe_unsafe_reason,
426+
}) = generated.get(&property.getter)
427+
{
428+
let rusty_name = rust_safe_name(&property.name);
429+
let rust_ret_type = ty.to_rust();
430+
431+
let method_bind_fetch = {
432+
let method_table = format_ident!("{}MethodTable", class.name);
433+
let rust_method_name = format_ident!("{}", property.getter);
434+
435+
quote! {
436+
let method_bind: *mut sys::godot_method_bind = #method_table::get(get_api()).#rust_method_name;
437+
}
438+
};
439+
440+
let doc_comment = docs
441+
.and_then(|docs| {
442+
docs.get_class_method_desc(
443+
class.name.as_str(),
444+
&format!("get_{}", property.name),
445+
)
446+
})
447+
.unwrap_or("");
448+
449+
let recover = ret_recover(&ty, *icall_ty);
450+
451+
let output = quote! {
452+
#[doc = #doc_comment]
453+
#[doc = #maybe_unsafe_reason]
454+
#[inline]
455+
pub #maybe_unsafe fn #rusty_name(&self) -> #rust_ret_type {
456+
unsafe {
457+
#method_bind_fetch
458+
459+
let ret = crate::icalls::#icall(method_bind, self.this.sys().as_ptr(), #property_index);
460+
461+
#recover
462+
}
463+
}
464+
};
465+
466+
result.extend(output);
467+
}
468+
469+
if let Some(Generated {
470+
icall,
471+
icall_ty,
472+
maybe_unsafe,
473+
maybe_unsafe_reason,
474+
}) = generated.get(&property.setter)
475+
{
476+
let rusty_name = rust_safe_name(&format!("set_{}", property.name));
477+
478+
let rust_arg_ty = ty.to_rust_arg();
479+
let arg_ident = format_ident!("value");
480+
let arg_erased = arg_erase(&ty, &arg_ident);
481+
482+
let method_bind_fetch = {
483+
let method_table = format_ident!("{}MethodTable", class.name);
484+
let rust_method_name = format_ident!("{}", property.setter);
485+
486+
quote! {
487+
let method_bind: *mut sys::godot_method_bind = #method_table::get(get_api()).#rust_method_name;
488+
}
489+
};
490+
491+
let doc_comment = docs
492+
.and_then(|docs| {
493+
docs.get_class_method_desc(
494+
class.name.as_str(),
495+
&format!("set_{}", property.name),
496+
)
497+
})
498+
.unwrap_or("");
499+
500+
let recover = ret_recover(&Ty::Void, *icall_ty);
501+
502+
let output = quote! {
503+
#[doc = #doc_comment]
504+
#[doc = #maybe_unsafe_reason]
505+
#[inline]
506+
pub #maybe_unsafe fn #rusty_name(&self, #arg_ident: #rust_arg_ty) {
507+
unsafe {
508+
#method_bind_fetch
509+
510+
let ret = crate::icalls::#icall(method_bind, self.this.sys().as_ptr(), #property_index, #arg_erased);
511+
512+
#recover
513+
}
514+
}
515+
};
516+
517+
result.extend(output);
518+
}
391519
}
392520

393521
result

test/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod test_async;
99
mod test_constructor;
1010
mod test_derive;
1111
mod test_free_ub;
12+
mod test_indexed_props;
1213
mod test_map_owned;
1314
mod test_register;
1415
mod test_return_leak;
@@ -76,6 +77,7 @@ pub extern "C" fn run_tests(
7677
status &= test_constructor::run_tests();
7778
status &= test_derive::run_tests();
7879
status &= test_free_ub::run_tests();
80+
status &= test_indexed_props::run_tests();
7981
status &= test_map_owned::run_tests();
8082
status &= test_register::run_tests();
8183
status &= test_return_leak::run_tests();
@@ -238,6 +240,7 @@ fn init(handle: InitHandle) {
238240
test_constructor::register(handle);
239241
test_derive::register(handle);
240242
test_free_ub::register(handle);
243+
test_indexed_props::register(handle);
241244
test_map_owned::register(handle);
242245
test_register::register(handle);
243246
test_return_leak::register(handle);

test/src/test_indexed_props.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use gdnative::core_types::Margin;
2+
use gdnative::prelude::*;
3+
4+
pub(crate) fn run_tests() -> bool {
5+
let mut status = true;
6+
7+
status &= test_indexed_props();
8+
9+
status
10+
}
11+
12+
pub(crate) fn register(_handle: InitHandle) {}
13+
14+
crate::godot_itest! { test_indexed_props {
15+
let control = Control::new();
16+
17+
assert_eq!(0, control.margin_top());
18+
assert_eq!(0, control.margin_left());
19+
20+
control.set_margin_top(42);
21+
22+
assert_eq!(42, control.margin_top());
23+
assert_eq!(42, control.margin(Margin::Top.into()) as i64);
24+
assert_eq!(0, control.margin_left());
25+
assert_eq!(0, control.margin(Margin::Left.into()) as i64);
26+
27+
control.set_margin(Margin::Left.into(), 24.0);
28+
29+
assert_eq!(24, control.margin_left());
30+
31+
control.free();
32+
}}

0 commit comments

Comments
 (0)