Skip to content

Commit cf730c0

Browse files
authored
Merge pull request #189 from toasteater/feature/derive-tovariant-fromvariant
Implement derive macro for ToVariant and FromVariant
2 parents 060c8c1 + b322c45 commit cf730c0

File tree

6 files changed

+833
-4
lines changed

6 files changed

+833
-4
lines changed

gdnative-core/src/macros.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -502,9 +502,11 @@ macro_rules! godot_wrap_method_inner {
502502
let $pname = if let Some(val) = <$pty as $crate::FromVariant>::from_variant(_variant) {
503503
val
504504
} else {
505-
godot_error!("Incorrect argument type {:?} for argument {}",
506-
_variant.get_type(),
507-
offset);
505+
godot_error!("Incorrect argument type for argument #{} ({}: {}). Got VariantType::{:?} (non-primitive types may impose structural checks)",
506+
offset + 1,
507+
stringify!($pname),
508+
stringify!($pty),
509+
_variant.get_type());
508510
return $crate::Variant::new().to_sys();
509511
};
510512

gdnative-core/src/variant.rs

+274-1
Original file line numberDiff line numberDiff line change
@@ -688,14 +688,70 @@ godot_test!(
688688
assert!(v_m1.try_to_f64().is_none());
689689
assert!(v_m1.try_to_array().is_none());
690690
}
691+
692+
test_variant_bool {
693+
let v_true = Variant::from_bool(true);
694+
assert_eq!(v_true.get_type(), VariantType::Bool);
695+
696+
assert!(!v_true.is_nil());
697+
assert_eq!(v_true.try_to_bool(), Some(true));
698+
assert!(v_true.try_to_f64().is_none());
699+
assert!(v_true.try_to_array().is_none());
700+
701+
let v_false = Variant::from_bool(false);
702+
assert_eq!(v_false.get_type(), VariantType::Bool);
703+
704+
assert!(!v_false.is_nil());
705+
assert_eq!(v_false.try_to_bool(), Some(false));
706+
assert!(v_false.try_to_f64().is_none());
707+
assert!(v_false.try_to_array().is_none());
708+
709+
}
691710
);
692711

693712
/// Types that can be converted to a `Variant`.
713+
///
714+
/// ## Wrappers and collections
715+
///
716+
/// Implementations are provided for a few common Rust wrappers and collections:
717+
///
718+
/// - `Option<T>` is unwrapped to inner value, or `Nil` if `None`
719+
/// - `Result<T, E>` is represented as an externally tagged `Dictionary` (see below).
720+
/// - `PhantomData<T>` is represented as `Nil`.
721+
/// - `&[T]` and `Vec<T>` are represented as `VariantArray`s. `FromVariant` is only implemented
722+
/// for `Vec<T>`.
723+
///
724+
/// ## Deriving `ToVariant`
725+
///
726+
/// The derive macro does the following mapping between Rust structures and Godot types:
727+
///
728+
/// - `Newtype(inner)` is unwrapped to `inner`
729+
/// - `Tuple(a, b, c)` is represented as a `VariantArray` (`[a, b, c]`)
730+
/// - `Struct { a, b, c }` is represented as a `Dictionary` (`{ "a": a, "b": b, "c": c }`)
731+
/// - `Unit` is represented as an empty `Dictionary` (`{}`)
732+
/// - `Enum::Variant(a, b, c)` is represented as an externally tagged `Dictionary`
733+
/// (`{ "Variant": [a, b, c] }`)
694734
pub trait ToVariant {
695735
fn to_variant(&self) -> Variant;
696736
}
697737

