Skip to content

Commit 45ab022

Browse files
authored
Exporting custom rust enums is not supported (#54)
Closes #46
1 parent be87ea4 commit 45ab022

File tree

11 files changed

+211
-31
lines changed

11 files changed

+211
-31
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ godot = { git = "https://github.com/godot-rust/gdext", tag = "v0.1.3", features
1414
godot-cell = { git = "https://github.com/godot-rust/gdext", tag = "v0.1.3" }
1515
itertools = "0.10.3"
1616
rand = "0.8.5"
17-
darling = { version = "0.20.10" }
17+
darling = { version = "0.20" }
1818
proc-macro2 = "1.0.68"
19-
quote = "1.0.33"
20-
syn = "2.0.38"
19+
quote = "1"
20+
syn = "2"
2121
const-str = "0.5.6"
2222
thiserror = "1"
2323

derive/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ darling.workspace = true
1111
proc-macro2.workspace = true
1212
quote.workspace = true
1313
syn.workspace = true
14+
itertools.workspace = true

derive/src/attribute_ops.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
use darling::ast::Data;
8-
use darling::util::{self, WithOriginal};
8+
use darling::util::{self, SpannedValue, WithOriginal};
99
use darling::{FromAttributes, FromDeriveInput, FromField, FromMeta};
1010
use proc_macro2::{Span, TokenStream};
1111
use quote::{quote, quote_spanned};
@@ -327,7 +327,7 @@ pub struct FieldOpts {
327327
#[darling(supports(struct_any), attributes(script), forward_attrs(doc))]
328328
pub struct GodotScriptOpts {
329329
pub ident: syn::Ident,
330-
pub data: Data<util::Ignored, FieldOpts>,
330+
pub data: Data<util::Ignored, SpannedValue<FieldOpts>>,
331331
pub base: Option<syn::Ident>,
332332
pub attrs: Vec<syn::Attribute>,
333333
}

derive/src/enums.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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 http://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use darling::{
8+
ast::Data,
9+
util::{Ignored, WithOriginal},
10+
FromDeriveInput, FromVariant,
11+
};
12+
use itertools::Itertools;
13+
use proc_macro2::TokenStream;
14+
use quote::{quote, quote_spanned};
15+
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Ident, Meta, Visibility};
16+
17+
use crate::type_paths::{convert_error_ty, godot_types, property_hints};
18+
19+
#[derive(FromDeriveInput)]
20+
#[darling(supports(enum_unit), attributes(script_enum))]
21+
struct EnumDeriveInput {
22+
vis: Visibility,
23+
ident: Ident,
24+
export: Option<WithOriginal<(), Meta>>,
25+
data: Data<EnumVariant, Ignored>,
26+
}
27+
28+
#[derive(FromVariant)]
29+
struct EnumVariant {
30+
ident: Ident,
31+
}
32+
33+
pub fn script_enum_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
34+
let godot_types = godot_types();
35+
let convert_error = convert_error_ty();
36+
let property_hints = property_hints();
37+
38+
let input = parse_macro_input!(input as DeriveInput);
39+
let input = EnumDeriveInput::from_derive_input(&input).unwrap();
40+
41+
let enum_ident = input.ident;
42+
let enum_as_try_from = quote_spanned! {enum_ident.span()=> <#enum_ident as TryFrom<Self::Via>>};
43+
let enum_from_self = quote_spanned! {enum_ident.span()=> <Self::Via as From<&#enum_ident>>};
44+
let enum_error_ident = Ident::new(&format!("{}Error", enum_ident), enum_ident.span());
45+
let enum_visibility = input.vis;
46+
47+
let variants = input.data.take_enum().unwrap();
48+
49+
let (from_variants, into_variants, hint_strings): (TokenStream, TokenStream, Vec<_>) = variants
50+
.iter()
51+
.enumerate()
52+
.map(|(index, variant)| {
53+
let variant_ident = &variant.ident;
54+
let index = index as u8;
55+
56+
(
57+
quote_spanned! {variant_ident.span()=> #enum_ident::#variant_ident => #index,},
58+
quote_spanned! {variant_ident.span()=> #index => Ok(#enum_ident::#variant_ident),},
59+
format!("{variant_ident}:{index}"),
60+
)
61+
})
62+
.multiunzip();
63+
let enum_property_hint_str = hint_strings.join(",");
64+
65+
let derive_export = input.export.map(|export| {
66+
quote_spanned! {export.original.span()=>
67+
impl ::godot_rust_script::GodotScriptExport for #enum_ident {
68+
fn hint(custom: Option<#property_hints>) -> #property_hints {
69+
if let Some(custom) = custom {
70+
return custom;
71+
}
72+
73+
#property_hints::ENUM
74+
}
75+
76+
fn hint_string(_custom_hint: Option<#property_hints>, custom_string: Option<String>) -> String {
77+
if let Some(custom_string) = custom_string {
78+
return custom_string;
79+
}
80+
81+
String::from(#enum_property_hint_str)
82+
}
83+
}
84+
}
85+
});
86+
87+
let derived = quote! {
88+
impl #godot_types::meta::FromGodot for #enum_ident {
89+
fn try_from_godot(via: Self::Via) -> Result<Self, #convert_error> {
90+
#enum_as_try_from::try_from(via)
91+
.map_err(|err| #convert_error::with_error_value(err, via))
92+
}
93+
}
94+
95+
impl #godot_types::meta::ToGodot for #enum_ident {
96+
fn to_godot(&self) -> Self::Via {
97+
#enum_from_self::from(self)
98+
}
99+
}
100+
101+
impl #godot_types::meta::GodotConvert for #enum_ident {
102+
type Via = u8;
103+
}
104+
105+
impl GodotScriptEnum for #enum_ident {}
106+
107+
impl From<&#enum_ident> for u8 {
108+
fn from(value: &#enum_ident) -> Self {
109+
match value {
110+
#from_variants
111+
}
112+
}
113+
}
114+
115+
#[derive(Debug)]
116+
#enum_visibility struct #enum_error_ident(u8);
117+
118+
impl ::std::fmt::Display for #enum_error_ident {
119+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120+
write!(f, "Enum value {} is out of range.", self.0)
121+
}
122+
}
123+
124+
impl ::std::error::Error for #enum_error_ident {}
125+
126+
impl TryFrom<u8> for #enum_ident {
127+
type Error = #enum_error_ident;
128+
129+
fn try_from(value: u8) -> ::std::result::Result<Self, Self::Error> {
130+
match value {
131+
#into_variants
132+
_ => Err(#enum_error_ident(value)),
133+
}
134+
}
135+
}
136+
137+
#derive_export
138+
};
139+
140+
derived.into()
141+
}

