Skip to content

Commit

Permalink
Try #775:
Browse files Browse the repository at this point in the history
  • Loading branch information
bors[bot] authored Oct 12, 2023
2 parents 6dc9721 + 825ad68 commit c2f6666
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 3 deletions.
35 changes: 32 additions & 3 deletions gdnative-core/src/export/property/hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,22 @@ where
/// ```
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct EnumHint {
values: Vec<String>,
values: Vec<(String, Option<i64>)>,
}

impl EnumHint {
#[inline]
pub fn new(values: Vec<String>) -> Self {
let values = values.into_iter().map(|v| (v, None)).collect();
EnumHint { values }
}

#[inline]
pub fn with_numbers(values: Vec<(String, i64)>) -> Self {
let values = values
.into_iter()
.map(|(key, val)| (key, Some(val)))
.collect();
EnumHint { values }
}

Expand All @@ -130,13 +140,22 @@ impl EnumHint {
let mut s = String::new();

let mut iter = self.values.iter();
let write_item = |s: &mut String, item: &(String, Option<i64>)| match item {
(key, Some(val)) => {
write!(s, "{key}:{val}")
}
(key, None) => {
write!(s, "{key}")
}
};

if let Some(first) = iter.next() {
write!(s, "{first}").unwrap();
write_item(&mut s, first).unwrap();
}

for rest in iter {
write!(s, ",{rest}").unwrap();
write!(s, ",").unwrap();
write_item(&mut s, rest).unwrap();
}

s.into()
Expand Down Expand Up @@ -469,3 +488,13 @@ impl ArrayHint {
}
}
}

godot_test!(test_enum_hint_without_mapping {
let hint = EnumHint::new(vec!["Foo".into(), "Bar".into()]);
assert_eq!(hint.to_godot_hint_string().to_string(), "Foo,Bar".to_string(),);
});

godot_test!(test_enum_hint_with_mapping {
let hint = EnumHint::with_numbers(vec![("Foo".into(), 42), ("Bar".into(), 67)]);
assert_eq!(hint.to_godot_hint_string().to_string(), "Foo:42,Bar:67".to_string(),);
});
65 changes: 65 additions & 0 deletions gdnative-derive/src/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use proc_macro2::{Span, TokenStream as TokenStream2};
use syn::spanned::Spanned;
use syn::{DeriveInput, Fields};

fn err_only_supports_fieldless_enums(span: Span) -> syn::Error {
syn::Error::new(span, "#[derive(Export)] only supports fieldless enums")
}

pub(crate) fn derive_export(input: &DeriveInput) -> syn::Result<TokenStream2> {
let derived_enum = match &input.data {
syn::Data::Enum(data) => data,
syn::Data::Struct(data) => {
return Err(err_only_supports_fieldless_enums(data.struct_token.span()));
}
syn::Data::Union(data) => {
return Err(err_only_supports_fieldless_enums(data.union_token.span()));
}
};

let export_impl = impl_export(&input.ident, derived_enum)?;
Ok(export_impl)
}

fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result<TokenStream2> {
let err = data
.variants
.iter()
.filter(|variant| !matches!(variant.fields, Fields::Unit))
.map(|variant| err_only_supports_fieldless_enums(variant.ident.span()))
.reduce(|mut acc, err| {
acc.combine(err);
acc
});
if let Some(err) = err {
return Err(err);
}

let mappings = data
.variants
.iter()
.map(|variant| {
let key = &variant.ident;
let val = quote! { #enum_ty::#key as i64 };
quote! { (stringify!(#key).to_string(), #val) }
})
.collect::<Vec<_>>();

let impl_block = quote! {
impl ::gdnative::export::Export for #enum_ty {
type Hint = ::gdnative::export::hint::IntHint<i64>;
#[inline]
fn export_info(hint: Option<Self::Hint>) -> ::gdnative::export::ExportInfo {
if let Some(hint) = hint {
return hint.export_info();
} else {
let mappings = vec![ #(#mappings),* ];
let enum_hint = ::gdnative::export::hint::EnumHint::with_numbers(mappings);
return ::gdnative::export::hint::IntHint::<i64>::Enum(enum_hint).export_info();
}
}
}
};

Ok(impl_block)
}
58 changes: 58 additions & 0 deletions gdnative-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use syn::{parse::Parser, AttributeArgs, DeriveInput, ItemFn, ItemImpl, ItemType};

mod export;
mod init;
mod methods;
mod native_script;
Expand Down Expand Up @@ -663,6 +664,63 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream {
}
}

/// Make a rust `enum` has drop-down list in Godot editor.
/// Note that the derived `enum` should also implements `Copy` trait.
///
/// Take the following example, you will see a drop-down list for the `dir`
/// property, and `Up` and `Down` converts to `1` and `-1` in the GDScript
/// side.
///
/// ```
/// use gdnative::prelude::*;
///
/// #[derive(Debug, PartialEq, Clone, Copy, Export, ToVariant, FromVariant)]
/// #[variant(enum = "repr")]
/// #[repr(i32)]
/// enum Dir {
/// Up = 1,
/// Down = -1,
/// }
///
/// #[derive(NativeClass)]
/// #[no_constructor]
/// struct Move {
/// #[property]
/// pub dir: Dir,
/// }
/// ```
///
/// You can't derive `Export` on `enum` that has non-unit variant.
///
/// ```compile_fail
/// use gdnative::prelude::*;
///
/// #[derive(Debug, PartialEq, Clone, Copy, Export)]
/// enum Action {
/// Move((f32, f32, f32)),
/// Attack(u64),
/// }
/// ```
///
/// You can't derive `Export` on `struct` or `union`.
///
/// ```compile_fail
/// use gdnative::prelude::*;
///
/// #[derive(Export)]
/// struct Foo {
/// f1: i32
/// }
/// ```
#[proc_macro_derive(Export)]
pub fn derive_export(input: TokenStream) -> TokenStream {
let derive_input = syn::parse_macro_input!(input as syn::DeriveInput);
match export::derive_export(&derive_input) {
Ok(stream) => stream.into(),
Err(err) => err.to_compile_error().into(),
}
}

/// Returns a standard header for derived implementations.
///
/// Adds the `automatically_derived` attribute and prevents common lints from triggering
Expand Down
6 changes: 6 additions & 0 deletions gdnative/tests/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ fn ui_tests() {
t.compile_fail("tests/ui/from_variant_fail_07.rs");
t.compile_fail("tests/ui/from_variant_fail_08.rs");
t.compile_fail("tests/ui/from_variant_fail_09.rs");

// Export
t.pass("tests/ui/export_pass.rs");
t.compile_fail("tests/ui/export_fail_01.rs");
t.compile_fail("tests/ui/export_fail_02.rs");
t.compile_fail("tests/ui/export_fail_03.rs");
}

// FIXME(rust/issues/54725): Full path spans are only available on nightly as of now
Expand Down
9 changes: 9 additions & 0 deletions gdnative/tests/ui/export_fail_01.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use gdnative::prelude::*;

#[derive(Export, ToVariant)]
pub enum Foo {
Bar(String),
Baz { a: i32, b: u32 },
}

fn main() {}
11 changes: 11 additions & 0 deletions gdnative/tests/ui/export_fail_01.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error: #[derive(Export)] only supports fieldless enums
--> tests/ui/export_fail_01.rs:5:5
|
5 | Bar(String),
| ^^^

error: #[derive(Export)] only supports fieldless enums
--> tests/ui/export_fail_01.rs:6:5
|
6 | Baz { a: i32, b: u32 },
| ^^^
8 changes: 8 additions & 0 deletions gdnative/tests/ui/export_fail_02.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use gdnative::prelude::*;

#[derive(Export, ToVariant)]
pub struct Foo {
bar: i32,
}

fn main() {}
5 changes: 5 additions & 0 deletions gdnative/tests/ui/export_fail_02.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: #[derive(Export)] only supports fieldless enums
--> tests/ui/export_fail_02.rs:4:5
|
4 | pub struct Foo {
| ^^^^^^
8 changes: 8 additions & 0 deletions gdnative/tests/ui/export_fail_03.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use gdnative::prelude::*;

#[derive(Export, ToVariant)]
pub union Foo {
bar: i32,
}

fn main() {}
11 changes: 11 additions & 0 deletions gdnative/tests/ui/export_fail_03.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error: #[derive(Export)] only supports fieldless enums
--> tests/ui/export_fail_03.rs:4:5
|
4 | pub union Foo {
| ^^^^^

error: Variant conversion derive macro does not work on unions.
--> tests/ui/export_fail_03.rs:4:1
|
4 | pub union Foo {
| ^^^
11 changes: 11 additions & 0 deletions gdnative/tests/ui/export_pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use gdnative::prelude::*;

#[derive(Export, ToVariant, Clone, Copy)]
#[variant(enum = "repr")]
#[repr(i32)]
pub enum Foo {
Bar,
Baz,
}

fn main() {}
7 changes: 7 additions & 0 deletions test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod test_as_arg;
mod test_async;
mod test_constructor;
mod test_derive;
mod test_export_enum;
mod test_free_ub;
mod test_generic_class;
mod test_indexed_props;
Expand All @@ -28,6 +29,11 @@ pub extern "C" fn run_tests(

status &= gdnative::core_types::test_core_types();

status &= gdnative::export::hint::test_enum_hint_without_mapping();
status &= gdnative::export::hint::test_enum_hint_with_mapping();

status &= test_underscore_method_binding();
status &= test_rust_class_construction();
status &= test_from_instance_id();
status &= test_nil_object_return_value();
status &= test_rust_class_construction();
Expand All @@ -47,6 +53,7 @@ pub extern "C" fn run_tests(
status &= test_vararray_return::run_tests();
status &= test_variant_call_args::run_tests();
status &= test_variant_ops::run_tests();
status &= test_export_enum::run_tests();

Variant::new(status).leak()
}
Expand Down
30 changes: 30 additions & 0 deletions test/src/test_export_enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use gdnative::prelude::*;

#[derive(Debug, PartialEq, Clone, Copy, Export, ToVariant, FromVariant)]
#[variant(enum = "repr")]
#[repr(i32)]
enum Dir {
Up = 1,
Down = -1,
}

pub(crate) fn run_tests() -> bool {
let mut ok = true;

ok &= test_from_variant();
ok &= test_to_variant();

ok
}

crate::godot_itest!(test_from_variant {
assert_eq!(Dir::from_variant(&1_i32.to_variant()), Ok(Dir::Up));
assert_eq!(Dir::from_variant(&(-1_i32).to_variant()), Ok(Dir::Down));
// 42 isn't mapped to any variant of `Dir`
assert!(Dir::from_variant(&42_i32.to_variant()).is_err());
});

crate::godot_itest!(test_to_variant {
assert_eq!(Dir::Up.to_variant(), 1_i32.to_variant());
assert_eq!(Dir::Down.to_variant(), (-1_i32).to_variant());
});

0 comments on commit c2f6666

Please sign in to comment.