From dc6f3e2a67df0eec38df782ac8908a515dcc02f5 Mon Sep 17 00:00:00 2001 From: toasteater <48371905+toasteater@users.noreply.github.com> Date: Thu, 29 Aug 2019 16:14:47 +0000 Subject: [PATCH 1/3] Implement derive macro for ToVariant and FromVariant The macro does the following mapping between Rust structures and Godot types: - `Newtype(inner)` are mapped to `inner` - `Tuple(a, b, c)` are mapped to `[a, b, c]` - `Struct { a, b, c }` are mapped to `{ "a": a, "b": b, "c": c }` - `Unit` are mapped to `{}` - `Enum::Variant(a, b, c)` are mapped to `{ "Variant": [a, b, c] }` --- gdnative-core/src/variant.rs | 19 + gdnative-derive/Cargo.toml | 1 + gdnative-derive/src/derive_conv_variant.rs | 480 +++++++++++++++++++++ gdnative-derive/src/lib.rs | 13 + test/src/lib.rs | 56 +++ 5 files changed, 569 insertions(+) create mode 100644 gdnative-derive/src/derive_conv_variant.rs diff --git a/gdnative-core/src/variant.rs b/gdnative-core/src/variant.rs index d288e593f..263689104 100644 --- a/gdnative-core/src/variant.rs +++ b/gdnative-core/src/variant.rs @@ -688,6 +688,25 @@ godot_test!( assert!(v_m1.try_to_f64().is_none()); assert!(v_m1.try_to_array().is_none()); } + + test_variant_bool { + let v_true = Variant::from_bool(true); + assert_eq!(v_true.get_type(), VariantType::Bool); + + assert!(!v_true.is_nil()); + assert_eq!(v_true.try_to_bool(), Some(true)); + assert!(v_true.try_to_f64().is_none()); + assert!(v_true.try_to_array().is_none()); + + let v_false = Variant::from_bool(false); + assert_eq!(v_false.get_type(), VariantType::Bool); + + assert!(!v_false.is_nil()); + assert_eq!(v_false.try_to_bool(), Some(false)); + assert!(v_false.try_to_f64().is_none()); + assert!(v_false.try_to_array().is_none()); + + } ); /// Types that can be converted to a `Variant`. diff --git a/gdnative-derive/Cargo.toml b/gdnative-derive/Cargo.toml index ddf833ec1..df4d06940 100644 --- a/gdnative-derive/Cargo.toml +++ b/gdnative-derive/Cargo.toml @@ -15,3 +15,4 @@ proc-macro = true [dependencies] syn = { version = "0.15.29", features = ["full", "extra-traits"] } quote = "0.6.11" +proc-macro2 = "^0.4.4" \ No newline at end of file diff --git a/gdnative-derive/src/derive_conv_variant.rs b/gdnative-derive/src/derive_conv_variant.rs new file mode 100644 index 000000000..4d655a963 --- /dev/null +++ b/gdnative-derive/src/derive_conv_variant.rs @@ -0,0 +1,480 @@ +use std::collections::{HashSet}; + +use proc_macro::TokenStream; +use syn::{Data, DeriveInput, Fields, Ident, Type, TypePath, Generics}; +use syn::visit::{self, Visit}; +use proc_macro2::{TokenStream as TokenStream2, Span, Literal}; + +pub(crate) struct DeriveData { + pub(crate) ident: Ident, + pub(crate) repr: Repr, + pub(crate) generics: Generics, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub(crate) enum Repr { + Struct(VariantRepr), + Enum(Vec<(Ident, VariantRepr)>), +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub(crate) enum VariantRepr { + Unit, + Struct(Vec<(Ident, Type)>), + Tuple(Vec<(Ident, Type)>), +} + +impl VariantRepr { + fn repr_for(fields: &Fields) -> Self { + match fields { + Fields::Named(fields) => { + VariantRepr::Struct(fields.named + .iter() + .map(|f| (f.ident.clone().expect("fields should be named"), f.ty.clone())) + .collect()) + }, + Fields::Unnamed(fields) => { + VariantRepr::Tuple(fields.unnamed + .iter() + .enumerate() + .map(|(n, f)| (Ident::new(&format!("__field_{}", n), Span::call_site()), f.ty.clone())) + .collect()) + }, + Fields::Unit => { + VariantRepr::Unit + }, + } + } + + fn destructure_pattern(&self) -> TokenStream2 { + match self { + VariantRepr::Unit => quote! { }, + VariantRepr::Tuple(fields) => { + let names = fields + .iter() + .map(|(ident, _)| ident); + quote! { + ( #( #names ),* ) + } + }, + VariantRepr::Struct(fields) => { + let names = fields + .iter() + .map(|(ident, _)| ident); + quote! { + { #( #names ),* } + } + }, + } + } + + fn to_variant(&self) -> TokenStream2 { + match self { + VariantRepr::Unit => { + quote! { ::gdnative::Dictionary::new().to_variant() } + }, + VariantRepr::Tuple(fields) => { + let names: Vec<&Ident> = fields + .iter() + .map(|(ident, _)| ident) + .collect(); + + if names.len() == 1 { + // as newtype + let inner = names.get(0).unwrap(); + quote! { + #inner.to_variant() + } + } + else { + quote! { + { + let mut __array = ::gdnative::VariantArray::new(); + #( + __array.push(&(#names).to_variant()); + )* + __array.to_variant() + } + } + } + }, + VariantRepr::Struct(fields) => { + let names: Vec<&Ident> = fields + .iter() + .map(|(ident, _)| ident) + .collect(); + + let name_strings: Vec = names + .iter() + .map(|ident| format!("{}", ident)) + .collect(); + + let name_string_literals = name_strings + .iter() + .map(|string| Literal::string(&string)); + + quote! { + { + let mut __dict = ::gdnative::Dictionary::new(); + #( + { + let __key = ::gdnative::GodotString::from(#name_string_literals).to_variant(); + __dict.set(&__key, &(#names).to_variant()); + } + )* + __dict.to_variant() + } + } + }, + } + } + + fn from_variant(&self, variant: &Ident, ctor: &TokenStream2) -> TokenStream2 { + match self { + VariantRepr::Unit => { + quote! { + if #variant.is_nil() { + None + } + else { + Some(#ctor) + } + } + }, + VariantRepr::Tuple(fields) => { + let types: Vec<&Type> = fields + .iter() + .map(|(_, ty)| ty) + .collect(); + + if types.len() == 1 { + // as newtype + let inner = types.get(0).unwrap(); + quote! { + { + let __inner = #inner::from_variant(#variant)?; + Some(#ctor(__inner)) + } + } + } + else { + let idents: Vec<&Ident> = fields + .iter() + .map(|(ident, _)| ident) + .collect(); + + let decl_idents = idents.iter(); + let ctor_idents = idents.iter(); + + let self_len = Literal::i32_suffixed(types.len() as i32); + let indices = (0..fields.len() as i32) + .map(|n| Literal::i32_suffixed(n)); + + quote! { + { + let __array = #variant.try_to_array()?; + if __array.len() != #self_len { + None + } + else { + #( + let #decl_idents = FromVariant::from_variant(__array.get_ref(#indices))?; + )* + Some(#ctor( #(#ctor_idents),* )) + } + } + } + } + }, + VariantRepr::Struct(fields) => { + let names: Vec<&Ident> = fields + .iter() + .map(|(ident, _)| ident) + .collect(); + + let name_strings: Vec = names + .iter() + .map(|ident| format!("{}", ident)) + .collect(); + + let name_string_literals = name_strings + .iter() + .map(|string| Literal::string(&string)); + + let decl_idents = names.iter(); + let ctor_idents = names.iter(); + + quote! { + { + let __dict = #variant.try_to_dictionary()?; + #( + let __key = ::gdnative::GodotString::from(#name_string_literals).to_variant(); + let #decl_idents = FromVariant::from_variant(__dict.get_ref(&__key))?; + )* + Some(#ctor { #( #ctor_idents ),* }) + } + } + }, + } + } +} + +pub(crate) fn extend_bounds(generics: Generics, repr: &Repr, bound: &syn::Path) -> Generics { + + // recursively visit all the field types to find what types should be bounded + struct Visitor<'ast> { + all_type_params: HashSet, + used: HashSet<&'ast TypePath>, + } + + impl<'ast> Visit<'ast> for Visitor<'ast> { + fn visit_type_path(&mut self, type_path: &'ast TypePath) { + let path = &type_path.path; + if let Some(seg) = path.segments.last() { + if seg.value().ident == "PhantomData" { + // things inside PhantomDatas doesn't need to be bounded, so stopping + // recursive visit here + return; + } + } + if let Some(seg) = path.segments.first() { + if self.all_type_params.contains(&seg.value().ident) { + // if the first segment of the type path is a known type variable, then this + // is likely an associated type + // TODO: what about cases like as Trait>::A? Maybe too fringe to be + // useful? serde_derive can't seem to parse these either. Probably good enough. + self.used.insert(type_path); + } + } + visit::visit_path(self, &type_path.path); + } + } + + let all_type_params = generics + .type_params() + .map(|param| param.ident.clone()) + .collect(); + + let mut visitor = Visitor { + all_type_params: all_type_params, + used: HashSet::new(), + }; + + // iterate through parsed variant representations and visit the types of each field + fn visit_var_repr<'ast>(visitor: &mut Visitor<'ast>, repr: &'ast VariantRepr) { + match repr { + VariantRepr::Unit => { }, + VariantRepr::Tuple(tys) => { + for (_, ty) in tys.iter() { + visitor.visit_type(ty); + } + }, + VariantRepr::Struct(fields) => { + for (_, ty) in fields.iter() { + visitor.visit_type(ty); + } + } + } + } + + match repr { + Repr::Enum(ref variants) => { + for (_, var_repr) in variants.iter() { + visit_var_repr(&mut visitor, var_repr); + } + }, + Repr::Struct(var_repr) => { + visit_var_repr(&mut visitor, var_repr); + }, + } + + // where thing: is_trait + fn where_predicate(thing: Type, is_trait: syn::Path) -> syn::WherePredicate { + syn::WherePredicate::Type(syn::PredicateType { + lifetimes: None, + bounded_ty: thing, + colon_token: ::default(), + bounds: vec![syn::TypeParamBound::Trait(syn::TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: is_trait, + })] + .into_iter() + .collect(), + }) + } + + // place bounds on all used type parameters and associated types + let new_predicates = visitor.used + .into_iter() + .cloned() + .map(|bounded_ty| { + where_predicate(syn::Type::Path(bounded_ty), bound.clone()) + }); + + let mut generics = generics.clone(); + generics + .make_where_clause() + .predicates + .extend(new_predicates); + + generics +} + +pub(crate) fn parse_derive_input(input: TokenStream, bound: &syn::Path) -> DeriveData { + let input = match syn::parse_macro_input::parse::(input) { + Ok(val) => val, + Err(err) => { + panic!("{}", err); + } + }; + + let repr = match input.data { + Data::Struct(struct_data) => Repr::Struct(VariantRepr::repr_for(&struct_data.fields)), + Data::Enum(enum_data) => { + Repr::Enum(enum_data.variants + .iter() + .map(|variant| (variant.ident.clone(), VariantRepr::repr_for(&variant.fields))) + .collect()) + }, + Data::Union(_) => panic!("Variant conversion derive macro does not work on unions."), + }; + + let generics = extend_bounds(input.generics, &repr, bound); + + DeriveData { ident: input.ident, repr, generics } +} + +pub(crate) fn derive_to_variant(input: TokenStream) -> TokenStream { + let bound: syn::Path = syn::parse2(quote! { ::gdnative::ToVariant }).unwrap(); + let DeriveData { ident, repr, generics } = parse_derive_input(input, &bound); + + let return_expr = match repr { + Repr::Struct(var_repr) => { + let destructure_pattern = var_repr.destructure_pattern(); + let to_variant = var_repr.to_variant(); + quote! { + { + let #ident #destructure_pattern = &self; + #to_variant + } + } + }, + Repr::Enum(variants) => { + let match_arms = variants + .iter() + .map(|(var_ident, var_repr)| { + let destructure_pattern = var_repr.destructure_pattern(); + let to_variant = var_repr.to_variant(); + let var_ident_string = format!("{}", var_ident); + let var_ident_string_literal = Literal::string(&var_ident_string); + quote! { + #ident::#var_ident #destructure_pattern => { + let mut __dict = ::gdnative::Dictionary::new(); + let __key = ::gdnative::GodotString::from(#var_ident_string_literal).to_variant(); + let __value = #to_variant; + __dict.set(&__key, &__value); + __dict.to_variant() + } + } + }); + + quote! { + match &self { + #( #match_arms ),* + } + } + }, + }; + + let where_clause = &generics.where_clause; + + let result = quote! { + impl #generics ::gdnative::ToVariant for #ident #generics #where_clause { + fn to_variant(&self) -> ::gdnative::Variant { + use ::gdnative::ToVariant; + use ::gdnative::FromVariant; + + #return_expr + } + } + }; + + result.into() +} + +pub(crate) fn derive_from_variant(input: TokenStream) -> TokenStream { + let bound: syn::Path = syn::parse2(quote! { ::gdnative::FromVariant }).unwrap(); + let DeriveData { ident, repr, generics } = parse_derive_input(input, &bound); + + let input_ident = Ident::new("__variant", Span::call_site()); + + let return_expr = match repr { + Repr::Struct(var_repr) => { + let from_variant = var_repr.from_variant(&input_ident, "e! { #ident }); + quote! { + { + #from_variant + } + } + }, + Repr::Enum(variants) => { + let var_input_ident = Ident::new("__enum_variant", Span::call_site()); + + let var_ident_strings: Vec = variants + .iter() + .map(|(var_ident, _)| format!("{}", var_ident)) + .collect(); + + let var_ident_string_literals = var_ident_strings + .iter() + .map(|string| Literal::string(&string)); + + let var_from_variants = variants + .iter() + .map(|(var_ident, var_repr)| { + var_repr.from_variant(&var_input_ident, "e! { #ident::#var_ident }) + }); + + let var_input_ident_iter = std::iter::repeat(&var_input_ident); + + quote! { + { + let __dict = #input_ident.try_to_dictionary()?; + let __keys = __dict.keys(); + if __keys.len() != 1 { + None + } + else { + let __key = __keys.get_ref(0).try_to_string()?; + match __key.as_str() { + #( + #var_ident_string_literals => { + let #var_input_ident_iter = __dict.get_ref(__keys.get_ref(0)); + #var_from_variants + }, + )* + _ => None, + } + } + } + } + }, + }; + + let where_clause = &generics.where_clause; + + let result = quote! { + impl #generics ::gdnative::FromVariant for #ident #generics #where_clause { + fn from_variant(#input_ident: &::gdnative::Variant) -> ::std::option::Option { + use ::gdnative::ToVariant; + use ::gdnative::FromVariant; + + #return_expr + } + } + }; + + result.into() +} \ No newline at end of file diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index c121caa8f..e1fcb4cbc 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -1,4 +1,6 @@ extern crate proc_macro; +extern crate proc_macro2; +#[macro_use] extern crate syn; #[macro_use] extern crate quote; @@ -7,6 +9,7 @@ use proc_macro::TokenStream; mod derive_macro; mod method_macro; +mod derive_conv_variant; #[proc_macro_attribute] pub fn methods(meta: TokenStream, input: TokenStream) -> TokenStream { @@ -86,3 +89,13 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream { // create output token stream trait_impl.into() } + +#[proc_macro_derive(ToVariant)] +pub fn derive_to_variant(input: TokenStream) -> TokenStream { + derive_conv_variant::derive_to_variant(input) +} + +#[proc_macro_derive(FromVariant)] +pub fn derive_from_variant(input: TokenStream) -> TokenStream { + derive_conv_variant::derive_from_variant(input) +} diff --git a/test/src/lib.rs b/test/src/lib.rs index fa4487f41..3833f5ed5 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -18,6 +18,7 @@ pub extern "C" fn run_tests( status &= gdnative::test_variant_nil(); status &= gdnative::test_variant_i64(); + status &= gdnative::test_variant_bool(); status &= gdnative::test_vector2_variants(); @@ -30,6 +31,7 @@ pub extern "C" fn run_tests( status &= test_constructor(); status &= test_underscore_method_binding(); + status &= test_derive_to_variant(); status &= test_rust_class_construction(); status &= test_owner_free_ub(); @@ -212,6 +214,60 @@ fn test_owner_free_ub() -> bool { ok } +fn test_derive_to_variant() -> bool { + println!(" -- test_derive_to_variant"); + + + #[derive(Clone, Eq, PartialEq, Debug, ToVariant, FromVariant)] + struct ToVar + where + T: Associated, + { + foo: T::A, + bar: T, + baz: ToVarEnum, + } + + #[derive(Clone, Eq, PartialEq, Debug, ToVariant, FromVariant)] + enum ToVarEnum { + Foo(T), + Bar, + Baz { baz: u8 }, + } + + trait Associated { + type A; + type B; + } + + impl Associated for f64 { + type A = i64; + type B = bool; + } + + let ok = std::panic::catch_unwind(|| { + let data = ToVar:: { + foo: 42, + bar: 54.0, + baz: ToVarEnum::Foo(true), + }; + let variant = data.to_variant(); + let dictionary = variant.try_to_dictionary().expect("should be dictionary"); + assert_eq!(Some(42), dictionary.get(&"foo".into()).try_to_i64()); + assert_eq!(Some(54.0), dictionary.get(&"bar".into()).try_to_f64()); + let enum_dict = dictionary.get(&"baz".into()).try_to_dictionary().expect("should be dictionary"); + assert_eq!(Some(true), enum_dict.get(&"Foo".into()).try_to_bool()); + assert_eq!(Some(&data.baz), ToVarEnum::from_variant(&enum_dict.to_variant()).as_ref()); + assert_eq!(Some(&data), ToVar::from_variant(&variant).as_ref()); + }).is_ok(); + + if !ok { + godot_error!(" !! Test test_derive_to_variant failed"); + } + + ok +} + fn init(handle: init::InitHandle) { handle.add_class::(); handle.add_class::(); From 5aea8295d82833701d84c24734abe778182c88cd Mon Sep 17 00:00:00 2001 From: toasteater <48371905+toasteater@users.noreply.github.com> Date: Fri, 30 Aug 2019 12:32:22 +0000 Subject: [PATCH 2/3] Implement To/FromVariant for common Rust types Allows taking and returning `Option`, `Result`, and `Vec` directly. Added `FromVariant` wrapper `MaybeNot` with looser semantic than `Option`. Fixes #186. --- gdnative-core/src/variant.rs | 256 ++++++++++++++++++++++++++++++++++- test/src/lib.rs | 4 + 2 files changed, 259 insertions(+), 1 deletion(-) diff --git a/gdnative-core/src/variant.rs b/gdnative-core/src/variant.rs index 263689104..521b4a951 100644 --- a/gdnative-core/src/variant.rs +++ b/gdnative-core/src/variant.rs @@ -710,11 +710,48 @@ godot_test!( ); /// Types that can be converted to a `Variant`. +/// +/// ## Wrappers and collections +/// +/// Implementations are provided for a few common Rust wrappers and collections: +/// +/// - `Option` is unwrapped to inner value, or `Nil` if `None` +/// - `Result` is represented as an externally tagged `Dictionary` (see below). +/// - `PhantomData` is represented as `Nil`. +/// - `&[T]` and `Vec` are represented as `VariantArray`s. `FromVariant` is only implemented +/// for `Vec`. +/// +/// ## Deriving `ToVariant` +/// +/// The derive macro does the following mapping between Rust structures and Godot types: +/// +/// - `Newtype(inner)` is unwrapped to `inner` +/// - `Tuple(a, b, c)` is represented as a `VariantArray` (`[a, b, c]`) +/// - `Struct { a, b, c }` is represented as a `Dictionary` (`{ "a": a, "b": b, "c": c }`) +/// - `Unit` is represented as an empty `Dictionary` (`{}`) +/// - `Enum::Variant(a, b, c)` is represented as an externally tagged `Dictionary` +/// (`{ "Variant": [a, b, c] }`) pub trait ToVariant { fn to_variant(&self) -> Variant; } /// Types that can be converted from a `Variant`. +/// +/// ## `Option` and `MaybeNot` +/// +/// `Option` requires the Variant to be `T` or `Nil`, in that order. For looser semantics, +/// use `MaybeNot`, which will catch all variant values that are not `T` as well. +/// +/// ## `Vec` +/// +/// The `FromVariant` implementation for `Vec` only allow homogeneous arrays. If you want to +/// manually handle potentially heterogeneous values e.g. for error reporting, use `VariantArray` +/// directly or compose with an appropriate wrapper: `Vec>` or `Vec>`. +/// +/// ## Deriving `FromVariant` +/// +/// The derive macro provides implementation consistent with derived `ToVariant`. See `ToVariant` +/// for detailed documentation. pub trait FromVariant: Sized { fn from_variant(variant: &Variant) -> Option; } @@ -903,4 +940,221 @@ impl FromVariant for Variant { fn from_variant(variant: &Variant) -> Option { Some(variant.clone()) } -} \ No newline at end of file +} + +impl ToVariant for std::marker::PhantomData { + fn to_variant(&self) -> Variant { + Variant::new() + } +} + +impl FromVariant for std::marker::PhantomData { + fn from_variant(variant: &Variant) -> Option { + if variant.is_nil() { + Some(std::marker::PhantomData) + } + else { + None + } + } +} + +impl ToVariant for Option { + fn to_variant(&self) -> Variant { + match &self { + Some(thing) => thing.to_variant(), + None => Variant::new(), + } + } +} + +impl FromVariant for Option { + fn from_variant(variant: &Variant) -> Option { + T::from_variant(variant).map(Some).or_else(|| { + if variant.is_nil() { + Some(None) + } + else { + None + } + }) + } +} + +/// Wrapper type around a `FromVariant` result that may not be a success +#[derive(Clone, Debug)] +pub struct MaybeNot(Result); + +impl FromVariant for MaybeNot { + fn from_variant(variant: &Variant) -> Option { + Some(MaybeNot(T::from_variant(variant).ok_or_else(|| variant.clone()))) + } +} + +impl MaybeNot { + pub fn into_result(self) -> Result { + self.0 + } + + pub fn as_ref(&self) -> Result<&T, &Variant> { + self.0.as_ref() + } + + pub fn as_mut(&mut self) -> Result<&mut T, &mut Variant> { + self.0.as_mut() + } + + pub fn cloned(&self) -> Result + where + T: Clone, + { + self.0.clone() + } + + pub fn ok(self) -> Option { + self.0.ok() + } +} + +impl ToVariant for Result { + fn to_variant(&self) -> Variant { + let mut dict = Dictionary::new(); + match &self { + Ok(val) => dict.set(&"Ok".into(), &val.to_variant()), + Err(err) => dict.set(&"Err".into(), &err.to_variant()), + } + dict.to_variant() + } +} + +impl FromVariant for Result { + fn from_variant(variant: &Variant) -> Option { + let dict = variant.try_to_dictionary()?; + if dict.len() != 1 { + return None; + } + let keys = dict.keys(); + let key_variant = keys.get_ref(0); + let key = key_variant.try_to_string()?; + match key.as_str() { + "Ok" => { + let val = T::from_variant(dict.get_ref(key_variant))?; + Some(Ok(val)) + }, + "Err" => { + let err = E::from_variant(dict.get_ref(key_variant))?; + Some(Err(err)) + }, + _ => None, + } + } +} + +impl ToVariant for &[T] { + fn to_variant(&self) -> Variant { + let mut array = VariantArray::new(); + for val in self.iter() { + // there is no real way to avoid CoW allocations right now, as ptrw isn't exposed + array.push(&val.to_variant()); + } + array.to_variant() + } +} + +impl ToVariant for Vec { + fn to_variant(&self) -> Variant { + self.as_slice().to_variant() + } +} + +impl FromVariant for Vec { + fn from_variant(variant: &Variant) -> Option { + use std::convert::TryInto; + + let arr = variant.try_to_array()?; + let len: usize = arr.len().try_into().ok()?; + let mut vec = Vec::with_capacity(len); + for idx in 0..len as i32 { + let item = T::from_variant(arr.get_ref(idx))?; + vec.push(item); + } + Some(vec) + } +} + +godot_test!( + test_variant_option { + use std::marker::PhantomData; + + let variant = Some(42 as i64).to_variant(); + assert_eq!(Some(42), variant.try_to_i64()); + + let variant = Option::::None.to_variant(); + assert!(variant.is_nil()); + + let variant = Variant::new(); + assert_eq!(Some(None), Option::::from_variant(&variant)); + assert_eq!(Some(None), Option::::from_variant(&variant)); + assert_eq!(Some(None), Option::::from_variant(&variant)); + + let variant = Variant::from_i64(42); + assert_eq!(Some(Some(42)), Option::::from_variant(&variant)); + assert_eq!(None, Option::::from_variant(&variant)); + assert_eq!(None, Option::::from_variant(&variant)); + + let variant = Variant::new(); + assert_eq!(Some(Some(())), Option::<()>::from_variant(&variant)); + assert_eq!(Some(Some(PhantomData)), Option::>::from_variant(&variant)); + + let variant = Variant::from_i64(42); + assert_eq!(None, Option::>::from_variant(&variant)); + } + + test_variant_result { + let variant = Result::::Ok(42 as i64).to_variant(); + let dict = variant.try_to_dictionary().expect("should be dic"); + assert_eq!(Some(42), dict.get_ref(&"Ok".into()).try_to_i64()); + + let variant = Result::<(), i64>::Err(54 as i64).to_variant(); + let dict = variant.try_to_dictionary().expect("should be dic"); + assert_eq!(Some(54), dict.get_ref(&"Err".into()).try_to_i64()); + + let variant = Variant::from_bool(true); + assert_eq!(None, Result::<(), i64>::from_variant(&variant)); + + let mut dict = Dictionary::new(); + dict.set(&"Ok".into(), &Variant::from_i64(42)); + assert_eq!(Some(Ok(42)), Result::::from_variant(&dict.to_variant())); + + let mut dict = Dictionary::new(); + dict.set(&"Err".into(), &Variant::from_i64(54)); + assert_eq!(Some(Err(54)), Result::::from_variant(&dict.to_variant())); + } + + test_to_variant_iter { + let slice: &[i64] = &[0, 1, 2, 3, 4]; + let variant = slice.to_variant(); + let array = variant.try_to_array().expect("should be array"); + assert_eq!(5, array.len()); + for i in 0..5 { + assert_eq!(Some(i), array.get_ref(i as i32).try_to_i64()); + } + + let vec = Vec::::from_variant(&variant).expect("should succeed"); + assert_eq!(slice, vec.as_slice()); + + let mut het_array = VariantArray::new(); + het_array.push(&Variant::from_i64(42)); + het_array.push(&Variant::new()); + assert_eq!(None, Vec::::from_variant(&het_array.to_variant())); + assert_eq!(Some(vec![Some(42), None]), Vec::>::from_variant(&het_array.to_variant())); + + het_array.push(&f64::to_variant(&54.0)); + assert_eq!(None, Vec::>::from_variant(&het_array.to_variant())); + let vec_maybe = Vec::>::from_variant(&het_array.to_variant()).expect("should succeed"); + assert_eq!(3, vec_maybe.len()); + assert_eq!(Some(&42), vec_maybe[0].as_ref().ok()); + assert_eq!(Some(&Variant::new()), vec_maybe[1].as_ref().err()); + assert_eq!(Some(&f64::to_variant(&54.0)), vec_maybe[2].as_ref().err()); + } +); \ No newline at end of file diff --git a/test/src/lib.rs b/test/src/lib.rs index 3833f5ed5..bd142e179 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -24,6 +24,10 @@ pub extern "C" fn run_tests( status &= gdnative::test_vector3_variants(); + status &= gdnative::test_variant_option(); + status &= gdnative::test_variant_result(); + status &= gdnative::test_to_variant_iter(); + status &= gdnative::test_byte_array_access(); status &= gdnative::test_int32_array_access(); status &= gdnative::test_float32_array_access(); From b322c45bf74d6a988a166c596f1dffba8f9ad014 Mon Sep 17 00:00:00 2001 From: toasteater <48371905+toasteater@users.noreply.github.com> Date: Sat, 31 Aug 2019 05:16:41 +0000 Subject: [PATCH 3/3] Improve type mismatch error message With Variant conversion traits becoming derivable, type errors are no longer limited to `VariantType` mismatches. The error message is expanded to be more informative for non-primitive types. --- gdnative-core/src/macros.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gdnative-core/src/macros.rs b/gdnative-core/src/macros.rs index 7840c3edf..0b05de392 100644 --- a/gdnative-core/src/macros.rs +++ b/gdnative-core/src/macros.rs @@ -502,9 +502,11 @@ macro_rules! godot_wrap_method_inner { let $pname = if let Some(val) = <$pty as $crate::FromVariant>::from_variant(_variant) { val } else { - godot_error!("Incorrect argument type {:?} for argument {}", - _variant.get_type(), - offset); + godot_error!("Incorrect argument type for argument #{} ({}: {}). Got VariantType::{:?} (non-primitive types may impose structural checks)", + offset + 1, + stringify!($pname), + stringify!($pty), + _variant.get_type()); return $crate::Variant::new().to_sys(); };