5
5
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
6
*/
7
7
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 } ;
12
15
13
16
pub fn make_enums ( enums : & [ Enum ] ) -> TokenStream {
14
17
let definitions = enums. iter ( ) . map ( make_enum_definition) ;
@@ -18,81 +21,95 @@ pub fn make_enums(enums: &[Enum]) -> TokenStream {
18
21
}
19
22
}
20
23
24
+ /// Creates a definition for the given enum.
25
+ ///
26
+ /// This will also implement all relevant traits and generate appropriate constants for each enumerator.
21
27
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 ( ) ;
35
47
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
+ }
37
55
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
+ }
40
59
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
44
63
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 ;
47
66
}
48
- }
49
67
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
+ }
51
73
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
+ }
54
80
}
81
+ }
55
82
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;
71
93
}
72
- } ;
94
+ } )
95
+ }
73
96
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 ( ) ;
78
103
79
104
if enum_. is_bitfield {
80
- bitfield_ops = quote ! {
105
+ quote ! {
106
+ // We may want to add this in the future.
107
+ //
81
108
// impl #enum_name {
82
109
// pub const UNSET: Self = Self { ord: 0 };
83
110
// }
84
- impl std:: ops:: BitOr for #rust_enum_name {
85
- type Output = Self ;
86
111
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 {
96
113
fn try_from_ord( ord: u64 ) -> Option <Self > {
97
114
Some ( Self { ord } )
98
115
}
@@ -101,17 +118,12 @@ pub fn make_enum_definition(enum_: &Enum) -> TokenStream {
101
118
self . ord
102
119
}
103
120
}
104
- } ;
121
+ }
105
122
} 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 {
115
127
fn try_from_ord( ord: i32 ) -> Option <Self > {
116
128
match ord {
117
129
#( ord @ #unique_ords ) |* => Some ( Self { ord } ) ,
@@ -123,118 +135,71 @@ pub fn make_enum_definition(enum_: &Enum) -> TokenStream {
123
135
self . ord
124
136
}
125
137
}
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
138
138
}
139
- impl #rust_enum_name {
140
- #(
141
- #enumerators
142
- ) *
143
- }
144
-
145
- #engine_impl
146
- #index_enum_impl
147
- #bitfield_ops
139
+ }
140
+ }
148
141
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 ;
152
147
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 ;
158
152
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
+ }
163
156
}
164
157
}
158
+ } else {
159
+ TokenStream :: new ( )
165
160
}
166
161
}
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 ( ) ;
167
167
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
+ }
174
171
175
- fn make_bitfield_flag_ord ( ord : u64 ) -> Literal {
176
- Literal :: u64_suffixed ( ord)
172
+ docs
177
173
}
178
174
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}`" ) ;
187
190
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) ;
192
191
quote ! {
193
- #[ doc( alias = #godot_name_str ) ]
194
- #[ doc = #doc_string ]
192
+ #[ doc( alias = #godot_name ) ]
193
+ #[ doc = #doc ]
195
194
}
195
+ } else {
196
+ TokenStream :: new ( )
196
197
} ;
197
198
198
199
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
+ } ;
237
204
}
238
-
239
- Some ( last_value as usize )
240
205
}
0 commit comments