diff --git a/godot-core/src/builtin/array.rs b/godot-core/src/builtin/array.rs index 839c68a4a..64fa17164 100644 --- a/godot-core/src/builtin/array.rs +++ b/godot-core/src/builtin/array.rs @@ -713,7 +713,7 @@ impl ToVariant for Array { impl FromVariant for Array { fn try_from_variant(variant: &Variant) -> Result { if variant.get_type() != Self::variant_type() { - return Err(VariantConversionError); + return Err(VariantConversionError::BadType); } let array = unsafe { Self::from_sys_init_default(|self_ptr| { diff --git a/godot-core/src/builtin/variant/impls.rs b/godot-core/src/builtin/variant/impls.rs index 76ba245c8..eae37e096 100644 --- a/godot-core/src/builtin/variant/impls.rs +++ b/godot-core/src/builtin/variant/impls.rs @@ -58,7 +58,7 @@ macro_rules! impl_variant_traits { fn try_from_variant(variant: &Variant) -> Result { // Type check -- at the moment, a strict match is required. if variant.get_type() != Self::variant_type() { - return Err(VariantConversionError) + return Err(VariantConversionError::BadType) } // In contrast to T -> Variant, the conversion Variant -> T assumes @@ -92,7 +92,7 @@ macro_rules! impl_variant_traits_int { impl FromVariant for $T { fn try_from_variant(v: &Variant) -> Result { i64::try_from_variant(v) - .and_then(|i| <$T>::try_from(i).map_err(|_e| VariantConversionError)) + .and_then(|i| <$T>::try_from(i).map_err(|_e| VariantConversionError::BadType)) } } @@ -251,7 +251,7 @@ impl ToVariant for T { impl FromVariant for T { fn try_from_variant(variant: &Variant) -> Result { ::try_from_variant(variant) - .and_then(|int| Self::try_from_ord(int).ok_or(VariantConversionError)) + .and_then(|int| Self::try_from_ord(int).ok_or(VariantConversionError::BadType)) } } diff --git a/godot-core/src/builtin/variant/variant_traits.rs b/godot-core/src/builtin/variant/variant_traits.rs index 794acb919..542b90239 100644 --- a/godot-core/src/builtin/variant/variant_traits.rs +++ b/godot-core/src/builtin/variant/variant_traits.rs @@ -50,11 +50,14 @@ pub trait ToVariant { // ---------------------------------------------------------------------------------------------------------------------------------------------- #[derive(Eq, PartialEq, Debug)] -pub struct VariantConversionError; -/*pub enum VariantConversionError { +//pub struct VariantConversionError; +pub enum VariantConversionError { /// Variant type does not match expected type BadType, /// Variant value cannot be represented in target type BadValue, -}*/ + + /// Variant value is missing a value for the target type + MissingValue, +} diff --git a/godot-core/src/obj/gd.rs b/godot-core/src/obj/gd.rs index f827739ac..bd06ef2a8 100644 --- a/godot-core/src/obj/gd.rs +++ b/godot-core/src/obj/gd.rs @@ -720,7 +720,7 @@ impl FromVariant for Gd { // TODO(#234) remove this cast when Godot stops allowing illegal conversions // (See https://github.com/godot-rust/gdext/issues/158) .and_then(|obj| obj.owned_cast().ok()) - .ok_or(VariantConversionError) + .ok_or(VariantConversionError::BadType) } } diff --git a/godot-core/src/obj/instance_id.rs b/godot-core/src/obj/instance_id.rs index 7d7962811..5f277d639 100644 --- a/godot-core/src/obj/instance_id.rs +++ b/godot-core/src/obj/instance_id.rs @@ -86,7 +86,7 @@ unsafe impl GodotFfi for InstanceId { impl FromVariant for InstanceId { fn try_from_variant(variant: &Variant) -> Result { i64::try_from_variant(variant) - .and_then(|i| InstanceId::try_from_i64(i).ok_or(VariantConversionError)) + .and_then(|i| InstanceId::try_from_i64(i).ok_or(VariantConversionError::BadValue)) } } diff --git a/godot-macros/src/derive_from_variant.rs b/godot-macros/src/derive_from_variant.rs new file mode 100644 index 000000000..98a32edd8 --- /dev/null +++ b/godot-macros/src/derive_from_variant.rs @@ -0,0 +1,200 @@ +/* + * 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/. + */ + +use crate::util::{decl_get_info, DeclInfo}; +use crate::ParseResult; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use venial::{Declaration, StructFields}; + +pub fn transform(decl: Declaration) -> ParseResult { + let DeclInfo { + where_, + generic_params, + name, + name_string, + } = decl_get_info(&decl); + let mut body = quote! { + let root = variant.try_to::()?; + let root = root.get(#name_string).ok_or(godot::builtin::VariantConversionError::BadType)?; + }; + + match decl { + Declaration::Struct(s) => match s.fields { + venial::StructFields::Unit => { + body = quote! { + #body + return Ok(Self); + } + } + venial::StructFields::Tuple(fields) => { + if fields.fields.len() == 1 { + body = quote! { + #body + let root = root.try_to()?; + Ok(Self(root)) + }; + } else { + let ident_and_set = fields.fields.iter().enumerate().map(|(k, _)| { + let ident = format_ident!("__{}", k); + ( + ident.clone(), + quote! { + let #ident = root.pop_front().ok_or(godot::builtin::VariantConversionError::MissingValue)?; + }, + + ) + }); + let (idents, ident_set): (Vec<_>, Vec<_>) = ident_and_set.unzip(); + body = quote! { + #body + let mut root = root.try_to::>()?; + #( + #ident_set + + )* + Ok(Self( + #(#idents.try_to()?,)* + )) + }; + } + } + venial::StructFields::Named(fields) => { + let fields = fields.fields.iter().map(|(field, _)|{ + let ident = &field.name; + let string_ident = &field.name.to_string(); + ( + quote!{ + let #ident = root.get(#string_ident).ok_or(godot::builtin::VariantConversionError::MissingValue)?; + }, + + quote!{ + #ident :#ident.try_to()? + } + ) + + }); + let (set_idents, set_self): (Vec<_>, Vec<_>) = fields.unzip(); + body = quote! { + #body + let root = root.try_to::()?; + #( + #set_idents + )* + Ok(Self{ #(#set_self,)* }) + } + } + }, + Declaration::Enum(enum_) => { + if enum_.variants.is_empty() { + body = quote! { + panic!(); + } + } else { + let mut matches = quote! {}; + for (enum_v, _) in &enum_.variants.inner { + let variant_name = enum_v.name.clone(); + let variant_name_string = enum_v.name.to_string(); + let if_let_content = match &enum_v.contents { + StructFields::Unit => quote! { + let child = root.try_to::(); + if child == Ok(String::from(#variant_name_string)) { + return Ok(Self::#variant_name); + } + }, + StructFields::Tuple(fields) => { + if fields.fields.len() == 1 { + let (field, _) = fields.fields.first().unwrap(); + let field_type = &field.ty; + quote! { + let child = root.try_to::(); + if let Ok(child) = child { + if let Some(variant) = child.get(#variant_name_string) { + return Ok(Self::#variant_name(variant.try_to::<#field_type>()?)); + } + } + } + } else { + let fields = fields.fields.iter().enumerate() + .map(|(k, (field, _))|{ + let ident = format_ident!("__{k}"); + let field_type = &field.ty; + ( + quote!{#ident}, + + quote!{ + let #ident = variant + .pop_front() + .ok_or(godot::builtin::VariantConversionError::MissingValue)? + .try_to::<#field_type>()?; + }) + }); + let (idents, set_idents): (Vec<_>, Vec<_>) = fields.unzip(); + + quote! { + let child = root.try_to::(); + if let Ok(child) = child { + if let Some(variant) = child.get(#variant_name_string) { + let mut variant = variant.try_to::>()?; + #(#set_idents)* + return Ok(Self::#variant_name(#(#idents ,)*)); + } + } + } + } + } + StructFields::Named(fields) => { + let fields = fields.fields.iter().map(|(field, _)| { + let field_name = &field.name; + let field_name_string = &field.name.to_string(); + let field_type = &field.ty; + ( + quote!{#field_name}, + quote!{ + let #field_name = variant.get(#field_name_string).ok_or(godot::builtin::VariantConversionError::MissingValue)?.try_to::<#field_type>()?; + } + ) + }); + let (fields, set_fields): (Vec<_>, Vec<_>) = fields.unzip(); + quote! { + if let Ok(root) = root.try_to::() { + if let Some(variant) = root.get(#variant_name_string) { + let variant = variant.try_to::()?; + #( + #set_fields + )* + return Ok(Self::#variant_name{ #(#fields,)* }); + } + } + } + } + }; + matches = quote! { + #matches + #if_let_content + }; + } + body = quote! { + #body + #matches + Err(godot::builtin::VariantConversionError::MissingValue) + }; + } + } + _ => unreachable!(), + } + + let gen = generic_params.as_ref().map(|x| x.as_inline_args()); + Ok(quote! { + impl #generic_params godot::builtin::FromVariant for #name #gen #where_ { + fn try_from_variant( + variant: &godot::builtin::Variant + ) -> Result { + #body + } + } + }) +} diff --git a/godot-macros/src/derive_to_variant.rs b/godot-macros/src/derive_to_variant.rs new file mode 100644 index 000000000..88664106c --- /dev/null +++ b/godot-macros/src/derive_to_variant.rs @@ -0,0 +1,230 @@ +/* + * 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/. + */ + +use crate::util::{decl_get_info, DeclInfo}; +use crate::ParseResult; +use proc_macro2::TokenStream; +#[allow(unused_imports)] +use quote::ToTokens; +use quote::{format_ident, quote}; +use venial::{Declaration, StructFields}; + +pub fn transform(decl: Declaration) -> ParseResult { + let mut body = quote! { + let mut root = godot::builtin::Dictionary::new(); + }; + + let DeclInfo { + where_, + generic_params, + name, + name_string, + } = decl_get_info(&decl); + + match &decl { + Declaration::Struct(struct_) => match &struct_.fields { + StructFields::Unit => make_struct_unit(&mut body, name_string), + StructFields::Tuple(fields) => make_struct_tuple(&mut body, fields, name_string), + StructFields::Named(named_struct) => { + make_struct_named(&mut body, named_struct, name_string); + } + }, + Declaration::Enum(enum_) => { + let arms = enum_.variants.iter().map(|(enum_v, _)| { + let variant_name = enum_v.name.clone(); + let variant_name_string = enum_v.name.to_string(); + let fields = match &enum_v.contents { + StructFields::Unit => quote! {}, + StructFields::Tuple(s) => make_tuple_enum_field(s), + StructFields::Named(named) => make_named_enum_field(named), + }; + let arm_content = match &enum_v.contents { + StructFields::Unit => quote! { + #variant_name_string.to_variant() + }, + + StructFields::Tuple(fields) => make_enum_tuple_arm(fields, variant_name_string), + StructFields::Named(fields) => make_enum_named_arm(fields, variant_name_string), + }; + quote! { + Self::#variant_name #fields => { + #arm_content + } + } + }); + + body = quote! { + #body + let content = match core::clone::Clone::clone(self) { + #( + #arms + )* + }; + root.insert(#name_string, content); + }; + } + // This is unreachable cause this case has already returned + // with an error in decl_get_info call. + _ => unreachable!(), + }; + body = quote! { + #body + root.to_variant() + }; + + let gen = generic_params.as_ref().map(|x| x.as_inline_args()); + // we need to allow unreachable for Uninhabited enums because it uses match self {} + // it's okay since we can't ever have a value to call to_variant on it anyway. + let allow_unreachable = matches!(&decl,Declaration::Enum(e) if e.variants.is_empty()); + let allow_unreachable = if allow_unreachable { + quote! { + #[allow(unreachable_code)] + } + } else { + quote! {} + }; + + Ok(quote! { + impl #generic_params godot::builtin::ToVariant for #name #gen #where_ { + #allow_unreachable + fn to_variant(&self) -> godot::builtin::Variant { + #body + } + } + }) +} + +fn make_named_enum_field(named: &venial::NamedStructFields) -> TokenStream { + let fields = named.fields.iter().map(|(field, _)| &field.name); + quote!( + {#(#fields ,)*} + ) +} + +fn make_tuple_enum_field(s: &venial::TupleStructFields) -> TokenStream { + let fields = s + .fields + .iter() + .enumerate() + .map(|(k, _)| format_ident!("__{}", k)); + quote! { + (#(#fields ,)*) + } +} + +fn make_enum_named_arm( + fields: &venial::NamedStructFields, + variant_name_string: String, +) -> TokenStream { + let fields = fields + .fields + .iter() + .map(|(field, _)| (field.name.clone(), field.name.to_string())) + .map(|(ident, ident_string)| { + quote!( + root.insert(#ident_string,#ident.to_variant()); + ) + }); + quote! { + let mut root = godot::builtin::Dictionary::new(); + #( + #fields + )* + godot::builtin::dict!{ #variant_name_string : root}.to_variant() + } +} + +fn make_enum_tuple_arm( + fields: &venial::TupleStructFields, + variant_name_string: String, +) -> TokenStream { + if fields.fields.len() == 1 { + return quote! {godot::builtin::dict! { #variant_name_string : __0}.to_variant()}; + } + let fields = fields + .fields + .iter() + .enumerate() + .map(|(k, _)| format_ident!("__{}", k)) + .map(|ident| { + quote!( + root.push(#ident.to_variant()); + ) + }); + quote! { + let mut root = godot::builtin::Array::new(); + #( + #fields + + )* + godot::builtin::dict!{ #variant_name_string: root }.to_variant() + } +} + +fn make_struct_named( + body: &mut TokenStream, + fields: &venial::NamedStructFields, + string_ident: String, +) { + let fields = fields.fields.items().map(|nf| { + let field_name = nf.name.clone(); + let field_name_string = nf.name.to_string(); + + quote!( + fields.insert(#field_name_string, self.#field_name.to_variant()); + ) + }); + + *body = quote! { + #body + let mut fields = godot::builtin::Dictionary::new(); + #( + #fields + )* + root.insert(#string_ident, fields.to_variant()); + }; +} + +fn make_struct_tuple( + body: &mut TokenStream, + fields: &venial::TupleStructFields, + string_ident: String, +) { + if fields.fields.len() == 1 { + *body = quote! { + #body + root.insert(#string_ident, self.0.to_variant()); + }; + + return; + } + let fields = fields + .fields + .iter() + .enumerate() + .map(|(k, _)| proc_macro2::Literal::usize_unsuffixed(k)) + .map(|ident| { + quote!( + fields.push(self.#ident.to_variant()); + ) + }); + + *body = quote! { + #body + let mut fields = godot::builtin::Array::new(); + #( + #fields + )* + root.insert(#string_ident, fields.to_variant()); + }; +} + +fn make_struct_unit(body: &mut TokenStream, string_ident: String) { + *body = quote! { + #body + root.insert(#string_ident, godot::builtin::Variant::nil()); + } +} diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index b8527497a..3d3a5aebf 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -87,7 +87,9 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; use venial::Declaration; +mod derive_from_variant; mod derive_godot_class; +mod derive_to_variant; mod gdextension; mod godot_api; mod itest; @@ -260,6 +262,16 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream { translate(input, derive_godot_class::transform) } +#[proc_macro_derive(ToVariant, attributes(variant))] +pub fn derive_to_variant(input: TokenStream) -> TokenStream { + translate(input, derive_to_variant::transform) +} + +#[proc_macro_derive(FromVariant, attributes(variant))] +pub fn derive_from_variant(input: TokenStream) -> TokenStream { + translate(input, derive_from_variant::transform) +} + #[proc_macro_attribute] pub fn godot_api(_meta: TokenStream, input: TokenStream) -> TokenStream { translate(input, godot_api::transform) diff --git a/godot-macros/src/util.rs b/godot-macros/src/util.rs index be770c4b6..bec1e1c81 100644 --- a/godot-macros/src/util.rs +++ b/godot-macros/src/util.rs @@ -11,7 +11,7 @@ use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::spanned::Spanned; use quote::{format_ident, ToTokens}; use std::collections::HashMap; -use venial::{Attribute, Error, Function, Impl}; +use venial::{Attribute, Error, Function, GenericParamList, Impl, WhereClause}; pub fn ident(s: &str) -> Ident { format_ident!("{}", s) @@ -625,3 +625,37 @@ pub(crate) fn path_ends_with(path: &[TokenTree], expected: &str) -> bool { .map(|last| last.to_string() == expected) .unwrap_or(false) } + +pub(crate) struct DeclInfo { + pub where_: Option, + pub generic_params: Option, + pub name: proc_macro2::Ident, + pub name_string: String, +} + +pub(crate) fn decl_get_info(decl: &venial::Declaration) -> DeclInfo { + let (where_, generic_params, name, name_string) = + if let venial::Declaration::Struct(struct_) = decl { + ( + struct_.where_clause.clone(), + struct_.generic_params.clone(), + struct_.name.clone(), + struct_.name.to_string(), + ) + } else if let venial::Declaration::Enum(enum_) = decl { + ( + enum_.where_clause.clone(), + enum_.generic_params.clone(), + enum_.name.clone(), + enum_.name.to_string(), + ) + } else { + panic!("only enums and structs are supported at the moment.") + }; + DeclInfo { + where_, + generic_params, + name, + name_string, + } +} diff --git a/godot/src/lib.rs b/godot/src/lib.rs index 3488351b5..b59636d66 100644 --- a/godot/src/lib.rs +++ b/godot/src/lib.rs @@ -151,7 +151,7 @@ pub mod init { /// Export user-defined classes and methods to be called by the engine. pub mod bind { pub use super::export::{Export, TypeStringHint}; - pub use godot_macros::{godot_api, GodotClass}; + pub use godot_macros::{godot_api, FromVariant, GodotClass, ToVariant}; } /// Testing facilities (unstable). @@ -165,7 +165,7 @@ pub use godot_core::private; /// Often-imported symbols. pub mod prelude { - pub use super::bind::{godot_api, Export, GodotClass, TypeStringHint}; + pub use super::bind::{godot_api, Export, FromVariant, GodotClass, ToVariant, TypeStringHint}; pub use super::builtin::*; pub use super::builtin::{array, dict, varray}; // Re-export macros. pub use super::engine::{ diff --git a/itest/rust/src/derive_variant.rs b/itest/rust/src/derive_variant.rs new file mode 100644 index 000000000..182659631 --- /dev/null +++ b/itest/rust/src/derive_variant.rs @@ -0,0 +1,155 @@ +/* + * 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/. + */ + +use std::fmt::Debug; + +use crate::itest; +use godot::bind::FromVariant; +use godot::bind::ToVariant; +use godot::builtin::{dict, varray, FromVariant, ToVariant}; + +#[derive(FromVariant, ToVariant, PartialEq, Debug)] +struct StructUnit; + +#[derive(FromVariant, ToVariant, PartialEq, Debug)] +struct StructNewType(String); + +#[derive(FromVariant, ToVariant, PartialEq, Debug)] +struct StructTuple(String, i32); + +#[derive(FromVariant, ToVariant, PartialEq, Debug)] +struct StructNamed { + field1: String, + field2: i32, +} + +#[derive(FromVariant, ToVariant, PartialEq, Debug)] +struct StructGenWhere(T) +where + T: ToVariant + FromVariant; + +trait Bound {} + +#[derive(FromVariant, ToVariant, PartialEq, Debug)] +struct StructGenBound(T); + +#[derive(FromVariant, ToVariant, PartialEq, Debug, Clone)] +enum Uninhabited {} + +#[derive(FromVariant, ToVariant, PartialEq, Debug, Clone)] +enum Enum { + Unit, + OneTuple(i32), + Named { data: String }, + Tuple(String, i32), +} + +fn roundtrip(value: T, expected: U) +where + T: ToVariant + FromVariant + std::cmp::PartialEq + Debug, + U: ToVariant, +{ + let expected = expected.to_variant(); + + assert_eq!(value.to_variant(), expected, "testing converting to"); + assert_eq!( + value, + T::from_variant(&expected), + "testing converting back from" + ); +} + +#[itest] +fn unit_struct() { + roundtrip( + StructUnit, + dict! { "StructUnit": godot::builtin::Variant::nil() }, + ); +} + +#[itest] +fn new_type_struct() { + roundtrip( + StructNewType(String::from("five")), + dict! { "StructNewType" : "five" }, + ) +} + +#[itest] +fn tuple_struct() { + roundtrip( + StructTuple(String::from("one"), 2), + dict! { + "StructTuple": varray!["one", 2] + }, + ) +} + +#[itest] +fn named_struct() { + roundtrip( + StructNamed { + field1: String::from("four"), + field2: 5, + }, + dict! { + "StructNamed": dict! { "field1": "four", "field2": 5 } + }, + ) +} + +#[itest] +fn generics() { + roundtrip( + StructGenWhere(String::from("4")), + dict! { "StructGenWhere": "4" }, + ) +} + +impl Bound for String {} + +#[itest] +fn generics_bound() { + roundtrip( + StructGenBound(String::from("4")), + dict! { "StructGenBound": "4" }, + ) +} + +#[itest] +fn enum_unit() { + roundtrip(Enum::Unit, dict! { "Enum": "Unit" }) +} + +#[itest] +fn enum_one_tuple() { + roundtrip( + Enum::OneTuple(4), + dict! { + "Enum": dict! { "OneTuple" : 4 } + }, + ) +} + +#[itest] +fn enum_tuple() { + roundtrip( + Enum::Tuple(String::from("four"), 5), + dict! { "Enum": dict! { "Tuple" : varray!["four", 5] } }, + ) +} + +#[itest] +fn enum_named() { + roundtrip( + Enum::Named { + data: String::from("data"), + }, + dict! { + "Enum": dict!{ "Named": dict!{ "data": "data" } } + }, + ) +} diff --git a/itest/rust/src/lib.rs b/itest/rust/src/lib.rs index 652528406..b139c338e 100644 --- a/itest/rust/src/lib.rs +++ b/itest/rust/src/lib.rs @@ -16,6 +16,7 @@ mod builtin_test; mod callable_test; mod codegen_test; mod color_test; +mod derive_variant; mod dictionary_test; mod enum_test; mod export_test; diff --git a/itest/rust/src/object_test.rs b/itest/rust/src/object_test.rs index 848d169e3..9acecdda1 100644 --- a/itest/rust/src/object_test.rs +++ b/itest/rust/src/object_test.rs @@ -336,7 +336,7 @@ fn object_engine_convert_variant_nil() { assert_eq!( Gd::::try_from_variant(&nil), - Err(VariantConversionError), + Err(VariantConversionError::BadType), "try_from_variant(&nil)" ); diff --git a/itest/rust/src/variant_test.rs b/itest/rust/src/variant_test.rs index 9809e669a..83af5b26c 100644 --- a/itest/rust/src/variant_test.rs +++ b/itest/rust/src/variant_test.rs @@ -297,20 +297,23 @@ fn variant_null_object_is_nil() { fn variant_conversion_fails() { assert_eq!( "hello".to_variant().try_to::(), - Err(VariantConversionError) + Err(VariantConversionError::BadType) + ); + assert_eq!( + 28.to_variant().try_to::(), + Err(VariantConversionError::BadType) ); - assert_eq!(28.to_variant().try_to::(), Err(VariantConversionError)); assert_eq!( 10.to_variant().try_to::(), - Err(VariantConversionError) + Err(VariantConversionError::BadType) ); assert_eq!( false.to_variant().try_to::(), - Err(VariantConversionError) + Err(VariantConversionError::BadType) ); assert_eq!( VariantArray::default().to_variant().try_to::(), - Err(VariantConversionError) + Err(VariantConversionError::BadType) ); //assert_eq!( // Dictionary::default().to_variant().try_to::(), @@ -318,7 +321,7 @@ fn variant_conversion_fails() { //); assert_eq!( Variant::nil().to_variant().try_to::(), - Err(VariantConversionError) + Err(VariantConversionError::BadType) ); }