Skip to content

Commit abd726f

Browse files
committed
Refactor Enum in codegen
1 parent cb41673 commit abd726f

File tree

4 files changed

+301
-210
lines changed

4 files changed

+301
-210
lines changed

godot-codegen/src/generator/enums.rs

Lines changed: 131 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use crate::models::domain::{Enum, Enumerator, EnumeratorValue};
9-
use crate::util;
10-
use proc_macro2::{Literal, TokenStream};
11-
use quote::quote;
8+
//! Functions for codegenning enums.
9+
//!
10+
//! See also models/domain/enums.rs for other enum-related methods.
11+
12+
use crate::models::domain::{Enum, Enumerator};
13+
use proc_macro2::TokenStream;
14+
use quote::{quote, ToTokens};
1215

1316
pub fn make_enums(enums: &[Enum]) -> TokenStream {
1417
let definitions = enums.iter().map(make_enum_definition);
@@ -18,81 +21,95 @@ pub fn make_enums(enums: &[Enum]) -> TokenStream {
1821
}
1922
}
2023

24+
/// Creates a definition for the given enum.
25+
///
26+
/// This will also implement all relevant traits and generate appropriate constants for each enumerator.
2127
pub fn make_enum_definition(enum_: &Enum) -> TokenStream {
22-
// TODO enums which have unique ords could be represented as Rust enums
23-
// This would allow exhaustive matches (or at least auto-completed matches + #[non_exhaustive]). But even without #[non_exhaustive],
24-
// this might be a forward compatibility hazard, if Godot deprecates enumerators and adds new ones with existing ords.
25-
26-
let rust_enum_name = &enum_.name;
27-
let godot_name_doc = if rust_enum_name != enum_.godot_name.as_str() {
28-
let doc = format!("Godot enum name: `{}`.", enum_.godot_name);
29-
quote! { #[doc = #doc] }
30-
} else {
31-
TokenStream::new()
32-
};
33-
34-
let rust_enumerators = &enum_.enumerators;
28+
// Things needed for the type definition
29+
let derives = enum_.derives();
30+
let enum_doc = make_enum_doc(enum_);
31+
let name = &enum_.name;
32+
33+
// Values
34+
let enumerators = enum_
35+
.enumerators
36+
.iter()
37+
.map(|enumerator| make_enumerator_definition(enumerator, name.to_token_stream()));
38+
39+
// Trait implementations
40+
let engine_trait_impl = make_enum_engine_trait_impl(enum_);
41+
let index_enum_impl = make_enum_index_impl(enum_);
42+
let bitwise_impls = make_enum_bitwise_operators(enum_);
43+
44+
// Various types
45+
let ord_type = enum_.ord_type();
46+
let engine_trait = enum_.engine_trait();
3547

36-
let mut enumerators = Vec::with_capacity(rust_enumerators.len());
48+
quote! {
49+
#[repr(transparent)]
50+
#[derive( #( #derives ),* )]
51+
#( #[doc = #enum_doc] )*
52+
pub struct #name {
53+
ord: #ord_type
54+
}
3755

38-
// This is only used for enum ords (i32), not bitfield flags (u64).
39-
let mut unique_ords = Vec::with_capacity(rust_enumerators.len());
56+
impl #name {
57+
#( #enumerators )*
58+
}
4059

41-
for enumerator in rust_enumerators.iter() {
42-
let def = make_enumerator_definition(enumerator);
43-
enumerators.push(def);
60+
#engine_trait_impl
61+
#index_enum_impl
62+
#bitwise_impls
4463

45-
if let EnumeratorValue::Enum(ord) = enumerator.value {
46-
unique_ords.push(ord);
64+
impl crate::builtin::meta::GodotConvert for #name {
65+
type Via = #ord_type;
4766
}
48-
}
4967

50-
let mut derives = vec!["Copy", "Clone", "Eq", "PartialEq", "Hash", "Debug"];
68+
impl crate::builtin::meta::ToGodot for #name {
69+
fn to_godot(&self) -> Self::Via {
70+
<Self as #engine_trait>::ord(*self)
71+
}
72+
}
5173

52-
if enum_.is_bitfield {
53-
derives.push("Default");
74+
impl crate::builtin::meta::FromGodot for #name {
75+
fn try_from_godot(via: Self::Via) -> std::result::Result<Self, crate::builtin::meta::ConvertError> {
76+
<Self as #engine_trait>::try_from_ord(via)
77+
.ok_or_else(|| crate::builtin::meta::FromGodotError::InvalidEnum.into_error(via))
78+
}
79+
}
5480
}
81+
}
5582

