From 0f558c6904b3138f9045c788ade239b34080e1b8 Mon Sep 17 00:00:00 2001 From: bogay Date: Sun, 31 Jul 2022 14:42:41 +0000 Subject: [PATCH 01/19] feat(EnumHint): store optional mapping value information --- gdnative-core/src/export/property/hint.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/gdnative-core/src/export/property/hint.rs b/gdnative-core/src/export/property/hint.rs index 8dbd28365..38ca66606 100644 --- a/gdnative-core/src/export/property/hint.rs +++ b/gdnative-core/src/export/property/hint.rs @@ -116,12 +116,22 @@ where /// ``` #[derive(Clone, Eq, PartialEq, Debug, Default)] pub struct EnumHint { - values: Vec, + values: Vec<(String, Option)>, } impl EnumHint { #[inline] pub fn new(values: Vec) -> 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 } } From 89cbefc7b3f63a5b0f8707310613f3176cd40857 Mon Sep 17 00:00:00 2001 From: bogay Date: Sun, 31 Jul 2022 14:45:26 +0000 Subject: [PATCH 02/19] feat(derive): add `ExportEnum` currently the implementaion is dirty. I will refactor it after verifying it can work. --- gdnative-derive/src/export_enum.rs | 80 ++++++++++++++++++++++++++++++ gdnative-derive/src/lib.rs | 14 ++++++ 2 files changed, 94 insertions(+) create mode 100644 gdnative-derive/src/export_enum.rs diff --git a/gdnative-derive/src/export_enum.rs b/gdnative-derive/src/export_enum.rs new file mode 100644 index 000000000..92eb29bce --- /dev/null +++ b/gdnative-derive/src/export_enum.rs @@ -0,0 +1,80 @@ +use proc_macro2::TokenStream as TokenStream2; +use syn::DeriveInput; + +pub(crate) fn derive_export_enum(input: &DeriveInput) -> syn::Result { + let derived_enum = match &input.data { + syn::Data::Enum(data) => data, + _ => todo!("return error"), + }; + + let to_variant_impl = impl_to_variant(&input.ident, derived_enum)?; + let from_variant_impl = impl_from_variant(&input.ident, derived_enum)?; + let export_impl = impl_export(&input.ident, derived_enum)?; + let combined_impl = quote! { + #to_variant_impl + #from_variant_impl + #export_impl + }; + + Ok(combined_impl) +} + +fn impl_to_variant(enum_ty: &syn::Ident, _data: &syn::DataEnum) -> syn::Result { + let impl_block = quote! { + impl ::gdnative::core_types::ToVariant for #enum_ty { + #[inline] + fn to_variant(&self) -> ::gdnative::core_types::Variant { + (*self as i64).to_variant() + } + } + }; + + Ok(impl_block) +} + +fn impl_from_variant(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { + // TODO: reject non-unit enum variant + let as_int = quote! { n }; + let arms = data.variants.iter().map(|variant| { + let ident = &variant.ident; + quote! { + if #as_int == #enum_ty::#ident as i64 { + return Ok(#enum_ty::#ident); + } + } + }); + + let impl_block = quote! { + impl ::gdnative::core_types::FromVariant for #enum_ty { + #[inline] + fn from_variant(variant: &::gdnative::core_types::Variant) -> Result { + let #as_int = variant.try_to::()?; + #(#arms)* + + panic!() + } + } + }; + + Ok(impl_block) +} + +fn impl_export(enum_ty: &syn::Ident, _data: &syn::DataEnum) -> syn::Result { + let impl_block = quote! { + impl ::gdnative::export::Export for #enum_ty { + type Hint = ::gdnative::export::hint::IntHint; + #[inline] + fn export_info(hint: Option) -> ::gdnative::export::ExportInfo { + if let Some(hint) = hint { + if matches!(hint, ::gdnative::export::hint::IntHint::::Enum(_)) { + return hint.export_info(); + } + } + + ::gdnative::export::ExportInfo::new(::gdnative::core_types::VariantType::I64) + } + } + }; + + Ok(impl_block) +} diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index a71625381..b52086dce 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -10,7 +10,12 @@ use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; use syn::{parse::Parser, AttributeArgs, DeriveInput, ItemFn, ItemImpl, ItemType}; +<<<<<<< HEAD mod init; +======= +mod export_enum; +mod extend_bounds; +>>>>>>> feat(derive): add `ExportEnum` mod methods; mod native_script; mod profiled; @@ -663,6 +668,15 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream { } } +#[proc_macro_derive(ExportEnum)] +pub fn derive_export_enum(input: TokenStream) -> TokenStream { + let derive_input = syn::parse_macro_input!(input as syn::DeriveInput); + match export_enum::derive_export_enum(&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 From d464e6303a56ef691871c59701a714792d5493a6 Mon Sep 17 00:00:00 2001 From: bogay Date: Wed, 3 Aug 2022 06:54:54 +0000 Subject: [PATCH 03/19] test(EnumHint): test g`to_godot_hint_string` --- gdnative-core/src/export/property/hint.rs | 10 ++++++++++ test/src/lib.rs | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/gdnative-core/src/export/property/hint.rs b/gdnative-core/src/export/property/hint.rs index 38ca66606..8516520af 100644 --- a/gdnative-core/src/export/property/hint.rs +++ b/gdnative-core/src/export/property/hint.rs @@ -479,3 +479,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(),); +}); diff --git a/test/src/lib.rs b/test/src/lib.rs index 92a0fbd2a..8aa79bd38 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -28,6 +28,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(); From 29458aede81df36d171d8737d4a82ca86346fb4c Mon Sep 17 00:00:00 2001 From: bogay Date: Wed, 3 Aug 2022 07:00:30 +0000 Subject: [PATCH 04/19] fix(ExportEnum): handle non-enum input --- gdnative-derive/src/export_enum.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/gdnative-derive/src/export_enum.rs b/gdnative-derive/src/export_enum.rs index 92eb29bce..3401d6d40 100644 --- a/gdnative-derive/src/export_enum.rs +++ b/gdnative-derive/src/export_enum.rs @@ -4,7 +4,12 @@ use syn::DeriveInput; pub(crate) fn derive_export_enum(input: &DeriveInput) -> syn::Result { let derived_enum = match &input.data { syn::Data::Enum(data) => data, - _ => todo!("return error"), + _ => { + return Err(syn::Error::new( + input.ident.span(), + "#[derive(ExportEnum)] can only use on enum", + )) + } }; let to_variant_impl = impl_to_variant(&input.ident, derived_enum)?; @@ -59,19 +64,26 @@ fn impl_from_variant(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result< Ok(impl_block) } -fn impl_export(enum_ty: &syn::Ident, _data: &syn::DataEnum) -> syn::Result { +fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { + let mappings = data.variants.iter().map(|variant| { + let ident = &variant.ident; + let key = stringify!(ident); + let val = quote! { #enum_ty::#ident as i64 }; + quote! { (#key.to_string(), #val) } + }); let impl_block = quote! { impl ::gdnative::export::Export for #enum_ty { type Hint = ::gdnative::export::hint::IntHint; #[inline] fn export_info(hint: Option) -> ::gdnative::export::ExportInfo { if let Some(hint) = hint { - if matches!(hint, ::gdnative::export::hint::IntHint::::Enum(_)) { - return hint.export_info(); - } + return hint.export_info(); + } else { + let mappings = vec![ #(#mappings),* ]; + let enum_hint = ::gdnative::export::hint::EnumHint::with_numbers(mappings); + return ::gdnative::export::hint::IntHint::::Enum(enum_hint).export_info(); } - ::gdnative::export::ExportInfo::new(::gdnative::core_types::VariantType::I64) } } }; From 27540889062c449e52c98b095918fac962b15ca3 Mon Sep 17 00:00:00 2001 From: bogay Date: Wed, 3 Aug 2022 19:18:56 +0800 Subject: [PATCH 05/19] test(ExportEnum): correctly deny invalid input --- gdnative-derive/src/export_enum.rs | 86 ++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/gdnative-derive/src/export_enum.rs b/gdnative-derive/src/export_enum.rs index 3401d6d40..adc4c2265 100644 --- a/gdnative-derive/src/export_enum.rs +++ b/gdnative-derive/src/export_enum.rs @@ -38,25 +38,35 @@ fn impl_to_variant(enum_ty: &syn::Ident, _data: &syn::DataEnum) -> syn::Result syn::Result { - // TODO: reject non-unit enum variant let as_int = quote! { n }; - let arms = data.variants.iter().map(|variant| { - let ident = &variant.ident; - quote! { - if #as_int == #enum_ty::#ident as i64 { - return Ok(#enum_ty::#ident); + let arms = data + .variants + .iter() + .map(|variant| { + let ident = &variant.ident; + if !matches!(variant.fields, syn::Fields::Unit) { + Err(syn::Error::new( + ident.span(), + "#[derive(ExportEnum)] only support unit variant", + )) + } else { + Ok(quote! { + if #as_int == #enum_ty::#ident as i64 { Ok(#enum_ty::#ident) } + }) } - } - }); + }) + .collect::, _>>()?; let impl_block = quote! { impl ::gdnative::core_types::FromVariant for #enum_ty { #[inline] fn from_variant(variant: &::gdnative::core_types::Variant) -> Result { let #as_int = variant.try_to::()?; - #(#arms)* - - panic!() + #(#arms)else * + // TODO: return FromVariantError + else { + Err(FromVariantError::Unspecified) + } } } }; @@ -66,10 +76,9 @@ fn impl_from_variant(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result< fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { let mappings = data.variants.iter().map(|variant| { - let ident = &variant.ident; - let key = stringify!(ident); - let val = quote! { #enum_ty::#ident as i64 }; - quote! { (#key.to_string(), #val) } + let key = &variant.ident; + let val = quote! { #enum_ty::#key as i64 }; + quote! { (stringify!(#key).to_string(), #val) } }); let impl_block = quote! { impl ::gdnative::export::Export for #enum_ty { @@ -83,10 +92,55 @@ fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result::Enum(enum_hint).export_info(); } - } } }; Ok(impl_block) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deny_non_unit_variant() -> syn::Result<()> { + let named_variant = quote! { NamedVariant { foo: i32, bar: f32 } }; + let unnamed_variant = quote! { UnnamedVariant(String) }; + let input = |variant| { + parse_quote! { + pub enum Foo { + #variant + } + } + }; + + assert!(derive_export_enum(&input(&named_variant)).is_err()); + assert!(derive_export_enum(&input(&unnamed_variant)).is_err()); + + Ok(()) + } + + #[test] + fn deny_struct_derive() -> syn::Result<()> { + let input = parse_quote! { + struct Foo; + }; + assert!(derive_export_enum(&input).is_err()); + + Ok(()) + } + + #[test] + fn deny_union_derive() -> syn::Result<()> { + let input: DeriveInput = parse_quote! { + union Foo { + f1: u32, + f2: f32, + } + }; + assert!(derive_export_enum(&input).is_err()); + + Ok(()) + } +} From 512dc08b5626d6a2ff3fc29f0b106b1e999b957a Mon Sep 17 00:00:00 2001 From: bogay Date: Sat, 6 Aug 2022 00:10:47 +0800 Subject: [PATCH 06/19] feat(ExportEnum): correctly return error for `from_variant` --- gdnative-derive/src/export_enum.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/gdnative-derive/src/export_enum.rs b/gdnative-derive/src/export_enum.rs index adc4c2265..ab1cb6cfd 100644 --- a/gdnative-derive/src/export_enum.rs +++ b/gdnative-derive/src/export_enum.rs @@ -38,7 +38,7 @@ fn impl_to_variant(enum_ty: &syn::Ident, _data: &syn::DataEnum) -> syn::Result syn::Result { - let as_int = quote! { n }; + let n = quote! { n }; let arms = data .variants .iter() @@ -51,21 +51,27 @@ fn impl_from_variant(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result< )) } else { Ok(quote! { - if #as_int == #enum_ty::#ident as i64 { Ok(#enum_ty::#ident) } + if #n == #enum_ty::#ident as i64 { Ok(#enum_ty::#ident) } }) } }) .collect::, _>>()?; + let expected_variants = data + .variants + .iter() + .map(|variant| quote! { stringify!(#variant.ident) }); let impl_block = quote! { impl ::gdnative::core_types::FromVariant for #enum_ty { #[inline] fn from_variant(variant: &::gdnative::core_types::Variant) -> Result { - let #as_int = variant.try_to::()?; + let #n = variant.try_to::()?; #(#arms)else * - // TODO: return FromVariantError else { - Err(FromVariantError::Unspecified) + Err(FromVariantError::UnknownEnumVariant { + variant: variant.to_string(), + expected: &[#(#expected_variants),*], + }) } } } From 5b2e639b201fea0f0f8b105dd4593113eaf5f36d Mon Sep 17 00:00:00 2001 From: bogay Date: Sat, 6 Aug 2022 23:42:20 +0800 Subject: [PATCH 07/19] test(ExportEnum): to/from variant --- test/src/lib.rs | 2 ++ test/src/test_export_enum.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 test/src/test_export_enum.rs diff --git a/test/src/lib.rs b/test/src/lib.rs index 8aa79bd38..d32d6d75d 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -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; @@ -52,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() } diff --git a/test/src/test_export_enum.rs b/test/src/test_export_enum.rs new file mode 100644 index 000000000..a2e4e566a --- /dev/null +++ b/test/src/test_export_enum.rs @@ -0,0 +1,28 @@ +use gdnative::prelude::*; + +#[derive(Debug, PartialEq, Clone, Copy, ExportEnum)] +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()); +}); From aeac5afd0a2bc66648f82cbff36c958dee4245fc Mon Sep 17 00:00:00 2001 From: bogay Date: Sat, 6 Aug 2022 23:42:44 +0800 Subject: [PATCH 08/19] docs(ExportEnum): add macro doc --- gdnative-derive/src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index b52086dce..712264b39 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -668,6 +668,52 @@ 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, ExportEnum)] +/// enum Dir { +/// Up = 1, +/// Down = -1, +/// } +/// +/// #[derive(NativeClass)] +/// #[no_constructor] +/// struct Move { +/// #[property] +/// pub dir: Dir, +/// } +/// ``` +/// +/// You can't derive `ExportEnum` on `enum` that has non-unit variant. +/// +/// ```compile_fail +/// use gdnative::prelude::*; +/// +/// #[derive(Debug, PartialEq, Clone, Copy, ExportEnum)] +/// enum Action { +/// Move((f32, f32, f32)), +/// Attack(u64), +/// } +/// ``` +/// +/// You can't derive `ExportEnum` on `struct` or `union`. +/// +/// ```compile_fail +/// use gdnative::prelude::*; +/// +/// #[derive(ExportEnum)] +/// struct Foo { +/// f1: i32 +/// } +/// ``` #[proc_macro_derive(ExportEnum)] pub fn derive_export_enum(input: TokenStream) -> TokenStream { let derive_input = syn::parse_macro_input!(input as syn::DeriveInput); From cb9087a96d001315ec439b830a9551dcd1f8b41f Mon Sep 17 00:00:00 2001 From: bogay Date: Sun, 8 Oct 2023 15:45:47 +0800 Subject: [PATCH 09/19] feat: rename ExportEnum to Export --- gdnative-core/src/export/property/hint.rs | 13 +- gdnative-derive/src/export.rs | 42 ++++++ gdnative-derive/src/export_enum.rs | 152 ---------------------- gdnative-derive/src/lib.rs | 22 ++-- test/src/test_export_enum.rs | 2 +- 5 files changed, 63 insertions(+), 168 deletions(-) create mode 100644 gdnative-derive/src/export.rs delete mode 100644 gdnative-derive/src/export_enum.rs diff --git a/gdnative-core/src/export/property/hint.rs b/gdnative-core/src/export/property/hint.rs index 8516520af..b1ffbc787 100644 --- a/gdnative-core/src/export/property/hint.rs +++ b/gdnative-core/src/export/property/hint.rs @@ -140,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)| 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() diff --git a/gdnative-derive/src/export.rs b/gdnative-derive/src/export.rs new file mode 100644 index 000000000..a8116d9e9 --- /dev/null +++ b/gdnative-derive/src/export.rs @@ -0,0 +1,42 @@ +use proc_macro2::TokenStream as TokenStream2; +use syn::DeriveInput; + +pub(crate) fn derive_export(input: &DeriveInput) -> syn::Result { + let derived_enum = match &input.data { + syn::Data::Enum(data) => data, + _ => { + return Err(syn::Error::new( + input.ident.span(), + "#[derive(Export)] can only use on enum", + )) + } + }; + + let export_impl = impl_export(&input.ident, derived_enum)?; + Ok(export_impl) +} + +fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { + 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) } + }); + let impl_block = quote! { + impl ::gdnative::export::Export for #enum_ty { + type Hint = ::gdnative::export::hint::IntHint; + #[inline] + fn export_info(hint: Option) -> ::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::::Enum(enum_hint).export_info(); + } + } + } + }; + + Ok(impl_block) +} diff --git a/gdnative-derive/src/export_enum.rs b/gdnative-derive/src/export_enum.rs deleted file mode 100644 index ab1cb6cfd..000000000 --- a/gdnative-derive/src/export_enum.rs +++ /dev/null @@ -1,152 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use syn::DeriveInput; - -pub(crate) fn derive_export_enum(input: &DeriveInput) -> syn::Result { - let derived_enum = match &input.data { - syn::Data::Enum(data) => data, - _ => { - return Err(syn::Error::new( - input.ident.span(), - "#[derive(ExportEnum)] can only use on enum", - )) - } - }; - - let to_variant_impl = impl_to_variant(&input.ident, derived_enum)?; - let from_variant_impl = impl_from_variant(&input.ident, derived_enum)?; - let export_impl = impl_export(&input.ident, derived_enum)?; - let combined_impl = quote! { - #to_variant_impl - #from_variant_impl - #export_impl - }; - - Ok(combined_impl) -} - -fn impl_to_variant(enum_ty: &syn::Ident, _data: &syn::DataEnum) -> syn::Result { - let impl_block = quote! { - impl ::gdnative::core_types::ToVariant for #enum_ty { - #[inline] - fn to_variant(&self) -> ::gdnative::core_types::Variant { - (*self as i64).to_variant() - } - } - }; - - Ok(impl_block) -} - -fn impl_from_variant(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { - let n = quote! { n }; - let arms = data - .variants - .iter() - .map(|variant| { - let ident = &variant.ident; - if !matches!(variant.fields, syn::Fields::Unit) { - Err(syn::Error::new( - ident.span(), - "#[derive(ExportEnum)] only support unit variant", - )) - } else { - Ok(quote! { - if #n == #enum_ty::#ident as i64 { Ok(#enum_ty::#ident) } - }) - } - }) - .collect::, _>>()?; - let expected_variants = data - .variants - .iter() - .map(|variant| quote! { stringify!(#variant.ident) }); - - let impl_block = quote! { - impl ::gdnative::core_types::FromVariant for #enum_ty { - #[inline] - fn from_variant(variant: &::gdnative::core_types::Variant) -> Result { - let #n = variant.try_to::()?; - #(#arms)else * - else { - Err(FromVariantError::UnknownEnumVariant { - variant: variant.to_string(), - expected: &[#(#expected_variants),*], - }) - } - } - } - }; - - Ok(impl_block) -} - -fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { - 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) } - }); - let impl_block = quote! { - impl ::gdnative::export::Export for #enum_ty { - type Hint = ::gdnative::export::hint::IntHint; - #[inline] - fn export_info(hint: Option) -> ::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::::Enum(enum_hint).export_info(); - } - } - } - }; - - Ok(impl_block) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deny_non_unit_variant() -> syn::Result<()> { - let named_variant = quote! { NamedVariant { foo: i32, bar: f32 } }; - let unnamed_variant = quote! { UnnamedVariant(String) }; - let input = |variant| { - parse_quote! { - pub enum Foo { - #variant - } - } - }; - - assert!(derive_export_enum(&input(&named_variant)).is_err()); - assert!(derive_export_enum(&input(&unnamed_variant)).is_err()); - - Ok(()) - } - - #[test] - fn deny_struct_derive() -> syn::Result<()> { - let input = parse_quote! { - struct Foo; - }; - assert!(derive_export_enum(&input).is_err()); - - Ok(()) - } - - #[test] - fn deny_union_derive() -> syn::Result<()> { - let input: DeriveInput = parse_quote! { - union Foo { - f1: u32, - f2: f32, - } - }; - assert!(derive_export_enum(&input).is_err()); - - Ok(()) - } -} diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index 712264b39..2644ee15a 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -10,12 +10,8 @@ use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; use syn::{parse::Parser, AttributeArgs, DeriveInput, ItemFn, ItemImpl, ItemType}; -<<<<<<< HEAD +mod export; mod init; -======= -mod export_enum; -mod extend_bounds; ->>>>>>> feat(derive): add `ExportEnum` mod methods; mod native_script; mod profiled; @@ -678,7 +674,7 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream { /// ``` /// use gdnative::prelude::*; /// -/// #[derive(Debug, PartialEq, Clone, Copy, ExportEnum)] +/// #[derive(Debug, PartialEq, Clone, Copy, Export)] /// enum Dir { /// Up = 1, /// Down = -1, @@ -692,32 +688,32 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// You can't derive `ExportEnum` on `enum` that has non-unit variant. +/// You can't derive `Export` on `enum` that has non-unit variant. /// /// ```compile_fail /// use gdnative::prelude::*; /// -/// #[derive(Debug, PartialEq, Clone, Copy, ExportEnum)] +/// #[derive(Debug, PartialEq, Clone, Copy, Export)] /// enum Action { /// Move((f32, f32, f32)), /// Attack(u64), /// } /// ``` /// -/// You can't derive `ExportEnum` on `struct` or `union`. +/// You can't derive `Export` on `struct` or `union`. /// /// ```compile_fail /// use gdnative::prelude::*; /// -/// #[derive(ExportEnum)] +/// #[derive(Export)] /// struct Foo { /// f1: i32 /// } /// ``` -#[proc_macro_derive(ExportEnum)] -pub fn derive_export_enum(input: TokenStream) -> TokenStream { +#[proc_macro_derive(Export)] +pub fn derive_export(input: TokenStream) -> TokenStream { let derive_input = syn::parse_macro_input!(input as syn::DeriveInput); - match export_enum::derive_export_enum(&derive_input) { + match export::derive_export(&derive_input) { Ok(stream) => stream.into(), Err(err) => err.to_compile_error().into(), } diff --git a/test/src/test_export_enum.rs b/test/src/test_export_enum.rs index a2e4e566a..b40cb8698 100644 --- a/test/src/test_export_enum.rs +++ b/test/src/test_export_enum.rs @@ -1,6 +1,6 @@ use gdnative::prelude::*; -#[derive(Debug, PartialEq, Clone, Copy, ExportEnum)] +#[derive(Debug, PartialEq, Clone, Copy, Export, ToVariant, FromVariant)] enum Dir { Up = 1, Down = -1, From 217d2530773c450219f4134a408bdf0088766043 Mon Sep 17 00:00:00 2001 From: bogay Date: Sun, 8 Oct 2023 16:01:19 +0800 Subject: [PATCH 10/19] test(Export): add UI test --- gdnative-derive/src/export.rs | 24 ++++++++++++++++++------ gdnative/tests/ui.rs | 3 +++ gdnative/tests/ui/export_fail_01.rs | 8 ++++++++ gdnative/tests/ui/export_fail_01.stderr | 5 +++++ 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 gdnative/tests/ui/export_fail_01.rs create mode 100644 gdnative/tests/ui/export_fail_01.stderr diff --git a/gdnative-derive/src/export.rs b/gdnative-derive/src/export.rs index a8116d9e9..340daabc2 100644 --- a/gdnative-derive/src/export.rs +++ b/gdnative-derive/src/export.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream as TokenStream2; -use syn::DeriveInput; +use syn::{DeriveInput, Fields}; pub(crate) fn derive_export(input: &DeriveInput) -> syn::Result { let derived_enum = match &input.data { @@ -17,11 +17,23 @@ pub(crate) fn derive_export(input: &DeriveInput) -> syn::Result { } fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { - 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) } - }); + let mappings = { + let mut m = Vec::with_capacity(data.variants.len()); + + for variant in &data.variants { + if !matches!(variant.fields, Fields::Unit) { + return Err(syn::Error::new( + variant.ident.span(), + "#[derive(Export)] only supports fieldless enums", + )); + } + let key = &variant.ident; + let val = quote! { #enum_ty::#key as i64 }; + m.push(quote! { (stringify!(#key).to_string(), #val) }); + } + + m + }; let impl_block = quote! { impl ::gdnative::export::Export for #enum_ty { type Hint = ::gdnative::export::hint::IntHint; diff --git a/gdnative/tests/ui.rs b/gdnative/tests/ui.rs index 859d569c9..99a54ffdb 100644 --- a/gdnative/tests/ui.rs +++ b/gdnative/tests/ui.rs @@ -40,6 +40,9 @@ 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.compile_fail("tests/ui/export_fail_01.rs"); } // FIXME(rust/issues/54725): Full path spans are only available on nightly as of now diff --git a/gdnative/tests/ui/export_fail_01.rs b/gdnative/tests/ui/export_fail_01.rs new file mode 100644 index 000000000..4ce672339 --- /dev/null +++ b/gdnative/tests/ui/export_fail_01.rs @@ -0,0 +1,8 @@ +use gdnative::prelude::*; + +#[derive(Export, ToVariant)] +pub enum Foo { + Bar(String), +} + +fn main() {} diff --git a/gdnative/tests/ui/export_fail_01.stderr b/gdnative/tests/ui/export_fail_01.stderr new file mode 100644 index 000000000..47cc9bd87 --- /dev/null +++ b/gdnative/tests/ui/export_fail_01.stderr @@ -0,0 +1,5 @@ +error: #[derive(Export)] only supports fieldless enums + --> tests/ui/export_fail_01.rs:5:5 + | +5 | Bar(String), + | ^^^ From 5936bed72e8e9dc39eb43c94edf54a2e8b3b499a Mon Sep 17 00:00:00 2001 From: bogay Date: Sun, 8 Oct 2023 19:44:02 +0800 Subject: [PATCH 11/19] test(Export): derive ToVariant in doctest --- gdnative-derive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index 2644ee15a..f0c3fad16 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -674,7 +674,7 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream { /// ``` /// use gdnative::prelude::*; /// -/// #[derive(Debug, PartialEq, Clone, Copy, Export)] +/// #[derive(Debug, PartialEq, Clone, Copy, Export, ToVariant, FromVariant)] /// enum Dir { /// Up = 1, /// Down = -1, From 7d7bdd70226327506b9e45fcdb5a3b28c9e64908 Mon Sep 17 00:00:00 2001 From: bogay Date: Sun, 8 Oct 2023 20:16:48 +0800 Subject: [PATCH 12/19] test: derive repr for enum --- test/src/test_export_enum.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/src/test_export_enum.rs b/test/src/test_export_enum.rs index b40cb8698..a738cdd28 100644 --- a/test/src/test_export_enum.rs +++ b/test/src/test_export_enum.rs @@ -1,6 +1,8 @@ use gdnative::prelude::*; #[derive(Debug, PartialEq, Clone, Copy, Export, ToVariant, FromVariant)] +#[variant(enum = "repr")] +#[repr(i32)] enum Dir { Up = 1, Down = -1, From a12fd4d0cf0a1828604b6369d1a3caf4f871214d Mon Sep 17 00:00:00 2001 From: bogay Date: Sun, 8 Oct 2023 22:03:20 +0800 Subject: [PATCH 13/19] feat(Export): report compile error for all non-fieldless enum variants --- gdnative-derive/src/export.rs | 38 ++++++++++++++++--------- gdnative/tests/ui/export_fail_01.rs | 1 + gdnative/tests/ui/export_fail_01.stderr | 6 ++++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/gdnative-derive/src/export.rs b/gdnative-derive/src/export.rs index 340daabc2..8efa519f0 100644 --- a/gdnative-derive/src/export.rs +++ b/gdnative-derive/src/export.rs @@ -7,7 +7,7 @@ pub(crate) fn derive_export(input: &DeriveInput) -> syn::Result { _ => { return Err(syn::Error::new( input.ident.span(), - "#[derive(Export)] can only use on enum", + "#[derive(Export)] only supports fieldless enums", )) } }; @@ -17,23 +17,35 @@ pub(crate) fn derive_export(input: &DeriveInput) -> syn::Result { } fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { - let mappings = { - let mut m = Vec::with_capacity(data.variants.len()); - - for variant in &data.variants { - if !matches!(variant.fields, Fields::Unit) { - return Err(syn::Error::new( + let err = data + .variants + .iter() + .filter_map(|variant| { + (!matches!(variant.fields, Fields::Unit)).then(|| { + syn::Error::new( variant.ident.span(), "#[derive(Export)] only supports fieldless enums", - )); - } + ) + }) + }) + .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 }; - m.push(quote! { (stringify!(#key).to_string(), #val) }); - } + quote! { (stringify!(#key).to_string(), #val) } + }) + .collect::>(); - m - }; let impl_block = quote! { impl ::gdnative::export::Export for #enum_ty { type Hint = ::gdnative::export::hint::IntHint; diff --git a/gdnative/tests/ui/export_fail_01.rs b/gdnative/tests/ui/export_fail_01.rs index 4ce672339..c69fed7d3 100644 --- a/gdnative/tests/ui/export_fail_01.rs +++ b/gdnative/tests/ui/export_fail_01.rs @@ -3,6 +3,7 @@ use gdnative::prelude::*; #[derive(Export, ToVariant)] pub enum Foo { Bar(String), + Baz { a: i32, b: u32 }, } fn main() {} diff --git a/gdnative/tests/ui/export_fail_01.stderr b/gdnative/tests/ui/export_fail_01.stderr index 47cc9bd87..79cc55485 100644 --- a/gdnative/tests/ui/export_fail_01.stderr +++ b/gdnative/tests/ui/export_fail_01.stderr @@ -3,3 +3,9 @@ error: #[derive(Export)] only supports fieldless enums | 5 | Bar(String), | ^^^ + +error: #[derive(Export)] only supports fieldless enums + --> tests/ui/export_fail_01.rs:6:5 + | +6 | Baz { a: i32, b: u32 }, + | ^^^ From c33e89ba58c69096e041a54d210195d3520376b7 Mon Sep 17 00:00:00 2001 From: bogay Date: Wed, 11 Oct 2023 22:15:10 +0800 Subject: [PATCH 14/19] test(Export): add UI tests --- gdnative-derive/src/export.rs | 27 ++++++++++++------------- gdnative/tests/ui.rs | 3 +++ gdnative/tests/ui/export_fail_02.rs | 8 ++++++++ gdnative/tests/ui/export_fail_02.stderr | 5 +++++ gdnative/tests/ui/export_fail_03.rs | 8 ++++++++ gdnative/tests/ui/export_fail_03.stderr | 11 ++++++++++ gdnative/tests/ui/export_pass.rs | 11 ++++++++++ 7 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 gdnative/tests/ui/export_fail_02.rs create mode 100644 gdnative/tests/ui/export_fail_02.stderr create mode 100644 gdnative/tests/ui/export_fail_03.rs create mode 100644 gdnative/tests/ui/export_fail_03.stderr create mode 100644 gdnative/tests/ui/export_pass.rs diff --git a/gdnative-derive/src/export.rs b/gdnative-derive/src/export.rs index 8efa519f0..7e11b5b38 100644 --- a/gdnative-derive/src/export.rs +++ b/gdnative-derive/src/export.rs @@ -1,14 +1,19 @@ -use proc_macro2::TokenStream as TokenStream2; +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 { let derived_enum = match &input.data { syn::Data::Enum(data) => data, - _ => { - return Err(syn::Error::new( - input.ident.span(), - "#[derive(Export)] only supports fieldless enums", - )) + 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())); } }; @@ -20,14 +25,8 @@ fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result tests/ui/export_fail_02.rs:4:5 + | +4 | pub struct Foo { + | ^^^^^^ diff --git a/gdnative/tests/ui/export_fail_03.rs b/gdnative/tests/ui/export_fail_03.rs new file mode 100644 index 000000000..d89f24118 --- /dev/null +++ b/gdnative/tests/ui/export_fail_03.rs @@ -0,0 +1,8 @@ +use gdnative::prelude::*; + +#[derive(Export, ToVariant)] +pub union Foo { + bar: i32, +} + +fn main() {} diff --git a/gdnative/tests/ui/export_fail_03.stderr b/gdnative/tests/ui/export_fail_03.stderr new file mode 100644 index 000000000..304eff3b3 --- /dev/null +++ b/gdnative/tests/ui/export_fail_03.stderr @@ -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 { + | ^^^ diff --git a/gdnative/tests/ui/export_pass.rs b/gdnative/tests/ui/export_pass.rs new file mode 100644 index 000000000..a5b12b5de --- /dev/null +++ b/gdnative/tests/ui/export_pass.rs @@ -0,0 +1,11 @@ +use gdnative::prelude::*; + +#[derive(Export, ToVariant, Clone, Copy)] +#[variant(enum = "repr")] +#[repr(i32)] +pub enum Foo { + Bar, + Baz, +} + +fn main() {} From 825ad680496c7a0e55cbdc052e149ad351b1247e Mon Sep 17 00:00:00 2001 From: bogay Date: Wed, 11 Oct 2023 22:29:20 +0800 Subject: [PATCH 15/19] docs(Export): fix example code the repr part should be added to change the serialization behavior. --- gdnative-derive/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index f0c3fad16..88f1f6b04 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -675,6 +675,8 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream { /// use gdnative::prelude::*; /// /// #[derive(Debug, PartialEq, Clone, Copy, Export, ToVariant, FromVariant)] +/// #[variant(enum = "repr")] +/// #[repr(i32)] /// enum Dir { /// Up = 1, /// Down = -1, From 98e0f4f02968a2a416d91eeda172417fcfee4777 Mon Sep 17 00:00:00 2001 From: bogay Date: Sat, 14 Oct 2023 00:39:00 +0800 Subject: [PATCH 16/19] feat(EnumHint): add EnumHintEntry to store entry info --- gdnative-core/src/export/property/hint.rs | 70 +++++++++++++++-------- gdnative-derive/src/export.rs | 25 ++++---- test/src/lib.rs | 2 - test/src/test_export_enum.rs | 30 ---------- 4 files changed, 60 insertions(+), 67 deletions(-) delete mode 100644 test/src/test_export_enum.rs diff --git a/gdnative-core/src/export/property/hint.rs b/gdnative-core/src/export/property/hint.rs index b1ffbc787..41d6cc8a7 100644 --- a/gdnative-core/src/export/property/hint.rs +++ b/gdnative-core/src/export/property/hint.rs @@ -1,6 +1,6 @@ //! Strongly typed property hints. -use std::fmt::{self, Write}; +use std::fmt::{self, Display, Write}; use std::ops::RangeInclusive; use crate::core_types::GodotString; @@ -116,52 +116,71 @@ where /// ``` #[derive(Clone, Eq, PartialEq, Debug, Default)] pub struct EnumHint { - values: Vec<(String, Option)>, + entries: Vec, } impl EnumHint { #[inline] - pub fn new(values: Vec) -> Self { - let values = values.into_iter().map(|v| (v, None)).collect(); - EnumHint { values } + pub fn new(keys: Vec) -> Self { + let entries = keys.into_iter().map(EnumHintEntry::new).collect(); + EnumHint { entries } } #[inline] - pub fn with_numbers(values: Vec<(String, i64)>) -> Self { - let values = values - .into_iter() - .map(|(key, val)| (key, Some(val))) - .collect(); - EnumHint { values } + pub fn with_entries(entries: Vec) -> Self { + EnumHint { entries } } /// Formats the hint as a Godot hint string. fn to_godot_hint_string(&self) -> GodotString { let mut s = String::new(); - let mut iter = self.values.iter(); - let write_item = |s: &mut String, item: &(String, Option)| match item { - (key, Some(val)) => { - write!(s, "{key}:{val}") - } - (key, None) => { - write!(s, "{key}") - } - }; + let mut iter = self.entries.iter(); if let Some(first) = iter.next() { - write_item(&mut s, first).unwrap(); + write!(s, "{first}").unwrap(); } for rest in iter { - write!(s, ",").unwrap(); - write_item(&mut s, rest).unwrap(); + write!(s, ",{rest}").unwrap(); } s.into() } } +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct EnumHintEntry { + key: String, + value: Option, +} + +impl EnumHintEntry { + #[inline] + pub fn new(key: String) -> Self { + Self { key, value: None } + } + + #[inline] + pub fn with_value(key: String, value: i64) -> Self { + Self { + key, + value: Some(value), + } + } +} + +impl Display for EnumHintEntry { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.key)?; + if let Some(value) = self.value { + write!(f, ":{}", value)?; + } + Ok(()) + } +} + /// Possible hints for integers. #[derive(Clone, Debug)] #[non_exhaustive] @@ -495,6 +514,9 @@ godot_test!(test_enum_hint_without_mapping { }); godot_test!(test_enum_hint_with_mapping { - let hint = EnumHint::with_numbers(vec![("Foo".into(), 42), ("Bar".into(), 67)]); + let hint = EnumHint::with_entries(vec![ + EnumHintEntry::with_value("Foo".to_string(), 42), + EnumHintEntry::with_value("Bar".to_string(), 67), + ]); assert_eq!(hint.to_godot_hint_string().to_string(), "Foo:42,Bar:67".to_string(),); }); diff --git a/gdnative-derive/src/export.rs b/gdnative-derive/src/export.rs index 7e11b5b38..4b1727f1c 100644 --- a/gdnative-derive/src/export.rs +++ b/gdnative-derive/src/export.rs @@ -1,3 +1,4 @@ +use crate::crate_gdnative_core; use proc_macro2::{Span, TokenStream as TokenStream2}; use syn::spanned::Spanned; use syn::{DeriveInput, Fields}; @@ -35,30 +36,32 @@ fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result>(); let impl_block = quote! { - impl ::gdnative::export::Export for #enum_ty { - type Hint = ::gdnative::export::hint::IntHint; - #[inline] - fn export_info(hint: Option) -> ::gdnative::export::ExportInfo { - if let Some(hint) = hint { - return hint.export_info(); - } else { + const _: () = { + pub enum NoHint {} + + impl #gdnative_core::export::Export for #enum_ty { + type Hint = NoHint; + + #[inline] + fn export_info(_hint: Option) -> #gdnative_core::export::ExportInfo { let mappings = vec![ #(#mappings),* ]; - let enum_hint = ::gdnative::export::hint::EnumHint::with_numbers(mappings); - return ::gdnative::export::hint::IntHint::::Enum(enum_hint).export_info(); + let enum_hint = #gdnative_core::export::hint::EnumHint::with_entries(mappings); + return #gdnative_core::export::hint::IntHint::::Enum(enum_hint).export_info(); } } - } + }; }; Ok(impl_block) diff --git a/test/src/lib.rs b/test/src/lib.rs index d32d6d75d..8aa79bd38 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -8,7 +8,6 @@ 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; @@ -53,7 +52,6 @@ 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() } diff --git a/test/src/test_export_enum.rs b/test/src/test_export_enum.rs deleted file mode 100644 index a738cdd28..000000000 --- a/test/src/test_export_enum.rs +++ /dev/null @@ -1,30 +0,0 @@ -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()); -}); From e055ea2670789cea4eca64699734c1e60a0a1f42 Mon Sep 17 00:00:00 2001 From: bogay Date: Sat, 14 Oct 2023 01:24:53 +0800 Subject: [PATCH 17/19] test: remove duplicated tests --- test/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/src/lib.rs b/test/src/lib.rs index 8aa79bd38..4858da0d4 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -31,8 +31,6 @@ pub extern "C" fn run_tests( 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(); From 7159dd9e51009786f97c062c0838cee62316af73 Mon Sep 17 00:00:00 2001 From: bogay Date: Sun, 15 Oct 2023 02:29:24 +0800 Subject: [PATCH 18/19] feat(Export): add #[export(...)] attribute --- gdnative-derive/src/export.rs | 137 +++++++++++++++++++++--- gdnative-derive/src/lib.rs | 5 +- gdnative/tests/ui.rs | 6 ++ gdnative/tests/ui/export_fail_01.rs | 1 + gdnative/tests/ui/export_fail_01.stderr | 8 +- gdnative/tests/ui/export_fail_02.rs | 1 + gdnative/tests/ui/export_fail_02.stderr | 4 +- gdnative/tests/ui/export_fail_03.rs | 1 + gdnative/tests/ui/export_fail_03.stderr | 8 +- gdnative/tests/ui/export_fail_04.rs | 21 ++++ gdnative/tests/ui/export_fail_04.stderr | 17 +++ gdnative/tests/ui/export_fail_05.rs | 9 ++ gdnative/tests/ui/export_fail_05.stderr | 5 + gdnative/tests/ui/export_fail_06.rs | 9 ++ gdnative/tests/ui/export_fail_06.stderr | 5 + gdnative/tests/ui/export_fail_07.rs | 9 ++ gdnative/tests/ui/export_fail_07.stderr | 5 + gdnative/tests/ui/export_fail_08.rs | 9 ++ gdnative/tests/ui/export_fail_08.stderr | 5 + gdnative/tests/ui/export_fail_09.rs | 10 ++ gdnative/tests/ui/export_fail_09.stderr | 5 + gdnative/tests/ui/export_pass.rs | 1 + 22 files changed, 255 insertions(+), 26 deletions(-) create mode 100644 gdnative/tests/ui/export_fail_04.rs create mode 100644 gdnative/tests/ui/export_fail_04.stderr create mode 100644 gdnative/tests/ui/export_fail_05.rs create mode 100644 gdnative/tests/ui/export_fail_05.stderr create mode 100644 gdnative/tests/ui/export_fail_06.rs create mode 100644 gdnative/tests/ui/export_fail_06.stderr create mode 100644 gdnative/tests/ui/export_fail_07.rs create mode 100644 gdnative/tests/ui/export_fail_07.stderr create mode 100644 gdnative/tests/ui/export_fail_08.rs create mode 100644 gdnative/tests/ui/export_fail_08.stderr create mode 100644 gdnative/tests/ui/export_fail_09.rs create mode 100644 gdnative/tests/ui/export_fail_09.stderr diff --git a/gdnative-derive/src/export.rs b/gdnative-derive/src/export.rs index 4b1727f1c..537586f6a 100644 --- a/gdnative-derive/src/export.rs +++ b/gdnative-derive/src/export.rs @@ -1,25 +1,134 @@ use crate::crate_gdnative_core; -use proc_macro2::{Span, TokenStream as TokenStream2}; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use quote::ToTokens; use syn::spanned::Spanned; -use syn::{DeriveInput, Fields}; +use syn::{DeriveInput, Fields, Meta}; + +#[derive(Copy, Clone, Debug)] +enum Kind { + Enum, +} + +#[derive(Debug)] +struct DeriveData { + kind: Kind, + ident: Ident, + data: syn::Data, +} + +fn parse_derive_input(input: DeriveInput) -> syn::Result { + let DeriveInput { + ident, data, attrs, .. + } = input.clone(); + + let (kind, errors) = attrs + .iter() + .filter(|attr| attr.path.is_ident("export")) + .fold((None, vec![]), |(mut kind, mut errors), attr| { + let list = match attr.parse_meta() { + Ok(meta) => match meta { + Meta::List(list) => list, + Meta::Path(path) => { + errors.push(syn::Error::new( + path.span(), + "missing macro arguments. expected #[export(...)]", + )); + return (kind, errors); + } + Meta::NameValue(pair) => { + errors.push(syn::Error::new( + pair.span(), + "missing macro arguments. expected #[export(...)]", + )); + return (kind, errors); + } + }, + Err(e) => { + errors.push(syn::Error::new( + e.span(), + format!("unknown attribute format. expected #[export(...)]: {e}"), + )); + return (kind, errors); + } + }; + + for meta in list.nested.into_iter() { + let syn::NestedMeta::Meta(Meta::NameValue(pair)) = meta else { + errors.push(syn::Error::new( + meta.span(), + "invalid syntax. expected #[export(key = \"value\")]", + )); + continue; + }; + + if !pair.path.is_ident("kind") { + errors.push(syn::Error::new( + pair.span(), + format!("found {}, expected kind", pair.path.into_token_stream()), + )); + continue; + } + + let syn::Lit::Str(str) = pair.lit else { + errors.push(syn::Error::new( + pair.lit.span(), + "string literal expected, wrap with double quotes", + )); + continue; + }; + + match str.value().as_str() { + "enum" => { + if kind.is_some() { + errors.push(syn::Error::new(str.span(), "kind already set")); + } else { + kind = Some(Kind::Enum); + } + } + _ => { + errors.push(syn::Error::new(str.span(), "unknown kind, expected enum")); + } + } + } + + (kind, errors) + }); + + if let Some(err) = errors.into_iter().reduce(|mut acc, err| { + acc.combine(err); + acc + }) { + return Err(err); + } + + match kind { + Some(kind) => Ok(DeriveData { ident, kind, data }), + None => Err(syn::Error::new(Span::call_site(), "kind not found")), + } +} 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 { - 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())); - } - }; +pub(crate) fn derive_export(input: DeriveInput) -> syn::Result { + let derive_data = parse_derive_input(input)?; - let export_impl = impl_export(&input.ident, derived_enum)?; - Ok(export_impl) + match derive_data.kind { + Kind::Enum => { + let derived_enum = match derive_data.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(&derive_data.ident, &derived_enum)?; + Ok(export_impl) + } + } } fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index 88f1f6b04..2d4138879 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -676,6 +676,7 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream { /// /// #[derive(Debug, PartialEq, Clone, Copy, Export, ToVariant, FromVariant)] /// #[variant(enum = "repr")] +/// #[export(kind = "enum")] /// #[repr(i32)] /// enum Dir { /// Up = 1, @@ -712,10 +713,10 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream { /// f1: i32 /// } /// ``` -#[proc_macro_derive(Export)] +#[proc_macro_derive(Export, attributes(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) { + match export::derive_export(derive_input) { Ok(stream) => stream.into(), Err(err) => err.to_compile_error().into(), } diff --git a/gdnative/tests/ui.rs b/gdnative/tests/ui.rs index 4a8684396..ec353ee01 100644 --- a/gdnative/tests/ui.rs +++ b/gdnative/tests/ui.rs @@ -46,6 +46,12 @@ fn ui_tests() { 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"); + t.compile_fail("tests/ui/export_fail_04.rs"); + t.compile_fail("tests/ui/export_fail_05.rs"); + t.compile_fail("tests/ui/export_fail_06.rs"); + t.compile_fail("tests/ui/export_fail_07.rs"); + t.compile_fail("tests/ui/export_fail_08.rs"); + t.compile_fail("tests/ui/export_fail_09.rs"); } // FIXME(rust/issues/54725): Full path spans are only available on nightly as of now diff --git a/gdnative/tests/ui/export_fail_01.rs b/gdnative/tests/ui/export_fail_01.rs index c69fed7d3..0a2ea147a 100644 --- a/gdnative/tests/ui/export_fail_01.rs +++ b/gdnative/tests/ui/export_fail_01.rs @@ -1,6 +1,7 @@ use gdnative::prelude::*; #[derive(Export, ToVariant)] +#[export(kind = "enum")] pub enum Foo { Bar(String), Baz { a: i32, b: u32 }, diff --git a/gdnative/tests/ui/export_fail_01.stderr b/gdnative/tests/ui/export_fail_01.stderr index 79cc55485..2848a43c4 100644 --- a/gdnative/tests/ui/export_fail_01.stderr +++ b/gdnative/tests/ui/export_fail_01.stderr @@ -1,11 +1,11 @@ error: #[derive(Export)] only supports fieldless enums - --> tests/ui/export_fail_01.rs:5:5 + --> tests/ui/export_fail_01.rs:6:5 | -5 | Bar(String), +6 | Bar(String), | ^^^ error: #[derive(Export)] only supports fieldless enums - --> tests/ui/export_fail_01.rs:6:5 + --> tests/ui/export_fail_01.rs:7:5 | -6 | Baz { a: i32, b: u32 }, +7 | Baz { a: i32, b: u32 }, | ^^^ diff --git a/gdnative/tests/ui/export_fail_02.rs b/gdnative/tests/ui/export_fail_02.rs index 7ce4c080e..fa1c82498 100644 --- a/gdnative/tests/ui/export_fail_02.rs +++ b/gdnative/tests/ui/export_fail_02.rs @@ -1,6 +1,7 @@ use gdnative::prelude::*; #[derive(Export, ToVariant)] +#[export(kind = "enum")] pub struct Foo { bar: i32, } diff --git a/gdnative/tests/ui/export_fail_02.stderr b/gdnative/tests/ui/export_fail_02.stderr index 288715542..64b5e1656 100644 --- a/gdnative/tests/ui/export_fail_02.stderr +++ b/gdnative/tests/ui/export_fail_02.stderr @@ -1,5 +1,5 @@ error: #[derive(Export)] only supports fieldless enums - --> tests/ui/export_fail_02.rs:4:5 + --> tests/ui/export_fail_02.rs:5:5 | -4 | pub struct Foo { +5 | pub struct Foo { | ^^^^^^ diff --git a/gdnative/tests/ui/export_fail_03.rs b/gdnative/tests/ui/export_fail_03.rs index d89f24118..4641ffe2e 100644 --- a/gdnative/tests/ui/export_fail_03.rs +++ b/gdnative/tests/ui/export_fail_03.rs @@ -1,6 +1,7 @@ use gdnative::prelude::*; #[derive(Export, ToVariant)] +#[export(kind = "enum")] pub union Foo { bar: i32, } diff --git a/gdnative/tests/ui/export_fail_03.stderr b/gdnative/tests/ui/export_fail_03.stderr index 304eff3b3..b9184f792 100644 --- a/gdnative/tests/ui/export_fail_03.stderr +++ b/gdnative/tests/ui/export_fail_03.stderr @@ -1,11 +1,11 @@ error: #[derive(Export)] only supports fieldless enums - --> tests/ui/export_fail_03.rs:4:5 + --> tests/ui/export_fail_03.rs:5:5 | -4 | pub union Foo { +5 | 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 { - | ^^^ +4 | #[export(kind = "enum")] + | ^ diff --git a/gdnative/tests/ui/export_fail_04.rs b/gdnative/tests/ui/export_fail_04.rs new file mode 100644 index 000000000..885285277 --- /dev/null +++ b/gdnative/tests/ui/export_fail_04.rs @@ -0,0 +1,21 @@ +use gdnative::prelude::*; + +#[derive(Export, ToVariant)] +#[export] +pub enum Foo { + Bar, +} + +#[derive(Export, ToVariant)] +#[export = "foo"] +pub enum Bar { + Foo, +} + +#[derive(Export, ToVariant)] +#[export(weird format a => b)] +pub enum Baz { + Quux, +} + +fn main() {} diff --git a/gdnative/tests/ui/export_fail_04.stderr b/gdnative/tests/ui/export_fail_04.stderr new file mode 100644 index 000000000..caa03b0d2 --- /dev/null +++ b/gdnative/tests/ui/export_fail_04.stderr @@ -0,0 +1,17 @@ +error: missing macro arguments. expected #[export(...)] + --> tests/ui/export_fail_04.rs:4:3 + | +4 | #[export] + | ^^^^^^ + +error: missing macro arguments. expected #[export(...)] + --> tests/ui/export_fail_04.rs:10:3 + | +10 | #[export = "foo"] + | ^^^^^^ + +error: unknown attribute format. expected #[export(...)]: expected `,` + --> tests/ui/export_fail_04.rs:16:16 + | +16 | #[export(weird format a => b)] + | ^^^^^^ diff --git a/gdnative/tests/ui/export_fail_05.rs b/gdnative/tests/ui/export_fail_05.rs new file mode 100644 index 000000000..ff3a5f39d --- /dev/null +++ b/gdnative/tests/ui/export_fail_05.rs @@ -0,0 +1,9 @@ +use gdnative::prelude::*; + +#[derive(Export, ToVariant)] +#[export(kind)] +pub enum Foo { + Bar, +} + +fn main() {} diff --git a/gdnative/tests/ui/export_fail_05.stderr b/gdnative/tests/ui/export_fail_05.stderr new file mode 100644 index 000000000..752be2a32 --- /dev/null +++ b/gdnative/tests/ui/export_fail_05.stderr @@ -0,0 +1,5 @@ +error: invalid syntax. expected #[export(key = "value")] + --> tests/ui/export_fail_05.rs:4:10 + | +4 | #[export(kind)] + | ^^^^ diff --git a/gdnative/tests/ui/export_fail_06.rs b/gdnative/tests/ui/export_fail_06.rs new file mode 100644 index 000000000..0413ea008 --- /dev/null +++ b/gdnative/tests/ui/export_fail_06.rs @@ -0,0 +1,9 @@ +use gdnative::prelude::*; + +#[derive(Export, ToVariant)] +#[export(kinb = "enum")] +pub enum Foo { + Bar, +} + +fn main() {} diff --git a/gdnative/tests/ui/export_fail_06.stderr b/gdnative/tests/ui/export_fail_06.stderr new file mode 100644 index 000000000..1105998c5 --- /dev/null +++ b/gdnative/tests/ui/export_fail_06.stderr @@ -0,0 +1,5 @@ +error: found kinb, expected kind + --> tests/ui/export_fail_06.rs:4:10 + | +4 | #[export(kinb = "enum")] + | ^^^^ diff --git a/gdnative/tests/ui/export_fail_07.rs b/gdnative/tests/ui/export_fail_07.rs new file mode 100644 index 000000000..954c3a3ac --- /dev/null +++ b/gdnative/tests/ui/export_fail_07.rs @@ -0,0 +1,9 @@ +use gdnative::prelude::*; + +#[derive(Export, ToVariant)] +#[export(kind = 123)] +pub enum Foo { + Bar, +} + +fn main() {} diff --git a/gdnative/tests/ui/export_fail_07.stderr b/gdnative/tests/ui/export_fail_07.stderr new file mode 100644 index 000000000..ebf80930a --- /dev/null +++ b/gdnative/tests/ui/export_fail_07.stderr @@ -0,0 +1,5 @@ +error: string literal expected, wrap with double quotes + --> tests/ui/export_fail_07.rs:4:17 + | +4 | #[export(kind = 123)] + | ^^^ diff --git a/gdnative/tests/ui/export_fail_08.rs b/gdnative/tests/ui/export_fail_08.rs new file mode 100644 index 000000000..dda42bc8a --- /dev/null +++ b/gdnative/tests/ui/export_fail_08.rs @@ -0,0 +1,9 @@ +use gdnative::prelude::*; + +#[derive(Export, ToVariant)] +#[export(kind = "foo")] +pub enum Foo { + Bar, +} + +fn main() {} diff --git a/gdnative/tests/ui/export_fail_08.stderr b/gdnative/tests/ui/export_fail_08.stderr new file mode 100644 index 000000000..ce38e6529 --- /dev/null +++ b/gdnative/tests/ui/export_fail_08.stderr @@ -0,0 +1,5 @@ +error: unknown kind, expected enum + --> tests/ui/export_fail_08.rs:4:17 + | +4 | #[export(kind = "foo")] + | ^^^^^ diff --git a/gdnative/tests/ui/export_fail_09.rs b/gdnative/tests/ui/export_fail_09.rs new file mode 100644 index 000000000..6077c5b90 --- /dev/null +++ b/gdnative/tests/ui/export_fail_09.rs @@ -0,0 +1,10 @@ +use gdnative::prelude::*; + +#[derive(Export, ToVariant)] +#[export(kind = "enum")] +#[export(kind = "enum")] +pub enum Foo { + Bar, +} + +fn main() {} diff --git a/gdnative/tests/ui/export_fail_09.stderr b/gdnative/tests/ui/export_fail_09.stderr new file mode 100644 index 000000000..73bd44a7c --- /dev/null +++ b/gdnative/tests/ui/export_fail_09.stderr @@ -0,0 +1,5 @@ +error: kind already set + --> tests/ui/export_fail_09.rs:5:17 + | +5 | #[export(kind = "enum")] + | ^^^^^^ diff --git a/gdnative/tests/ui/export_pass.rs b/gdnative/tests/ui/export_pass.rs index a5b12b5de..d417a105a 100644 --- a/gdnative/tests/ui/export_pass.rs +++ b/gdnative/tests/ui/export_pass.rs @@ -2,6 +2,7 @@ use gdnative::prelude::*; #[derive(Export, ToVariant, Clone, Copy)] #[variant(enum = "repr")] +#[export(kind = "enum")] #[repr(i32)] pub enum Foo { Bar, From 348bac2d53e3cf2e200f064d89de296718cb43b2 Mon Sep 17 00:00:00 2001 From: bogay Date: Tue, 17 Oct 2023 01:10:57 +0800 Subject: [PATCH 19/19] test(ui): use glob to match tests for #[derive(Export)] --- gdnative/tests/ui.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/gdnative/tests/ui.rs b/gdnative/tests/ui.rs index ec353ee01..bfd7ef401 100644 --- a/gdnative/tests/ui.rs +++ b/gdnative/tests/ui.rs @@ -43,15 +43,7 @@ fn ui_tests() { // 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"); - t.compile_fail("tests/ui/export_fail_04.rs"); - t.compile_fail("tests/ui/export_fail_05.rs"); - t.compile_fail("tests/ui/export_fail_06.rs"); - t.compile_fail("tests/ui/export_fail_07.rs"); - t.compile_fail("tests/ui/export_fail_08.rs"); - t.compile_fail("tests/ui/export_fail_09.rs"); + t.compile_fail("tests/ui/export_fail_*.rs"); } // FIXME(rust/issues/54725): Full path spans are only available on nightly as of now