From be7e6cc81f42e3380f9b48934245368ef900476c Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Sun, 27 Nov 2022 15:14:38 +0000 Subject: [PATCH 1/8] allow custom derive to work on generics --- godot-macros/src/derive_godot_class.rs | 20 +++++++++++--------- godot-macros/src/godot_api.rs | 15 ++++++--------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/godot-macros/src/derive_godot_class.rs b/godot-macros/src/derive_godot_class.rs index 4413f53d7..98ad0d9f4 100644 --- a/godot-macros/src/derive_godot_class.rs +++ b/godot-macros/src/derive_godot_class.rs @@ -9,7 +9,7 @@ use crate::{util, ParseResult}; use proc_macro2::{Ident, Punct, Span, TokenStream}; use quote::spanned::Spanned; use quote::{format_ident, quote}; -use venial::{Attribute, NamedField, Struct, StructFields, TyExpr}; +use venial::{Attribute, GenericParamList, NamedField, Struct, StructFields, TyExpr, WhereClause}; pub fn transform(input: TokenStream) -> ParseResult { let decl = venial::parse_declaration(input)?; @@ -25,14 +25,16 @@ pub fn transform(input: TokenStream) -> ParseResult { let base_ty_str = struct_cfg.base_ty.to_string(); let class_name = &class.name; let class_name_str = class.name.to_string(); + let generics = &class.generic_params; + let where_clause = &class.where_clause; let inherits_macro = format_ident!("inherits_transitive_{}", &base_ty_str); let prv = quote! { ::godot::private }; - let deref_impl = make_deref_impl(class_name, &fields); + let deref_impl = make_deref_impl(class_name, generics, where_clause, &fields); let (godot_init_impl, create_fn); if struct_cfg.has_generated_init { - godot_init_impl = make_godot_init_impl(class_name, fields); + godot_init_impl = make_godot_init_impl(class_name, where_clause, generics, fields); create_fn = quote! { Some(#prv::callbacks::create::<#class_name>) }; } else { godot_init_impl = TokenStream::new(); @@ -40,7 +42,7 @@ pub fn transform(input: TokenStream) -> ParseResult { }; Ok(quote! { - impl ::godot::obj::GodotClass for #class_name { + impl #generics ::godot::obj::GodotClass for #class_name #generics #where_clause { type Base = ::godot::engine::#base_ty; type Declarer = ::godot::obj::dom::UserDomain; type Mem = ::Mem; @@ -208,7 +210,7 @@ impl ExportedField { } } -fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream { +fn make_godot_init_impl(class_name: &Ident, where_clause: &Option, generics: &Option, fields: Fields) -> TokenStream { let base_init = if let Some(ExportedField { name, .. }) = fields.base_field { quote! { #name: base, } } else { @@ -220,7 +222,7 @@ fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream { }); quote! { - impl ::godot::obj::cap::GodotInit for #class_name { + impl #generics ::godot::obj::cap::GodotInit for #class_name #generics #where_clause { fn __godot_init(base: ::godot::obj::Base) -> Self { Self { #( #rest_init )* @@ -231,7 +233,7 @@ fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream { } } -fn make_deref_impl(class_name: &Ident, fields: &Fields) -> TokenStream { +fn make_deref_impl(class_name: &Ident, generics: &Option, where_clause: &Option, fields: &Fields) -> TokenStream { let base_field = if let Some(ExportedField { name, .. }) = &fields.base_field { name } else { @@ -239,14 +241,14 @@ fn make_deref_impl(class_name: &Ident, fields: &Fields) -> TokenStream { }; quote! { - impl std::ops::Deref for #class_name { + impl #generics std::ops::Deref for #class_name #generics #where_clause { type Target = ::Base; fn deref(&self) -> &Self::Target { &*self.#base_field } } - impl std::ops::DerefMut for #class_name { + impl #generics std::ops::DerefMut for #class_name #generics #where_clause { fn deref_mut(&mut self) -> &mut Self::Target { &mut *self.#base_field } diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index c756288a8..adbe861eb 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -23,13 +23,6 @@ pub fn transform(input: TokenStream) -> Result { )?, }; - if decl.impl_generic_params.is_some() { - bail( - "#[godot_api] currently does not support generic parameters", - &decl, - )?; - } - if decl.self_ty.as_path().is_none() { return bail("invalid Self type for #[godot_api] impl", decl); }; @@ -71,13 +64,15 @@ fn transform_inherent_impl(mut decl: Impl) -> Result { let (funcs, signals) = process_godot_fns(&mut decl)?; let signal_name_strs = signals.into_iter().map(|ident| ident.to_string()); + let generics = &decl.impl_generic_params; + let where_clause = &decl.where_clause; let prv = quote! { ::godot::private }; let result = quote! { #decl - impl ::godot::obj::cap::ImplementsGodotApi for #class_name { + impl #generics ::godot::obj::cap::ImplementsGodotApi for #class_name #generics #where_clause { //fn __register_methods(_builder: &mut ::godot::builder::ClassBuilder) { fn __register_methods() { #( @@ -218,6 +213,8 @@ fn extract_attributes(method: &Function) -> Result, Error> { fn transform_trait_impl(original_impl: Impl) -> Result { let class_name = util::validate_impl(&original_impl, Some("GodotExt"), "godot_api")?; let class_name_str = class_name.to_string(); + let generics = &original_impl.impl_generic_params; + let where_clause = &original_impl.where_clause; let mut godot_init_impl = TokenStream::new(); let mut register_fn = quote! { None }; @@ -245,7 +242,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result { "init" => { godot_init_impl = quote! { - impl ::godot::obj::cap::GodotInit for #class_name { + impl #generics ::godot::obj::cap::GodotInit for #class_name #generics #where_clause { fn __godot_init(base: ::godot::obj::Base) -> Self { ::init(base) } From 7b466cd81780a498414ded4b2ab2979ecc524575 Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Sun, 27 Nov 2022 15:26:01 +0000 Subject: [PATCH 2/8] fix parameter order in make_godot_init_impl --- godot-macros/src/derive_godot_class.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/godot-macros/src/derive_godot_class.rs b/godot-macros/src/derive_godot_class.rs index 98ad0d9f4..bb8daa7a6 100644 --- a/godot-macros/src/derive_godot_class.rs +++ b/godot-macros/src/derive_godot_class.rs @@ -34,7 +34,7 @@ pub fn transform(input: TokenStream) -> ParseResult { let (godot_init_impl, create_fn); if struct_cfg.has_generated_init { - godot_init_impl = make_godot_init_impl(class_name, where_clause, generics, fields); + godot_init_impl = make_godot_init_impl(class_name, generics, where_clause, fields); create_fn = quote! { Some(#prv::callbacks::create::<#class_name>) }; } else { godot_init_impl = TokenStream::new(); @@ -210,7 +210,7 @@ impl ExportedField { } } -fn make_godot_init_impl(class_name: &Ident, where_clause: &Option, generics: &Option, fields: Fields) -> TokenStream { +fn make_godot_init_impl(class_name: &Ident, generics: &Option, where_clause: &Option, fields: Fields) -> TokenStream { let base_init = if let Some(ExportedField { name, .. }) = fields.base_field { quote! { #name: base, } } else { From 6ba754331bc339f6c0d6c1678653f402f9954f77 Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Sun, 27 Nov 2022 16:11:46 +0000 Subject: [PATCH 3/8] add missed generics and where clauses --- godot-macros/src/godot_api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index adbe861eb..651b1b1f9 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -278,9 +278,9 @@ fn transform_trait_impl(original_impl: Impl) -> Result { #original_impl #godot_init_impl - impl ::godot::private::You_forgot_the_attribute__godot_api for #class_name {} + impl #generics ::godot::private::You_forgot_the_attribute__godot_api for #class_name #generics #where_clause {} - impl ::godot::obj::cap::ImplementsGodotExt for #class_name { + impl #generics ::godot::obj::cap::ImplementsGodotExt for #class_name #generics #where_clause { fn __virtual_call(name: &str) -> ::godot::sys::GDNativeExtensionClassCallVirtual { //println!("virtual_call: {}.{}", std::any::type_name::(), name); From 9a5c27cf5855a964e9795adb2fd147521d085b74 Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Sun, 27 Nov 2022 16:12:04 +0000 Subject: [PATCH 4/8] remove check for generics as we now support them --- godot-macros/src/util.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/godot-macros/src/util.rs b/godot-macros/src/util.rs index 29a5e0da9..f578b7042 100644 --- a/godot-macros/src/util.rs +++ b/godot-macros/src/util.rs @@ -221,14 +221,7 @@ pub(crate) fn validate_impl( // impl Trait for Self -- validate Self if let Some(segment) = extract_typename(&original_impl.self_ty) { - if segment.generic_args.is_none() { - Ok(segment.ident) - } else { - bail( - format!("#[{attr}] for does currently not support generic arguments"), - &original_impl, - ) - } + Ok(segment.ident) } else { bail( format!("#[{attr}] requires Self type to be a simple path"), From 9dc70186a08e21949fab22a9c40fd20f52a7f83c Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Sun, 27 Nov 2022 20:52:23 +0000 Subject: [PATCH 5/8] Generic Struct Test --- itest/rust/src/generic_struct_test.rs | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 itest/rust/src/generic_struct_test.rs diff --git a/itest/rust/src/generic_struct_test.rs b/itest/rust/src/generic_struct_test.rs new file mode 100644 index 000000000..d49285972 --- /dev/null +++ b/itest/rust/src/generic_struct_test.rs @@ -0,0 +1,56 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#![allow(dead_code)] + +use godot::bind::{godot_api, GodotClass, GodotExt}; +use godot::builtin::GodotString; +use godot::obj::{Base, Gd}; +use godot::test::itest; +use std::marker::PhantomData; + +/// A simple abstractio to see if we can derive GodotClass for Generic Structs +trait Abstraction {} +struct A {} +struct B {} +impl Abstraction for A {} +impl Abstraction for B {} + + +#[derive(GodotClass, Debug)] +#[class(init, base=Node)] +struct GenericStructTest { + #[base] + some_base: Base, + // Use phantom data so we're _only_ testing the generic aspect + phantom_data: PhantomData +} + +#[godot_api] +impl GenericStructTest {} + +#[godot_api] +impl GodotExt for GenericStructTest {} + +pub(crate) fn run() -> bool { + let mut ok = true; + ok &= test_to_string(); + ok +} + +// pub(crate) fn register() { +// godot::register_class::(); +// } + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +#[itest] +fn test_to_string() { + let _obj1 = Gd::>::new_default(); + dbg!(_obj1); + let _obj2 = Gd::>::new_default(); + dbg!(_obj2); +} From c6b424385d72f719fdd325495314e1816d59b3a2 Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Sun, 27 Nov 2022 21:04:38 +0000 Subject: [PATCH 6/8] add config for macos to itest --- itest/godot/itest.gdextension | 1 + 1 file changed, 1 insertion(+) diff --git a/itest/godot/itest.gdextension b/itest/godot/itest.gdextension index cbf50b53f..8f9ed871d 100644 --- a/itest/godot/itest.gdextension +++ b/itest/godot/itest.gdextension @@ -4,3 +4,4 @@ entry_symbol = "itest_init" [libraries] linux.64 = "res://../../target/debug/libitest.so" windows.64 = "res://../../target/debug/itest.dll" +macos.x86_64 = "res://../../target/debug/itest.dylib" From 411d36de0d90ed6d3c330cc6a8c58a0bba86c30e Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Tue, 29 Nov 2022 21:21:54 +0000 Subject: [PATCH 7/8] fix macos lib path for itest --- itest/godot/itest.gdextension | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itest/godot/itest.gdextension b/itest/godot/itest.gdextension index 8f9ed871d..85f17f42f 100644 --- a/itest/godot/itest.gdextension +++ b/itest/godot/itest.gdextension @@ -4,4 +4,4 @@ entry_symbol = "itest_init" [libraries] linux.64 = "res://../../target/debug/libitest.so" windows.64 = "res://../../target/debug/itest.dll" -macos.x86_64 = "res://../../target/debug/itest.dylib" +macos.x86_64 = "res://../../target/debug/libitest.dylib" From b8e4d8d92954c01b684f69651a8d85aeba26dd6f Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Tue, 29 Nov 2022 21:41:15 +0000 Subject: [PATCH 8/8] test correctly fails --- itest/rust/src/generic_struct_test.rs | 15 +++++++++++---- itest/rust/src/lib.rs | 2 ++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/itest/rust/src/generic_struct_test.rs b/itest/rust/src/generic_struct_test.rs index d49285972..15dbea606 100644 --- a/itest/rust/src/generic_struct_test.rs +++ b/itest/rust/src/generic_struct_test.rs @@ -6,15 +6,18 @@ #![allow(dead_code)] +use std::fmt::Debug; use godot::bind::{godot_api, GodotClass, GodotExt}; -use godot::builtin::GodotString; +use godot::engine::Node; use godot::obj::{Base, Gd}; use godot::test::itest; use std::marker::PhantomData; /// A simple abstractio to see if we can derive GodotClass for Generic Structs trait Abstraction {} +#[derive(Debug)] struct A {} +#[derive(Debug)] struct B {} impl Abstraction for A {} impl Abstraction for B {} @@ -22,7 +25,7 @@ impl Abstraction for B {} #[derive(GodotClass, Debug)] #[class(init, base=Node)] -struct GenericStructTest { +struct GenericStructTest where T: Abstraction + Debug { #[base] some_base: Base, // Use phantom data so we're _only_ testing the generic aspect @@ -30,10 +33,14 @@ struct GenericStructTest { } #[godot_api] -impl GenericStructTest {} +impl GenericStructTest where T: Abstraction + Debug { + fn get_phantom_data(&self) -> String { + format!("{:?}", self.phantom_data) + } +} #[godot_api] -impl GodotExt for GenericStructTest {} +impl GodotExt for GenericStructTest where T: Abstraction + Debug {} pub(crate) fn run() -> bool { let mut ok = true; diff --git a/itest/rust/src/lib.rs b/itest/rust/src/lib.rs index af5be2576..42bb6802e 100644 --- a/itest/rust/src/lib.rs +++ b/itest/rust/src/lib.rs @@ -12,6 +12,7 @@ use std::panic::UnwindSafe; mod base_test; mod enum_test; mod gdscript_ffi_test; +mod generic_struct_test; mod node_test; mod object_test; mod singleton_test; @@ -24,6 +25,7 @@ fn run_tests() -> bool { let mut ok = true; ok &= base_test::run(); ok &= gdscript_ffi_test::run(); + ok &= generic_struct_test::run(); ok &= node_test::run(); ok &= enum_test::run(); ok &= object_test::run();