Skip to content

Commit f098597

Browse files
authored
Try #177:
2 parents cccf247 + 12a828f commit f098597

File tree

6 files changed

+395
-85
lines changed

6 files changed

+395
-85
lines changed

godot-core/src/obj/traits.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ use godot_ffi as sys;
1313
/// Makes `T` eligible to be managed by Godot and stored in [`Gd<T>`][crate::obj::Gd] pointers.
1414
///
1515
/// The behavior of types implementing this trait is influenced by the associated types; check their documentation for information.
16+
///
17+
/// You wouldn't usually implement this trait yourself; see the [documentation for the
18+
/// `derive(GodotClass)` macro](godot_macros::GodotClass).
1619
pub trait GodotClass: 'static
1720
where
1821
Self: Sized,

godot-macros/src/derive_godot_class.rs

Lines changed: 173 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7-
use crate::util::{bail, ident, KvParser};
7+
use crate::util::{bail, ident, string_lit_contents, KvParser, KvValue};
88
use crate::ParseResult;
99
use proc_macro2::{Ident, Punct, TokenStream};
1010
use quote::{format_ident, quote};
@@ -68,7 +68,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
6868
let mut base_ty = ident("RefCounted");
6969
let mut has_generated_init = false;
7070

71-
// #[func] attribute on struct
71+
// #[class] attribute on struct
7272
if let Some(mut parser) = KvParser::parse(&class.attributes, "class")? {
7373
if let Some(base) = parser.handle_ident("base")? {
7474
base_ty = base;
@@ -174,11 +174,46 @@ impl Field {
174174

175175
struct ExportedField {
176176
field: Field,
177-
getter: String,
178-
setter: String,
177+
getter: GetterSetter,
178+
setter: GetterSetter,
179179
hint: Option<ExportHint>,
180180
}
181181

182+
#[derive(Clone, Debug, Eq, PartialEq)]
183+
enum GetterSetter {
184+
/// Getter/setter should be omitted, field is write/read only.
185+
Omitted,
186+
/// Trivial getter/setter should be autogenerated.
187+
Generated,
188+
/// Getter/setter is hand-written by the user, and here is its name.
189+
Custom(String),
190+
}
191+
192+
impl GetterSetter {
193+
fn parse(parser: &mut KvParser, key: &str) -> ParseResult<Self> {
194+
Ok(match parser.handle_any(key) {
195+
// No `get` argument
196+
None => GetterSetter::Omitted,
197+
// `get` without value
198+
Some(KvValue::None) => GetterSetter::Generated,
199+
// `get = literal`
200+
Some(KvValue::Lit(name_lit)) => {
201+
let name = string_lit_contents(&name_lit).ok_or_else(|| {
202+
venial::Error::new(format!(
203+
"argument to {key} must be a string literal, got: {name_lit}"
204+
))
205+
})?;
206+
GetterSetter::Custom(name)
207+
}
208+
Some(KvValue::Ident(ident)) => {
209+
return Err(venial::Error::new(format!(
210+
"argument to {key} must be a string, got: {ident}"
211+
)));
212+
}
213+
})
214+
}
215+
}
216+
182217
#[derive(Clone)]
183218
struct ExportHint {
184219
hint_type: Ident,
@@ -196,8 +231,12 @@ impl ExportHint {
196231

197232
impl ExportedField {
198233
pub fn new_from_kv(field: Field, parser: &mut KvParser) -> ParseResult<ExportedField> {
199-
let getter = parser.handle_lit_required("getter")?;
200-
let setter = parser.handle_lit_required("setter")?;
234+
let mut getter = GetterSetter::parse(parser, "get")?;
235+
let mut setter = GetterSetter::parse(parser, "set")?;
236+
if getter == GetterSetter::Omitted && setter == GetterSetter::Omitted {
237+
getter = GetterSetter::Generated;
238+
setter = GetterSetter::Generated;
239+
}
201240

202241
let hint = parser
203242
.handle_ident("hint")?
@@ -265,51 +304,109 @@ fn make_deref_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
265304
}
266305

267306
fn make_exports_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
268-
let export_tokens = fields
269-
.exported_fields
270-
.iter()
271-
.map(|exported_field: &ExportedField| {
272-
use std::str::FromStr;
273-
let name = exported_field.field.name.to_string();
274-
let getter = proc_macro2::Literal::from_str(&exported_field.getter).unwrap();
275-
let setter = proc_macro2::Literal::from_str(&exported_field.setter).unwrap();
276-
let field_type = exported_field.field.ty.clone();
277-
278-
let ExportHint {
279-
hint_type,
280-
description,
281-
} = exported_field.hint.clone().unwrap_or_else(ExportHint::none);
282-
283-
// trims '"' and '\' from both ends of the hint description.
284-
let description = description.trim_matches(|c| c == '\\' || c == '"');
285-
286-
quote! {
287-
use ::godot::builtin::meta::VariantMetadata;
288-
289-
let class_name = ::godot::builtin::StringName::from(#class_name::CLASS_NAME);
290-
let property_info = ::godot::builtin::meta::PropertyInfo::new(
291-
<#field_type>::variant_type(),
292-
::godot::builtin::meta::ClassName::of::<#class_name>(),
293-
::godot::builtin::StringName::from(#name),
294-
::godot::engine::global::PropertyHint::#hint_type,
295-
GodotString::from(#description),
307+
let mut getter_setter_impls = Vec::new();
308+
let mut export_tokens = Vec::with_capacity(fields.exported_fields.len());
309+
310+
for exported_field in &fields.exported_fields {
311+
let field_name = exported_field.field.name.to_string();
312+
let field_ident = ident(&field_name);
313+
let field_type = exported_field.field.ty.clone();
314+
315+
let ExportHint {
316+
hint_type,
317+
description,
318+
} = exported_field.hint.clone().unwrap_or_else(ExportHint::none);
319+
320+
// trims '"' and '\' from both ends of the hint description.
321+
let description = description.trim_matches(|c| c == '\\' || c == '"');
322+
323+
let getter_name;
324+
match &exported_field.getter {
325+
GetterSetter::Omitted => {
326+
getter_name = "".to_owned();
327+
}
328+
GetterSetter::Generated => {
329+
getter_name = format!("get_{field_name}");
330+
let getter_ident = ident(&getter_name);
331+
let signature = quote! {
332+
fn #getter_ident(&self) -> #field_type
333+
};
334+
getter_setter_impls.push(quote! {
335+
pub #signature {
336+
self.#field_ident
337+
}
338+
});
339+
export_tokens.push(quote! {
340+
::godot::private::gdext_register_method!(#class_name, #signature);
341+
});
342+
}
343+
GetterSetter::Custom(name) => {
344+
getter_name = name.clone();
345+
let getter_ident = ident(&getter_name);
346+
export_tokens.push(existence_check(&getter_ident));
347+
}
348+
}
349+
350+
let setter_name;
351+
match &exported_field.setter {
352+
GetterSetter::Omitted => {
353+
setter_name = "".to_owned();
354+
}
355+
GetterSetter::Generated => {
356+
setter_name = format!("set_{field_name}");
357+
let setter_ident = ident(&setter_name);
358+
let signature = quote! {
359+
fn #setter_ident(&mut self, #field_ident: #field_type)
360+
};
361+
getter_setter_impls.push(quote! {
362+
pub #signature {
363+
self.#field_ident = #field_ident;
364+
}
365+
});
366+
export_tokens.push(quote! {
367+
::godot::private::gdext_register_method!(#class_name, #signature);
368+
});
369+
}
370+
GetterSetter::Custom(name) => {
371+
setter_name = name.clone();
372+
let setter_ident = ident(&setter_name);
373+
export_tokens.push(existence_check(&setter_ident));
374+
}
375+
};
376+
377+
export_tokens.push(quote! {
378+
use ::godot::builtin::meta::VariantMetadata;
379+
380+
let class_name = ::godot::builtin::StringName::from(#class_name::CLASS_NAME);
381+
382+
let property_info = ::godot::builtin::meta::PropertyInfo::new(
383+
<#field_type>::variant_type(),
384+
::godot::builtin::meta::ClassName::of::<#class_name>(),
385+
::godot::builtin::StringName::from(#field_name),
386+
::godot::engine::global::PropertyHint::#hint_type,
387+
GodotString::from(#description),
388+
);
389+
let property_info_sys = property_info.property_sys();
390+
391+
let getter_name = ::godot::builtin::StringName::from(#getter_name);
392+
let setter_name = ::godot::builtin::StringName::from(#setter_name);
393+
unsafe {
394+
::godot::sys::interface_fn!(classdb_register_extension_class_property)(
395+
::godot::sys::get_library(),
396+
class_name.string_sys(),
397+
std::ptr::addr_of!(property_info_sys),
398+
setter_name.string_sys(),
399+
getter_name.string_sys(),
296400
);
297-
let property_info_sys = property_info.property_sys();
298-
299-
let getter_string_name = ::godot::builtin::StringName::from(#getter);
300-
let setter_string_name = ::godot::builtin::StringName::from(#setter);
301-
unsafe {
302-
::godot::sys::interface_fn!(classdb_register_extension_class_property)(
303-
::godot::sys::get_library(),
304-
class_name.string_sys(),
305-
std::ptr::addr_of!(property_info_sys),
306-
setter_string_name.string_sys(),
307-
getter_string_name.string_sys(),
308-
);
309-
}
310401
}
311402
});
403+
}
404+
312405
quote! {
406+
impl #class_name {
407+
#(#getter_setter_impls)*
408+
}
409+
313410
impl ::godot::obj::cap::ImplementsGodotExports for #class_name {
314411
fn __register_exports() {
315412
#(
@@ -321,3 +418,32 @@ fn make_exports_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
321418
}
322419
}
323420
}
421+
422+
/// Checks at compile time that a function with the given name exists on `Self`.
423+
#[must_use]
424+
fn existence_check(ident: &Ident) -> TokenStream {
425+
quote! {
426+
#[allow(path_statements)]
427+
Self::#ident;
428+
}
429+
}
430+
431+
/// Tests that the proc-macro doesn't panic. This is helpful because `RUST_BACKTRACE=1` does not
432+
/// affect panics in macro invocations (e.g. in our integration tests), but it works fine in unit
433+
/// tests.
434+
#[test]
435+
fn does_not_panic() {
436+
let decl = venial::parse_declaration(quote! {
437+
#[class(base=Node)]
438+
struct HasProperty {
439+
#[base]
440+
base: Base<Node>,
441+
#[export]
442+
int_val_default: i32,
443+
#[export(get = "get_int_val", set = "set_int_val")]
444+
int_val: i32,
445+
}
446+
})
447+
.unwrap();
448+
transform(decl).unwrap();
449+
}

0 commit comments

Comments
 (0)