698738
/// Types that can be converted from a `Variant`.
739+
///
740+
/// ## `Option<T>` and `MaybeNot<T>`
741+
///
742+
/// `Option<T>` requires the Variant to be `T` or `Nil`, in that order. For looser semantics,
743+
/// use `MaybeNot<T>`, which will catch all variant values that are not `T` as well.
744+
///
745+
/// ## `Vec<T>`
746+
///
747+
/// The `FromVariant` implementation for `Vec<T>` only allow homogeneous arrays. If you want to
748+
/// manually handle potentially heterogeneous values e.g. for error reporting, use `VariantArray`
749+
/// directly or compose with an appropriate wrapper: `Vec<Option<T>>` or `Vec<MaybeNot<T>>`.
750+
///
751+
/// ## Deriving `FromVariant`
752+
///
753+
/// The derive macro provides implementation consistent with derived `ToVariant`. See `ToVariant`
754+
/// for detailed documentation.
699755
pub trait FromVariant: Sized {
700756
fn from_variant(variant: &Variant) -> Option<Self>;
701757
}
@@ -884,4 +940,221 @@ impl FromVariant for Variant {
884940
fn from_variant(variant: &Variant) -> Option<Self> {
885941
Some(variant.clone())
886942
}
887-
}
943+
}
944+
945+
impl<T> ToVariant for std::marker::PhantomData<T> {
946+
fn to_variant(&self) -> Variant {
947+
Variant::new()
948+
}
949+
}
950+
951+
impl<T> FromVariant for std::marker::PhantomData<T> {
952+
fn from_variant(variant: &Variant) -> Option<Self> {
953+
if variant.is_nil() {
954+
Some(std::marker::PhantomData)
955+
}
956+
else {
957+
None
958+
}
959+
}
960+
}
961+
962+
impl<T: ToVariant> ToVariant for Option<T> {
963+
fn to_variant(&self) -> Variant {
964+
match &self {
965+
Some(thing) => thing.to_variant(),
966+
None => Variant::new(),
967+
}
968+
}
969+
}
970+
971+
impl<T: FromVariant> FromVariant for Option<T> {
972+
fn from_variant(variant: &Variant) -> Option<Self> {
973+
T::from_variant(variant).map(Some).or_else(|| {
974+
if variant.is_nil() {
975+
Some(None)
976+
}
977+
else {
978+
None
979+
}
980+
})
981+
}
982+
}
983+
984+
/// Wrapper type around a `FromVariant` result that may not be a success
985+
#[derive(Clone, Debug)]
986+
pub struct MaybeNot<T>(Result<T, Variant>);
987+
988+
impl<T: FromVariant> FromVariant for MaybeNot<T> {
989+
fn from_variant(variant: &Variant) -> Option<Self> {
990+
Some(MaybeNot(T::from_variant(variant).ok_or_else(|| variant.clone())))
991+
}
992+
}
993+
994+
impl<T> MaybeNot<T> {
995+
pub fn into_result(self) -> Result<T, Variant> {
996+
self.0
997+
}
998+
999+
pub fn as_ref(&self) -> Result<&T, &Variant> {
1000+
self.0.as_ref()
1001+
}
1002+
1003+
pub fn as_mut(&mut self) -> Result<&mut T, &mut Variant> {
1004+
self.0.as_mut()
1005+
}
1006+
1007+
pub fn cloned(&self) -> Result<T, Variant>
1008+
where
1009+
T: Clone,
1010+
{
1011+
self.0.clone()
1012+
}
1013+
1014+
pub fn ok(self) -> Option<T> {
1015+
self.0.ok()
1016+
}
1017+
}
1018+
1019+
impl<T: ToVariant, E: ToVariant> ToVariant for Result<T, E> {
1020+
fn to_variant(&self) -> Variant {
1021+
let mut dict = Dictionary::new();
1022+
match &self {
1023+
Ok(val) => dict.set(&"Ok".into(), &val.to_variant()),
1024+
Err(err) => dict.set(&"Err".into(), &err.to_variant()),
1025+
}
1026+
dict.to_variant()
1027+
}
1028+
}
1029+
1030+
impl<T: FromVariant, E: FromVariant> FromVariant for Result<T, E> {
1031+
fn from_variant(variant: &Variant) -> Option<Self> {
1032+
let dict = variant.try_to_dictionary()?;
1033+
if dict.len() != 1 {
1034+
return None;
1035+
}
1036+
let keys = dict.keys();
1037+
let key_variant = keys.get_ref(0);
1038+
let key = key_variant.try_to_string()?;
1039+
match key.as_str() {
1040+
"Ok" => {
1041+
let val = T::from_variant(dict.get_ref(key_variant))?;
1042+
Some(Ok(val))
1043+
},
1044+
"Err" => {
1045+
let err = E::from_variant(dict.get_ref(key_variant))?;
1046+
Some(Err(err))
1047+
},
1048+
_ => None,
1049+
}
1050+
}
1051+
}
1052+
1053+
impl<T: ToVariant> ToVariant for &[T] {
1054+
fn to_variant(&self) -> Variant {
1055+
let mut array = VariantArray::new();
1056+
for val in self.iter() {
1057+
// there is no real way to avoid CoW allocations right now, as ptrw isn't exposed
1058+
array.push(&val.to_variant());
1059+
}
1060+
array.to_variant()
1061+
}
1062+
}
1063+
1064+
impl<T: ToVariant> ToVariant for Vec<T> {
1065+
fn to_variant(&self) -> Variant {
1066+
self.as_slice().to_variant()
1067+
}
1068+
}
1069+
1070+
impl<T: FromVariant> FromVariant for Vec<T> {
1071+
fn from_variant(variant: &Variant) -> Option<Self> {
1072+
use std::convert::TryInto;
1073+
1074+
let arr = variant.try_to_array()?;
1075+
let len: usize = arr.len().try_into().ok()?;
1076+
let mut vec = Vec::with_capacity(len);
1077+
for idx in 0..len as i32 {
1078+
let item = T::from_variant(arr.get_ref(idx))?;
1079+
vec.push(item);
1080+
}
1081+
Some(vec)
1082+
}
1083+
}
1084+
1085+
godot_test!(
1086+
test_variant_option {
1087+
use std::marker::PhantomData;
1088+
1089+
let variant = Some(42 as i64).to_variant();
1090+
assert_eq!(Some(42), variant.try_to_i64());
1091+
1092+
let variant = Option::<bool>::None.to_variant();
1093+
assert!(variant.is_nil());
1094+
1095+
let variant = Variant::new();
1096+
assert_eq!(Some(None), Option::<i64>::from_variant(&variant));
1097+
assert_eq!(Some(None), Option::<bool>::from_variant(&variant));
1098+
assert_eq!(Some(None), Option::<String>::from_variant(&variant));
1099+
1100+
let variant = Variant::from_i64(42);
1101+
assert_eq!(Some(Some(42)), Option::<i64>::from_variant(&variant));
1102+
assert_eq!(None, Option::<bool>::from_variant(&variant));
1103+
assert_eq!(None, Option::<String>::from_variant(&variant));
1104+
1105+
let variant = Variant::new();
1106+
assert_eq!(Some(Some(())), Option::<()>::from_variant(&variant));
1107+
assert_eq!(Some(Some(PhantomData)), Option::<PhantomData<*const u8>>::from_variant(&variant));
1108+
1109+
let variant = Variant::from_i64(42);
1110+
assert_eq!(None, Option::<PhantomData<*const u8>>::from_variant(&variant));
1111+
}
1112+
1113+
test_variant_result {
1114+
let variant = Result::<i64, ()>::Ok(42 as i64).to_variant();
1115+
let dict = variant.try_to_dictionary().expect("should be dic");
1116+
assert_eq!(Some(42), dict.get_ref(&"Ok".into()).try_to_i64());
1117+
1118+
let variant = Result::<(), i64>::Err(54 as i64).to_variant();
1119+
let dict = variant.try_to_dictionary().expect("should be dic");
1120+
assert_eq!(Some(54), dict.get_ref(&"Err".into()).try_to_i64());
1121+
1122+
let variant = Variant::from_bool(true);
1123+
assert_eq!(None, Result::<(), i64>::from_variant(&variant));
1124+
1125+
let mut dict = Dictionary::new();
1126+
dict.set(&"Ok".into(), &Variant::from_i64(42));
1127+
assert_eq!(Some(Ok(42)), Result::<i64, i64>::from_variant(&dict.to_variant()));
1128+
1129+
let mut dict = Dictionary::new();
1130+
dict.set(&"Err".into(), &Variant::from_i64(54));
1131+
assert_eq!(Some(Err(54)), Result::<i64, i64>::from_variant(&dict.to_variant()));
1132+
}
1133+
1134+
test_to_variant_iter {
1135+
let slice: &[i64] = &[0, 1, 2, 3, 4];
1136+
let variant = slice.to_variant();
1137+
let array = variant.try_to_array().expect("should be array");
1138+
assert_eq!(5, array.len());
1139+
for i in 0..5 {
1140+
assert_eq!(Some(i), array.get_ref(i as i32).try_to_i64());
1141+
}
1142+
1143+
let vec = Vec::<i64>::from_variant(&variant).expect("should succeed");
1144+
assert_eq!(slice, vec.as_slice());
1145+
1146+
let mut het_array = VariantArray::new();
1147+
het_array.push(&Variant::from_i64(42));
1148+
het_array.push(&Variant::new());
1149+
assert_eq!(None, Vec::<i64>::from_variant(&het_array.to_variant()));
1150+
assert_eq!(Some(vec![Some(42), None]), Vec::<Option<i64>>::from_variant(&het_array.to_variant()));
1151+
1152+
het_array.push(&f64::to_variant(&54.0));
1153+
assert_eq!(None, Vec::<Option<i64>>::from_variant(&het_array.to_variant()));
1154+
let vec_maybe = Vec::<MaybeNot<i64>>::from_variant(&het_array.to_variant()).expect("should succeed");
1155+
assert_eq!(3, vec_maybe.len());
1156+
assert_eq!(Some(&42), vec_maybe[0].as_ref().ok());
1157+
assert_eq!(Some(&Variant::new()), vec_maybe[1].as_ref().err());
1158+
assert_eq!(Some(&f64::to_variant(&54.0)), vec_maybe[2].as_ref().err());
1159+
}
1160+
);

gdnative-derive/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ proc-macro = true
1515
[dependencies]
1616
syn = { version = "0.15.29", features = ["full", "extra-traits"] }
1717
quote = "0.6.11"
18+
proc-macro2 = "^0.4.4"

0 commit comments

Comments
 (0)