Skip to content

Commit e44194a

Browse files
\#[derive(Property, Export)]
Initial work for #[derive(Property)] (failing) general fixing around make proper tests for derive(Property) review stuff tm remove default repr type since isize isnt GodotFfi this is what happens when i try to commit too fast again Documentation + review fixes implement and document Export add a test for derive(Export) review fixing ci fixes further fixing oopsies
1 parent 4dd712c commit e44194a

File tree

5 files changed

+328
-2
lines changed

5 files changed

+328
-2
lines changed

godot-macros/src/derive_export.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use proc_macro2::TokenStream as TokenStream2;
8+
use quote::quote;
9+
use venial::{Declaration, StructFields};
10+
11+
use crate::util::{bail, decl_get_info, DeclInfo};
12+
use crate::ParseResult;
13+
14+
pub fn transform(decl: Declaration) -> ParseResult<TokenStream2> {
15+
let DeclInfo { name, .. } = decl_get_info(&decl);
16+
17+
let enum_ = match decl {
18+
Declaration::Enum(e) => e,
19+
Declaration::Struct(s) => {
20+
return bail!(s.tk_struct, "Export can only be derived on enums for now")
21+
}
22+
Declaration::Union(u) => {
23+
return bail!(u.tk_union, "Export can only be derived on enums for now")
24+
}
25+
_ => unreachable!(),
26+
};
27+
28+
let hint_string = if enum_.variants.is_empty() {
29+
return bail!(
30+
enum_.name,
31+
"In order to derive Export, enums must have at least one variant"
32+
);
33+
} else {
34+
let mut hint_string_segments = Vec::new();
35+
for (enum_v, _) in enum_.variants.inner.iter() {
36+
let v_name = enum_v.name.clone();
37+
let v_disc = if let Some(c) = enum_v.value.clone() {
38+
c.value
39+
} else {
40+
return bail!(
41+
v_name,
42+
"Property can only be derived on enums with explicit discriminants in all their variants"
43+
);
44+
};
45+
let v_disc_trimmed = v_disc
46+
.to_string()
47+
.trim_matches(['(', ')'].as_slice())
48+
.to_string();
49+
50+
hint_string_segments.push(format!("{v_name}:{v_disc_trimmed}"));
51+
52+
match &enum_v.contents {
53+
StructFields::Unit => {}
54+
_ => {
55+
return bail!(
56+
v_name,
57+
"Property can only be derived on enums with only unit variants for now"
58+
)
59+
}
60+
};
61+
}
62+
hint_string_segments.join(",")
63+
};
64+
65+
let out = quote! {
66+
#[allow(unused_parens)]
67+
impl godot::bind::property::Export for #name {
68+
fn default_export_info() -> godot::bind::property::ExportInfo {
69+
godot::bind::property::ExportInfo {
70+
hint: godot::engine::global::PropertyHint::PROPERTY_HINT_ENUM,
71+
hint_string: godot::prelude::GodotString::from(#hint_string),
72+
}
73+
}
74+
}
75+
};
76+
Ok(out)
77+
}