derive/src/lib.rs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
mod attribute_ops;
88
mod impl_attribute;
99
mod type_paths;
10+
mod enums;
1011

1112
use attribute_ops::{FieldOpts, GodotScriptOpts};
12-
use darling::{ FromAttributes, FromDeriveInput};
13+
use darling::{ util::SpannedValue, FromAttributes, FromDeriveInput};
1314
use proc_macro2::TokenStream;
1415
use quote::{quote, quote_spanned, ToTokens};
1516
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Ident, Type};
16-
use type_paths::{godot_types, string_name_ty, variant_ty};
17+
use type_paths::{godot_types, property_hints, string_name_ty, variant_ty};
1718

1819
use crate::attribute_ops::{FieldExportOps, PropertyOpts};
1920

@@ -27,6 +28,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
2728
let variant_ty = variant_ty();
2829
let string_name_ty = string_name_ty();
2930
let call_error_ty = quote!(#godot_types::sys::GDExtensionCallErrorType);
31+
let property_hint_ty = property_hints();
3032

3133
let base_class = opts
3234
.base
@@ -63,12 +65,17 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
6365
.iter()
6466
.any(|attr| attr.path().is_ident("export"));
6567

66-
let (hint, hint_string) = {
68+
let (hint, hint_string) = exported.then(|| {
6769
let ops = FieldExportOps::from_attributes(&field.attrs)
6870
.map_err(|err| err.write_errors())?;
6971

70-
ops.hint(&field.ty)?
71-
};
72+
ops.hint(&field.ty)
73+
}).transpose()?.unwrap_or_else(|| {
74+
(
75+
quote_spanned!(field.span()=> #property_hint_ty::NONE),
76+
quote_spanned!(field.span()=> String::new()),
77+
)
78+
});
7279

7380
let description = get_field_description(field);
7481
let item = quote! {
@@ -176,8 +183,9 @@ fn rust_to_variant_type(ty: &syn::Type) -> Result<TokenStream, TokenStream> {
176183
T::Path(path) => Ok(quote_spanned! {
177184
ty.span() => {
178185
use #godot_types::sys::GodotFfi;
186+
use #godot_types::meta::GodotType;
179187

180-
<#path as #godot_types::meta::GodotType>::Ffi::variant_type()
188+
<<#path as #godot_types::meta::GodotConvert>::Via as GodotType>::Ffi::variant_type()
181189
}
182190
}),
183191
T::Verbatim(_) => Err(syn::Error::new(
@@ -197,8 +205,9 @@ fn rust_to_variant_type(ty: &syn::Type) -> Result<TokenStream, TokenStream> {
197205
Ok(quote_spanned! {
198206
tuple.span() => {
199207
use #godot_types::sys::GodotFfi;
208+
use #godot_types::meta::GodotType;
200209

201-
<#tuple as #godot_types::meta::GodotType>::Ffi::variant_type()
210+
<<#tuple as #godot_types::meta::GodotConvert>::Via as GodotType>::Ffi::variant_type()
202211
}
203212
})
204213
}
@@ -218,7 +227,7 @@ fn is_context_type(ty: &syn::Type) -> bool {
218227
path.path.segments.last().map(|segment| segment.ident == "Context").unwrap_or(false)
219228
}
220229

221-
fn derive_default_with_base(field_opts: &[FieldOpts]) -> TokenStream {
230+
fn derive_default_with_base(field_opts: &[SpannedValue<FieldOpts>]) -> TokenStream {
222231
let godot_types = godot_types();
223232
let fields: TokenStream = field_opts
224233
.iter()
@@ -245,7 +254,7 @@ fn derive_default_with_base(field_opts: &[FieldOpts]) -> TokenStream {
245254
}
246255
}
247256

248-
fn derive_get_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a) -> TokenStream {
257+
fn derive_get_fields<'a>(public_fields: impl Iterator<Item = &'a SpannedValue<FieldOpts>> + 'a) -> TokenStream {
249258
let godot_types = godot_types();
250259
let string_name_ty = string_name_ty();
251260
let variant_ty = variant_ty();
@@ -262,11 +271,11 @@ fn derive_get_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a
262271
};
263272

264273
let accessor = match opts.get {
265-
Some(getter) => quote_spanned!(getter.span() => #getter(&self)),
266-
None => quote!(self.#field_ident),
274+
Some(getter) => quote_spanned!(getter.span()=> #getter(&self)),
275+
None => quote_spanned!(field_ident.span()=> self.#field_ident),
267276
};
268277

269-
quote! {
278+
quote_spanned! {field.ty.span()=>
270279
#[allow(clippy::needless_borrow)]
271280
#field_name => Some(#godot_types::prelude::ToGodot::to_variant(&#accessor)),
272281
}
@@ -284,7 +293,7 @@ fn derive_get_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a
284293
}
285294
}
286295

287-
fn derive_set_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a) -> TokenStream {
296+
fn derive_set_fields<'a>(public_fields: impl Iterator<Item = &'a SpannedValue<FieldOpts>> + 'a) -> TokenStream {
288297
let string_name_ty = string_name_ty();
289298
let variant_ty = variant_ty();
290299
let godot_types = godot_types();
@@ -300,15 +309,14 @@ fn derive_set_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a
300309
Err(err) => return err.write_errors(),
301310
};
302311

303-
let variant_value = quote!(#godot_types::prelude::FromGodot::try_from_variant(&value));
312+
let variant_value = quote_spanned!(field.ty.span()=> #godot_types::prelude::FromGodot::try_from_variant(&value));
304313

305314
let assignment = match opts.set {
306-
Some(setter) => quote_spanned!(setter.span() => #setter(self, local_value)),
307-
None => quote!(self.#field_ident = local_value),
315+
Some(setter) => quote_spanned!(setter.span()=> #setter(self, local_value)),
316+
None => quote_spanned!(field.ty.span() => self.#field_ident = local_value),
308317
};
309318

310-
quote_spanned! {
311-
field_ident.span() =>
319+
quote! {
312320
#field_name => {
313321
let local_value = match #variant_value {
314322
Ok(v) => v,
@@ -334,7 +342,7 @@ fn derive_set_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a
334342
}
335343

336344
fn derive_property_states_export<'a>(
337-
public_fields: impl Iterator<Item = &'a FieldOpts> + 'a,
345+
public_fields: impl Iterator<Item = &'a SpannedValue<FieldOpts>> + 'a,
338346
) -> TokenStream {
339347
let string_name_ty = string_name_ty();
340348
let variant_ty = variant_ty();
@@ -420,3 +428,8 @@ fn extract_ident_from_type(impl_target: &syn::Type) -> Result<Ident, TokenStream
420428
_ => Err(compile_error("Unsupported type!", impl_target)),
421429
}
422430
}
431+
432+
#[proc_macro_derive(GodotScriptEnum, attributes(script_enum))]
433+
pub fn script_enum_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
434+
enums::script_enum_derive(input)
435+
}

derive/src/type_paths.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ pub fn string_name_ty() -> TokenStream {
2828

2929
quote!(#godot_types::prelude::StringName)
3030
}
31+
32+
pub fn convert_error_ty() -> TokenStream {
33+
let godot_types = godot_types();
34+
35+
quote!(#godot_types::meta::error::ConvertError)
36+
}

rust-script/src/interface.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::marker::PhantomData;
1111
use std::ops::{Deref, DerefMut};
1212
use std::{collections::HashMap, fmt::Debug};
1313

14+
use godot::meta::{FromGodot, GodotConvert, ToGodot};
1415
use godot::obj::Inherits;
1516
use godot::prelude::{Gd, Object, StringName, Variant};
1617

@@ -201,6 +202,8 @@ macro_rules! setup_library {
201202
};
202203
}
203204

205+
pub trait GodotScriptEnum: GodotConvert + FromGodot + ToGodot {}
206+
204207
#[macro_export]
205208
macro_rules! init {
206209
($scripts_module:tt) => {

0 commit comments

Comments
 (0)