Skip to content

Commit 44f38db

Browse files
committed
Add as_str and godot_name to nonbitfield enums
1 parent 3d053d7 commit 44f38db

File tree

4 files changed

+169
-19
lines changed

4 files changed

+169
-19
lines changed

godot-codegen/src/generator/enums.rs

Lines changed: 116 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub fn make_enum_definition_with(
7676
quote! { #[doc(hidden)] pub }
7777
});
7878

79-
let debug_impl = make_enum_debug_impl(enum_);
79+
let debug_impl = make_enum_debug_impl(enum_, define_traits && !enum_.is_bitfield);
8080
quote! {
8181
#[repr(transparent)]
8282
#[derive( #( #derives ),* )]
@@ -146,11 +146,8 @@ fn make_enum_index_impl(enum_: &Enum) -> Option<TokenStream> {
146146
})
147147
}
148148

149-
/// Implement `Debug` trait for the enum.
150-
fn make_enum_debug_impl(enum_: &Enum) -> TokenStream {
151-
let enum_name = &enum_.name;
152-
let enum_name_str = enum_name.to_string();
153-
149+
// Creates the match cases to return the enumerator name as &str.
150+
fn make_enum_to_str_cases(enum_: &Enum) -> TokenStream {
154151
let enumerators = enum_.enumerators.iter().map(|enumerator| {
155152
let Enumerator { name, .. } = enumerator;
156153
let name_str = name.to_string();
@@ -159,22 +156,55 @@ fn make_enum_debug_impl(enum_: &Enum) -> TokenStream {
159156
}
160157
});
161158

159+
quote! {
160+
#( #enumerators )*
161+
}
162+
}
163+
164+
/// Implement `Debug` trait for the enum.
165+
fn make_enum_debug_impl(enum_: &Enum, use_as_str: bool) -> TokenStream {
166+
let enum_name = &enum_.name;
167+
let enum_name_str = enum_name.to_string();
168+
169+
// Print the ord if no matching enumerator can be found.
170+
let enumerator_not_found = quote! {
171+
f.debug_struct(#enum_name_str)
172+
.field("ord", &self.ord)
173+
.finish()?;
174+
175+
return Ok(());
176+
};
177+
178+
// Reuse `as_str` if traits are defined and not a bitfield.
179+
let function_body = if use_as_str {
180+
quote! {
181+
use crate::obj::EngineEnum;
182+
183+
let enumerator = self.as_str();
184+
if enumerator.is_empty() {
185+
#enumerator_not_found
186+
}
187+
}
188+
} else {
189+
let enumerators = make_enum_to_str_cases(enum_);
190+
191+
quote! {
192+
// Many enums have duplicates, thus allow unreachable.
193+
// In the future, we could print sth like "ONE|TWO" instead (at least for unstable Debug).
194+
#[allow(unreachable_patterns)]
195+
let enumerator = match *self {
196+
#enumerators
197+
_ => {
198+
#enumerator_not_found
199+
}
200+
};
201+
}
202+
};
203+
162204
quote! {
163205
impl std::fmt::Debug for #enum_name {
164206
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165-
// Many enums have duplicates, thus allow unreachable.
166-
// In the future, we could print sth like "ONE|TWO" instead (at least for unstable Debug).
167-
#[allow(unreachable_patterns)]
168-
let enumerator = match *self {
169-
#( #enumerators )*
170-
_ => {
171-
f.debug_struct(#enum_name_str)
172-
.field("ord", &self.ord)
173-
.finish()?;
174-
return Ok(());
175-
}
176-
};
177-
207+
#function_body
178208
f.write_str(enumerator)
179209
}
180210
}
@@ -222,6 +252,8 @@ fn make_enum_engine_trait_impl(enum_: &Enum) -> TokenStream {
222252
}
223253
});
224254

255+
let str_functions = make_enum_str_functions(enum_);
256+
225257
quote! {
226258
impl #engine_trait for #name {
227259
fn try_from_ord(ord: i32) -> Option<Self> {
@@ -234,10 +266,13 @@ fn make_enum_engine_trait_impl(enum_: &Enum) -> TokenStream {
234266
fn ord(self) -> i32 {
235267
self as i32
236268
}
269+
270+
#str_functions
237271
}
238272
}
239273
} else {
240274
let unique_ords = enum_.unique_ords().expect("self is an enum");
275+
let str_functions = make_enum_str_functions(enum_);
241276

242277
quote! {
243278
impl #engine_trait for #name {
@@ -251,8 +286,70 @@ fn make_enum_engine_trait_impl(enum_: &Enum) -> TokenStream {
251286
fn ord(self) -> i32 {
252287
self.ord
253288
}
289+
290+
#str_functions
291+
}
292+
}
293+
}
294+
}
295+
296+
/// Creates the `as_str` and `godot_name` implementations for the enum.
297+
fn make_enum_str_functions(enum_: &Enum) -> TokenStream {
298+
let as_str_enumerators = make_enum_to_str_cases(enum_);
299+
300+
// Only enumerations with different godot names are specified.
301+
// `as_str` is called for the rest of them.
302+
let godot_different_cases = {
303+
let enumerators = enum_
304+
.enumerators
305+
.iter()
306+
.filter(|enumerator| enumerator.name != enumerator.godot_name)
307+
.map(|enumerator| {
308+
let Enumerator {
309+
name, godot_name, ..
310+
} = enumerator;
311+
let godot_name_str = godot_name.to_string();
312+
quote! {
313+
Self::#name => #godot_name_str,
314+
}
315+
});
316+
317+
quote! {
318+
#( #enumerators )*
319+
}
320+
};
321+
322+
let godot_name_match = if godot_different_cases.is_empty() {
323+
// If empty, all the Rust names match the Godot ones.
324+
// Remove match statement to avoid `clippy::match_single_binding`.
325+
quote! {
326+
self.as_str()
327+
}
328+
} else {
329+
quote! {
330+
// Many enums have duplicates, thus allow unreachable.
331+
#[allow(unreachable_patterns)]
332+
match *self {
333+
#godot_different_cases
334+
_ => self.as_str(),
254335
}
255336
}
337+
};
338+
339+
quote! {
340+
#[inline]
341+
fn as_str(&self) -> &'static str {
342+
// Many enums have duplicates, thus allow unreachable.
343+
#[allow(unreachable_patterns)]
344+
match *self {
345+
#as_str_enumerators
346+
_ => "",
347+
}
348+
}
349+
350+
fn godot_name(&self) -> &'static str {
351+
#godot_name_match
352+
}
256353
}
257354
}
258355