godot-macros/src/derive_property.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use proc_macro2::TokenStream as TokenStream2;
8+
use quote::{quote, ToTokens};
9+
use venial::{Declaration, StructFields};
10+
11+
use crate::util::{bail, decl_get_info, ident, DeclInfo};
12+
use crate::ParseResult;
13+
14+
pub fn transform(decl: Declaration) -> ParseResult<TokenStream2> {
15+
let DeclInfo {
16+
name, name_string, ..
17+
} = decl_get_info(&decl);
18+
19+
let body_get;
20+
let body_set;
21+
let intermediate;
22+
23+
let enum_ = match decl {
24+
Declaration::Enum(e) => e,
25+
Declaration::Struct(s) => {
26+
return bail!(s.tk_struct, "Property can only be derived on enums for now")
27+
}
28+
Declaration::Union(u) => {
29+
return bail!(u.tk_union, "Property can only be derived on enums for now")
30+
}
31+
_ => unreachable!(),
32+
};
33+
34+
if enum_.variants.is_empty() {
35+
return bail!(
36+
enum_.name,
37+
"In order to derive Property, enums must have at least one variant"
38+
);
39+
} else {
40+
let mut matches_get = quote! {};
41+
let mut matches_set = quote! {};
42+
intermediate = if let Some(attr) = enum_
43+
.attributes
44+
.iter()
45+
.find(|attr| attr.get_single_path_segment() == Some(&ident("repr")))
46+
{
47+
attr.value.to_token_stream()
48+
} else {
49+
return bail!(
50+
name,
51+
"Property can only be derived on enums with an explicit `#[repr(i*/u*)]` type"
52+
);
53+
};
54+
55+
for (enum_v, _) in enum_.variants.inner.iter() {
56+
let v_name = enum_v.name.clone();
57+
let v_disc = if let Some(c) = enum_v.value.clone() {
58+
c.value
59+
} else {
60+
return bail!(
61+
v_name,
62+
"Property can only be derived on enums with explicit discriminants in all their variants"
63+
);
64+
};
65+
66+
let match_content_get;
67+
let match_content_set;
68+
match &enum_v.contents {
69+
StructFields::Unit => {
70+
match_content_get = quote! {
71+
Self::#v_name => #v_disc,
72+
};
73+
match_content_set = quote! {
74+
#v_disc => Self::#v_name,
75+
};
76+
}
77+
_ => {
78+
return bail!(
79+
v_name,
80+
"Property can only be derived on enums with only unit variants for now"
81+
)
82+
}
83+
};
84+
matches_get = quote! {
85+
#matches_get
86+
#match_content_get
87+
};
88+
matches_set = quote! {
89+
#matches_set
90+
#match_content_set
91+
};
92+
}
93+
body_get = quote! {
94+
match &self {
95+
#matches_get
96+
}
97+
};
98+
body_set = quote! {
99+
*self = match value {
100+
#matches_set
101+
_ => panic!("Incorrect conversion from {} to {}", stringify!(#intermediate), #name_string),
102+
}
103+
};
104+
}
105+
106+
let out = quote! {
107+
#[allow(unused_parens)]
108+
impl godot::bind::property::Property for #name {
109+
type Intermediate = #intermediate;
110+
111+
fn get_property(&self) -> #intermediate {
112+
#body_get
113+
}
114+
115+
fn set_property(&mut self, value: #intermediate) {
116+
#body_set
117+
}
118+
}
119+
};
120+
Ok(out)
121+
}

godot-macros/src/lib.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ use proc_macro2::TokenStream as TokenStream2;
8787
use quote::quote;
8888
use venial::Declaration;
8989

90+
mod derive_export;
9091
mod derive_from_variant;
9192
mod derive_godot_class;
93+
mod derive_property;
9294
mod derive_to_variant;
9395
mod gdextension;
9496
mod godot_api;
@@ -454,6 +456,57 @@ pub fn derive_from_variant(input: TokenStream) -> TokenStream {
454456
translate(input, derive_from_variant::transform)
455457
}
456458