56-
let derives = derives.into_iter().map(util::ident);
57-
58-
let index_enum_impl = if enum_.is_bitfield {
59-
// Bitfields don't implement IndexEnum.
60-
TokenStream::new()
61-
} else {
62-
// Enums implement IndexEnum only if they are "index-like" (see docs).
63-
if let Some(enum_max) = try_count_index_enum(enum_) {
64-
quote! {
65-
impl crate::obj::IndexEnum for #rust_enum_name {
66-
const ENUMERATOR_COUNT: usize = #enum_max;
67-
}
68-
}
69-
} else {
70-
TokenStream::new()
83+
/// Creates an implementation of `IndexEnum` for the given enum.
84+
///
85+
/// Returns `None` if `enum_` isn't an indexable enum.
86+
fn make_enum_index_impl(enum_: &Enum) -> Option<TokenStream> {
87+
let enum_max = enum_.find_index_enum_max()?;
88+
let name = &enum_.name;
89+
90+
Some(quote! {
91+
impl crate::obj::IndexEnum for #name {
92+
const ENUMERATOR_COUNT: usize = #enum_max;
7193
}
72-
};
94+
})
95+
}
7396

74-
let bitfield_ops;
75-
let self_as_trait;
76-
let engine_impl;
77-
let enum_ord_type;
97+
/// Creates an implementation of the engine trait for the given enum.
98+
///
99+
/// This will implement the trait returned by [`Enum::engine_trait`].
100+
fn make_enum_engine_trait_impl(enum_: &Enum) -> TokenStream {
101+
let name = &enum_.name;
102+
let engine_trait = enum_.engine_trait();
78103

79104
if enum_.is_bitfield {
80-
bitfield_ops = quote! {
105+
quote! {
106+
// We may want to add this in the future.
107+
//
81108
// impl #enum_name {
82109
// pub const UNSET: Self = Self { ord: 0 };
83110
// }
84-
impl std::ops::BitOr for #rust_enum_name {
85-
type Output = Self;
86111

87-
fn bitor(self, rhs: Self) -> Self::Output {
88-
Self { ord: self.ord | rhs.ord }
89-
}
90-
}
91-
};
92-
enum_ord_type = quote! { u64 };
93-
self_as_trait = quote! { <Self as crate::obj::EngineBitfield> };
94-
engine_impl = quote! {
95-
impl crate::obj::EngineBitfield for #rust_enum_name {
112+
impl #engine_trait for #name {
96113
fn try_from_ord(ord: u64) -> Option<Self> {
97114
Some(Self { ord })
98115
}
@@ -101,17 +118,12 @@ pub fn make_enum_definition(enum_: &Enum) -> TokenStream {
101118
self.ord
102119
}
103120
}
104-
};
121+
}
105122
} else {
106-
// Ordinals are not necessarily in order.
107-
unique_ords.sort();
108-
unique_ords.dedup();
109-
110-
bitfield_ops = TokenStream::new();
111-
enum_ord_type = quote! { i32 };
112-
self_as_trait = quote! { <Self as crate::obj::EngineEnum> };
113-
engine_impl = quote! {
114-
impl crate::obj::EngineEnum for #rust_enum_name {
123+
let unique_ords = enum_.unique_ords().expect("self is an enum");
124+
125+
quote! {
126+
impl #engine_trait for #name {
115127
fn try_from_ord(ord: i32) -> Option<Self> {
116128
match ord {
117129
#( ord @ #unique_ords )|* => Some(Self { ord }),
@@ -123,118 +135,71 @@ pub fn make_enum_definition(enum_: &Enum) -> TokenStream {
123135
self.ord
124136
}
125137
}
126-
};
127-
};
128-
129-
// Enumerator ordinal stored as i32, since that's enough to hold all current values and the default repr in C++.
130-
// Public interface is i64 though, for consistency (and possibly forward compatibility?).
131-
// Bitfield ordinals are stored as u64. See also: https://github.com/godotengine/godot-cpp/pull/1320
132-
quote! {
133-
#[repr(transparent)]
134-
#[derive(#( #derives ),*)]
135-
#godot_name_doc
136-
pub struct #rust_enum_name {
137-
ord: #enum_ord_type
138138
}
139-
impl #rust_enum_name {
140-
#(
141-
#enumerators
142-
)*
143-
}
144-
145-
#engine_impl
146-
#index_enum_impl
147-
#bitfield_ops
139+
}
140+
}
148141