godot-core/src/builtin/vectors/vector_axis.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ macro_rules! impl_vector_axis_enum {
3838
fn ord(self) -> i32 {
3939
self as i32
4040
}
41+
42+
fn as_str(&self) -> &'static str {
43+
match *self {
44+
$(
45+
Self::$axis => stringify!($axis),
46+
)+
47+
}
48+
}
49+
50+
fn godot_name(&self) -> &'static str {
51+
match *self {
52+
$(
53+
Self::$axis => concat!("AXIS_", stringify!($axis)),
54+
)+
55+
}
56+
}
4157
}
4258

4359
impl GodotConvert for $AxisEnum {

godot-core/src/obj/traits.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,16 @@ pub trait EngineEnum: Copy {
164164
Self::try_from_ord(ord)
165165
.unwrap_or_else(|| panic!("ordinal {ord} does not map to any enumerator"))
166166
}
167+
168+
// The name of the enumerator, as it appears in Rust.
169+
//
170+
// If the value does not match one of the known enumerators, the empty string is returned.
171+
fn as_str(&self) -> &'static str;
172+
173+
// The equivalent name of the enumerator, as specified in Godot.
174+
//
175+
// If the value does not match one of the known enumerators, the empty string is returned.
176+
fn godot_name(&self) -> &'static str;
167177
}
168178

169179
/// Auto-implemented for all engine-provided bitfields.

itest/rust/src/object_tests/enum_test.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use godot::builtin::varray;
1010
use godot::classes::input::CursorShape;
1111
use godot::classes::mesh::PrimitiveType;
1212
use godot::classes::{time, ArrayMesh};
13+
use godot::global::{Orientation, Key};
1314
use std::collections::HashSet;
1415

1516
#[itest]
@@ -70,3 +71,29 @@ fn add_surface_from_arrays() {
7071
let mut mesh = ArrayMesh::new();
7172
mesh.add_surface_from_arrays(PrimitiveType::PRIMITIVE_TRIANGLES, varray![]);
7273
}
74+
75+
#[itest]
76+
fn enum_as_str_correct() {
77+
use godot::obj::EngineEnum;
78+
assert_eq!(Orientation::Vertical.as_str(), "VERTICAL");
79+
assert_eq!(Orientation::Horizontal.as_str(), "HORIZONTAL");
80+
81+
assert_eq!(Key::NONE.as_str(), "NONE");
82+
assert_eq!(Key::SPECIAL.as_str(), "SPECIAL");
83+
assert_eq!(Key::ESCAPE.as_str(), "ESCAPE");
84+
assert_eq!(Key::TAB.as_str(), "TAB");
85+
assert_eq!(Key::A.as_str(), "A");
86+
}
87+
88+
#[itest]
89+
fn enum_godot_name_correct() {
90+
use godot::obj::EngineEnum;
91+
assert_eq!(Orientation::Vertical.godot_name(), Orientation::Vertical.as_str());
92+
assert_eq!(Orientation::Horizontal.godot_name(), Orientation::Vertical.as_str());
93+
94+
assert_eq!(Key::NONE.godot_name(), "KEY_NONE");
95+
assert_eq!(Key::SPECIAL.godot_name(), "KEY_SPECIAL");
96+
assert_eq!(Key::ESCAPE.godot_name(), "KEY_ESCAPE");
97+
assert_eq!(Key::TAB.godot_name(), "KEY_TAB");
98+
assert_eq!(Key::A.godot_name(), "KEY_A");
99+
}

0 commit comments

Comments
 (0)