459+
/// Derive macro for [Property](godot::bind::property::Property) on enums.
460+
///
461+
/// Currently has some tight requirements which are expected to be softened as implementation expands:
462+
/// - Only works for enums, structs aren't supported by this derive macro at the moment.
463+
/// - The enum must have an explicit `#[repr(u*/i*)]` type.
464+
/// - This will likely stay this way, since `isize`, the default repr type, is not a concept in Godot.
465+
/// - The enum variants must not have any fields - currently only unit variants are supported.
466+
/// - The enum variants must have explicit discriminants, that is, e.g. `A = 2`, not just `A`
467+
///
468+
/// # Example
469+
///
470+
/// ```no_run
471+
/// # use godot::prelude::*;
472+
/// #[repr(i32)]
473+
/// #[derive(Property)]
474+
/// # #[derive(PartialEq, Eq, Debug)]
475+
/// enum TestEnum {
476+
/// A = 0,
477+
/// B = 1,
478+
/// }
479+
///
480+
/// #[derive(GodotClass)]
481+
/// struct TestClass {
482+
/// #[var]
483+
/// foo: TestEnum
484+
/// }
485+
///
486+
/// # //TODO: remove this when https://github.com/godot-rust/gdext/issues/187 is truly addressed
487+
/// # #[godot_api]
488+
/// # impl TestClass {}
489+
///
490+
/// # fn main() {
491+
/// let mut class = TestClass {foo: TestEnum::B};
492+
/// assert_eq!(class.get_foo(), TestEnum::B as i32);
493+
/// class.set_foo(TestEnum::A as i32);
494+
/// assert_eq!(class.foo, TestEnum::A);
495+
/// # }
496+
/// ```
497+
#[proc_macro_derive(Property)]
498+
pub fn derive_property(input: TokenStream) -> TokenStream {
499+
translate(input, derive_property::transform)
500+
}
501+
502+
/// Derive macro for [Export](godot::bind::property::Property) on enums.
503+
///
504+
/// Currently has some tight requirements which are expected to be softened as implementation expands, see requirements for [Property]
505+
#[proc_macro_derive(Export)]
506+
pub fn derive_export(input: TokenStream) -> TokenStream {
507+
translate(input, derive_export::transform)
508+
}
509+
457510
#[proc_macro_attribute]
458511
pub fn godot_api(_meta: TokenStream, input: TokenStream) -> TokenStream {
459512
translate(input, godot_api::transform)

godot/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ pub mod init {
169169
/// Export user-defined classes and methods to be called by the engine.
170170
pub mod bind {
171171
pub use godot_core::property;
172-
pub use godot_macros::{godot_api, FromVariant, GodotClass, ToVariant};
172+
pub use godot_macros::{godot_api, Export, FromVariant, GodotClass, Property, ToVariant};
173173
}
174174

175175
/// Testing facilities (unstable).
@@ -184,7 +184,7 @@ pub use godot_core::private;
184184
/// Often-imported symbols.
185185
pub mod prelude {
186186
pub use super::bind::property::{Export, Property, TypeStringHint};
187-
pub use super::bind::{godot_api, FromVariant, GodotClass, ToVariant};
187+
pub use super::bind::{godot_api, Export, FromVariant, GodotClass, Property, ToVariant};
188188

189189
pub use super::builtin::math::FloatExt as _;
190190
pub use super::builtin::*;

itest/rust/src/property_test.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use godot::{
88
bind::property::ExportInfo,
99
engine::{global::PropertyHint, Texture},
1010
prelude::*,
11+
test::itest,
1112
};
1213

1314
// No tests currently, tests using these classes are in Godot scripts.
@@ -293,3 +294,77 @@ struct CheckAllExports {
293294

294295
#[godot_api]
295296
impl CheckAllExports {}
297+
298+
#[repr(i64)]
299+
#[derive(Property, Debug, PartialEq, Eq, Export)]
300+
pub enum TestEnum {
301+
A = 0,
302+
B = 1,
303+
C = 2,
304+
}
305+
306+
#[derive(GodotClass)]
307+
pub struct DeriveProperty {
308+
#[var]
309+
pub foo: TestEnum,
310+
}
311+
312+
#[godot_api]
313+
impl DeriveProperty {}
314+
315+
#[itest]
316+
fn derive_property() {
317+
let mut class = DeriveProperty { foo: TestEnum::B };
318+
assert_eq!(class.get_foo(), TestEnum::B as i64);
319+
class.set_foo(TestEnum::C as i64);
320+
assert_eq!(class.foo, TestEnum::C);
321+
}
322+
323+
#[derive(GodotClass)]
324+
pub struct DeriveExport {
325+
#[export]
326+
pub foo: TestEnum,
327+
328+
#[base]
329+
pub base: Base<RefCounted>,
330+
}
331+
332+
#[godot_api]
333+
impl DeriveExport {}
334+
335+
#[godot_api]
336+
impl RefCountedVirtual for DeriveExport {
337+
fn init(base: godot::obj::Base<Self::Base>) -> Self {
338+
Self {
339+
foo: TestEnum::B,
340+
base,
341+
}
342+
}
343+
}
344+
345+
#[itest]
346+
fn derive_export() {
347+
let class: Gd<DeriveExport> = Gd::new_default();
348+
349+
let property = class
350+
.get_property_list()
351+
.iter_shared()
352+
.find(|c| c.get_or_nil("name") == "foo".to_variant())
353+
.unwrap();
354+
assert_eq!(
355+
property.get_or_nil("class_name"),
356+
"DeriveExport".to_variant()
357+
);
358+
assert_eq!(
359+
property.get_or_nil("type"),
360+
(VariantType::Int as i32).to_variant()
361+
);
362+
assert_eq!(
363+
property.get_or_nil("hint"),
364+
(PropertyHint::PROPERTY_HINT_ENUM.ord()).to_variant()
365+
);
366+
assert_eq!(
367+
property.get_or_nil("hint_string"),
368+
"A:0,B:1,C:2".to_variant()
369+
);
370+
}

0 commit comments

Comments
 (0)