149-
impl crate::builtin::meta::GodotConvert for #rust_enum_name {
150-
type Via = #enum_ord_type;
151-
}
142+
/// Creates implementations for bitwise operators for the given enum.
143+
///
144+
/// Currently this is just [`BitOr`](std::ops::BitOr) for bitfields but that could be expanded in the future.
145+
fn make_enum_bitwise_operators(enum_: &Enum) -> TokenStream {
146+
let name = &enum_.name;
152147

153-
impl crate::builtin::meta::ToGodot for #rust_enum_name {
154-
fn to_godot(&self) -> Self::Via {
155-
#self_as_trait::ord(*self)
156-
}
157-
}
148+
if enum_.is_bitfield {
149+
quote! {
150+
impl std::ops::BitOr for #name {
151+
type Output = Self;
158152

159-
impl crate::builtin::meta::FromGodot for #rust_enum_name {
160-
fn try_from_godot(via: Self::Via) -> std::result::Result<Self, crate::builtin::meta::ConvertError> {
161-
#self_as_trait::try_from_ord(via)
162-
.ok_or_else(|| crate::builtin::meta::FromGodotError::InvalidEnum.into_error(via))
153+
fn bitor(self, rhs: Self) -> Self::Output {
154+
Self { ord: self.ord | rhs.ord }
155+
}
163156
}
164157
}
158+
} else {
159+
TokenStream::new()
165160
}
166161
}
162+
/// Returns the documentation for the given enum.
163+
///
164+
/// Each string is one line of documentation, usually this needs to be wrapped in a `#[doc = ..]`.
165+
fn make_enum_doc(enum_: &Enum) -> Vec<String> {
166+
let mut docs = Vec::new();
167167

168-
pub fn make_enumerator_ord(ord: i32) -> Literal {
169-
Literal::i32_suffixed(ord)
170-
}
171-
172-
// ----------------------------------------------------------------------------------------------------------------------------------------------
173-
// Implementation
168+
if enum_.name != enum_.godot_name {
169+
docs.push(format!("Godot enum name: `{}`.", enum_.godot_name))
170+
}
174171

175-
fn make_bitfield_flag_ord(ord: u64) -> Literal {
176-
Literal::u64_suffixed(ord)
172+
docs
177173
}
178174

179-
fn make_enumerator_definition(enumerator: &Enumerator) -> TokenStream {
180-
let ordinal_lit = match enumerator.value {
181-
EnumeratorValue::Enum(ord) => make_enumerator_ord(ord),
182-
EnumeratorValue::Bitfield(ord) => make_bitfield_flag_ord(ord),
183-
};
184-
185-
let rust_ident = &enumerator.name;
186-
let godot_name_str = &enumerator.godot_name;
175+
/// Creates a `const` definition for `enumerator` of the type `enum_type`.
176+
///
177+
/// That is, it'll be a definition like
178+
/// ```ignore
179+
/// pub const NAME: enum_type = ..;
180+
/// ```
181+
fn make_enumerator_definition(enumerator: &Enumerator, enum_type: TokenStream) -> TokenStream {
182+
let Enumerator {
183+
name,
184+
godot_name,
185+
value,
186+
} = enumerator;
187+
188+
let docs = if &name.to_string() != godot_name {
189+
let doc = format!("Godot enumerator name: `{godot_name}`");
187190

188-
let doc = if rust_ident == godot_name_str {
189-
TokenStream::new()
190-
} else {
191-
let doc_string = format!("Godot enumerator name: `{}`.", godot_name_str);
192191
quote! {
193-
#[doc(alias = #godot_name_str)]
194-
#[doc = #doc_string]
192+
#[doc(alias = #godot_name)]
193+
#[doc = #doc]
195194
}
195+
} else {
196+
TokenStream::new()
196197
};
197198

198199
quote! {
199-
#doc
200-
pub const #rust_ident: Self = Self { ord: #ordinal_lit };
201-
}
202-
}
203-
204-
/// If an enum qualifies as "indexable" (can be used as array index), returns the number of possible values.
205-
///
206-
/// See `godot::obj::IndexEnum` for what constitutes "indexable".
207-
fn try_count_index_enum(enum_: &Enum) -> Option<usize> {
208-
if enum_.is_bitfield || enum_.enumerators.is_empty() {
209-
return None;
210-
}
211-
212-
// Sort by ordinal value. Allocates for every enum in the JSON, but should be OK (most enums are indexable).
213-
let enumerators = {
214-
let mut enumerators = enum_.enumerators.iter().collect::<Vec<_>>();
215-
enumerators.sort_by_key(|v| v.value.to_i64());
216-
enumerators
217-
};
218-
219-
// Highest ordinal must be the "MAX" one.
220-
// The presence of "MAX" indicates that Godot devs intended the enum to be used as an index.
221-
// The condition is not strictly necessary and could theoretically be relaxed; there would need to be concrete use cases though.
222-
let last = enumerators.last().unwrap(); // safe because of is_empty check above.
223-
if !last.godot_name.ends_with("_MAX") {
224-
return None;
225-
}
226-
227-
// The rest of the enumerators must be contiguous and without gaps (duplicates are OK).
228-
let mut last_value = 0;
229-
for enumerator in enumerators.iter() {
230-
let e_value = enumerator.value.to_i64();
231-
232-
if last_value != e_value && last_value + 1 != e_value {
233-
return None;
234-
}
235-
236-
last_value = e_value;
200+
#docs
201+
pub const #name: #enum_type = #enum_type {
202+
ord: #value
203+
};
237204
}
238-
239-
Some(last_value as usize)
240205
}

godot-codegen/src/generator/notifications.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
use crate::context::Context;
9-
use crate::generator::enums;
109
use crate::models::domain::TyName;
1110
use crate::models::json::JsonClassConstant;
1211
use crate::{conv, util};
@@ -89,7 +88,7 @@ pub fn make_notification_enum(
8988
let mut notification_enumerators_ord = Vec::new();
9089
for (constant_ident, constant_value) in all_constants {
9190
notification_enumerators_pascal.push(constant_ident);
92-
notification_enumerators_ord.push(enums::make_enumerator_ord(constant_value));
91+
notification_enumerators_ord.push(constant_value);
9392
}
9493

9594
let code = quote! {

0 commit comments

Comments
 (0)