mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 06:30:19 +00:00
bevy_reflect: TypeInfo
casting methods (#13320)
# Objective There are times when we might know the type of a `TypeInfo` ahead of time. Or we may have already checked it one way or another. In such cases, it's a bit cumbersome to have to pattern match every time we want to access the nested info: ```rust if let TypeInfo::List(info) = <Vec<i32>>::type_info() { // ... } else { panic!("expected list info"); } ``` Ideally, there would be a way to simply perform the cast down to `ListInfo` since we already know it will succeed. Or even if we don't, perhaps we just want a cleaner way of exiting a function early (i.e. with the `?` operator). ## Solution Taking a bit from [`mirror-mirror`](https://docs.rs/mirror-mirror/latest/mirror_mirror/struct.TypeDescriptor.html#implementations), `TypeInfo` now has methods for attempting a cast into the variant's info type. ```rust let info = <Vec<i32>>::type_info().as_list().unwrap(); // ... ``` These new conversion methods return a `Result` where the error type is a new `TypeInfoError` enum. A `Result` was chosen as the return type over `Option` because if we do choose to `unwrap` it, the error message will give us some indication of what went wrong. In other words, it can truly replace those instances where we were panicking in the `else` case. ### Open Questions 1. Should the error types instead be a struct? I chose an enum for future-proofing, but right now it only has one error state. Alternatively, we could make it a reflect-wide casting error so it could be used for similar methods on `ReflectRef` and friends. 2. I was going to do it in a separate PR but should I just go ahead and add similar methods to `ReflectRef`, `ReflectMut`, and `ReflectOwned`? 🤔 3. Should we name these `try_as_***` instead of `as_***` since they return a `Result`? ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Changelog ### Added - `TypeInfoError` enum - `TypeInfo::kind` method - `TypeInfo::as_struct` method - `TypeInfo::as_tuple_struct` method - `TypeInfo::as_tuple` method - `TypeInfo::as_list` method - `TypeInfo::as_array` method - `TypeInfo::as_map` method - `TypeInfo::as_enum` method - `TypeInfo::as_value` method - `VariantInfoError` enum - `VariantInfo::variant_type` method - `VariantInfo::as_unit_variant` method - `VariantInfo::as_tuple_variant` method - `VariantInfo::as_struct_variant` method
This commit is contained in:
parent
9f376df2d5
commit
e512cb602c
3 changed files with 237 additions and 138 deletions
|
@ -2,7 +2,9 @@ use crate::attributes::{impl_custom_attribute_methods, CustomAttributes};
|
||||||
use crate::{NamedField, UnnamedField};
|
use crate::{NamedField, UnnamedField};
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Describes the form of an enum variant.
|
/// Describes the form of an enum variant.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
@ -35,6 +37,19 @@ pub enum VariantType {
|
||||||
Unit,
|
Unit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`VariantInfo`]-specific error.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum VariantInfoError {
|
||||||
|
/// Caused when a variant was expected to be of a certain [type], but was not.
|
||||||
|
///
|
||||||
|
/// [type]: VariantType
|
||||||
|
#[error("variant type mismatch: expected {expected:?}, received {received:?}")]
|
||||||
|
TypeMismatch {
|
||||||
|
expected: VariantType,
|
||||||
|
received: VariantType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// A container for compile-time enum variant info.
|
/// A container for compile-time enum variant info.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum VariantInfo {
|
pub enum VariantInfo {
|
||||||
|
@ -85,6 +100,17 @@ impl VariantInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [type] of this variant.
|
||||||
|
///
|
||||||
|
/// [type]: VariantType
|
||||||
|
pub fn variant_type(&self) -> VariantType {
|
||||||
|
match self {
|
||||||
|
Self::Struct(_) => VariantType::Struct,
|
||||||
|
Self::Tuple(_) => VariantType::Tuple,
|
||||||
|
Self::Unit(_) => VariantType::Unit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl_custom_attribute_methods!(
|
impl_custom_attribute_methods!(
|
||||||
self,
|
self,
|
||||||
match self {
|
match self {
|
||||||
|
@ -96,6 +122,29 @@ impl VariantInfo {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_cast_method {
|
||||||
|
($name:ident : $kind:ident => $info:ident) => {
|
||||||
|
#[doc = concat!("Attempts a cast to [`", stringify!($info), "`].")]
|
||||||
|
#[doc = concat!("\n\nReturns an error if `self` is not [`VariantInfo::", stringify!($kind), "`].")]
|
||||||
|
pub fn $name(&self) -> Result<&$info, VariantInfoError> {
|
||||||
|
match self {
|
||||||
|
Self::$kind(info) => Ok(info),
|
||||||
|
_ => Err(VariantInfoError::TypeMismatch {
|
||||||
|
expected: VariantType::$kind,
|
||||||
|
received: self.variant_type(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conversion convenience methods for [`VariantInfo`].
|
||||||
|
impl VariantInfo {
|
||||||
|
impl_cast_method!(as_struct_variant: Struct => StructVariantInfo);
|
||||||
|
impl_cast_method!(as_tuple_variant: Tuple => TupleVariantInfo);
|
||||||
|
impl_cast_method!(as_unit_variant: Unit => UnitVariantInfo);
|
||||||
|
}
|
||||||
|
|
||||||
/// Type info for struct variants.
|
/// Type info for struct variants.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct StructVariantInfo {
|
pub struct StructVariantInfo {
|
||||||
|
@ -304,3 +353,28 @@ impl UnitVariantInfo {
|
||||||
|
|
||||||
impl_custom_attribute_methods!(self.custom_attributes, "variant");
|
impl_custom_attribute_methods!(self.custom_attributes, "variant");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate as bevy_reflect;
|
||||||
|
use crate::{Reflect, Typed};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_error_on_invalid_cast() {
|
||||||
|
#[derive(Reflect)]
|
||||||
|
enum Foo {
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = Foo::type_info().as_enum().unwrap();
|
||||||
|
let variant = info.variant_at(0).unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
variant.as_tuple_variant(),
|
||||||
|
Err(VariantInfoError::TypeMismatch {
|
||||||
|
expected: VariantType::Tuple,
|
||||||
|
received: VariantType::Unit
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1550,8 +1550,7 @@ mod tests {
|
||||||
bar: usize,
|
bar: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = MyStruct::type_info();
|
let info = MyStruct::type_info().as_struct().unwrap();
|
||||||
if let TypeInfo::Struct(info) = info {
|
|
||||||
assert!(info.is::<MyStruct>());
|
assert!(info.is::<MyStruct>());
|
||||||
assert_eq!(MyStruct::type_path(), info.type_path());
|
assert_eq!(MyStruct::type_path(), info.type_path());
|
||||||
assert_eq!(i32::type_path(), info.field("foo").unwrap().type_path());
|
assert_eq!(i32::type_path(), info.field("foo").unwrap().type_path());
|
||||||
|
@ -1559,9 +1558,6 @@ mod tests {
|
||||||
assert!(info.field("foo").unwrap().is::<i32>());
|
assert!(info.field("foo").unwrap().is::<i32>());
|
||||||
assert_eq!("foo", info.field("foo").unwrap().name());
|
assert_eq!("foo", info.field("foo").unwrap().name());
|
||||||
assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path());
|
assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::Struct`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: &dyn Reflect = &MyStruct { foo: 123, bar: 321 };
|
let value: &dyn Reflect = &MyStruct { foo: 123, bar: 321 };
|
||||||
let info = value.get_represented_type_info().unwrap();
|
let info = value.get_represented_type_info().unwrap();
|
||||||
|
@ -1574,16 +1570,13 @@ mod tests {
|
||||||
bar: usize,
|
bar: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = <MyGenericStruct<i32>>::type_info();
|
let info = <MyGenericStruct<i32>>::type_info().as_struct().unwrap();
|
||||||
if let TypeInfo::Struct(info) = info {
|
|
||||||
assert!(info.is::<MyGenericStruct<i32>>());
|
assert!(info.is::<MyGenericStruct<i32>>());
|
||||||
assert_eq!(MyGenericStruct::<i32>::type_path(), info.type_path());
|
assert_eq!(MyGenericStruct::<i32>::type_path(), info.type_path());
|
||||||
assert_eq!(i32::type_path(), info.field("foo").unwrap().type_path());
|
assert_eq!(i32::type_path(), info.field("foo").unwrap().type_path());
|
||||||
assert_eq!("foo", info.field("foo").unwrap().name());
|
assert_eq!("foo", info.field("foo").unwrap().name());
|
||||||
assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path());
|
assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::Struct`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: &dyn Reflect = &MyGenericStruct {
|
let value: &dyn Reflect = &MyGenericStruct {
|
||||||
foo: String::from("Hello!"),
|
foo: String::from("Hello!"),
|
||||||
|
@ -1596,27 +1589,21 @@ mod tests {
|
||||||
#[derive(Reflect)]
|
#[derive(Reflect)]
|
||||||
struct MyTupleStruct(usize, i32, MyStruct);
|
struct MyTupleStruct(usize, i32, MyStruct);
|
||||||
|
|
||||||
let info = MyTupleStruct::type_info();
|
let info = MyTupleStruct::type_info().as_tuple_struct().unwrap();
|
||||||
if let TypeInfo::TupleStruct(info) = info {
|
|
||||||
assert!(info.is::<MyTupleStruct>());
|
assert!(info.is::<MyTupleStruct>());
|
||||||
assert_eq!(MyTupleStruct::type_path(), info.type_path());
|
assert_eq!(MyTupleStruct::type_path(), info.type_path());
|
||||||
assert_eq!(i32::type_path(), info.field_at(1).unwrap().type_path());
|
assert_eq!(i32::type_path(), info.field_at(1).unwrap().type_path());
|
||||||
assert!(info.field_at(1).unwrap().is::<i32>());
|
assert!(info.field_at(1).unwrap().is::<i32>());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::TupleStruct`");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tuple
|
// Tuple
|
||||||
type MyTuple = (u32, f32, String);
|
type MyTuple = (u32, f32, String);
|
||||||
|
|
||||||
let info = MyTuple::type_info();
|
let info = MyTuple::type_info().as_tuple().unwrap();
|
||||||
if let TypeInfo::Tuple(info) = info {
|
|
||||||
assert!(info.is::<MyTuple>());
|
assert!(info.is::<MyTuple>());
|
||||||
assert_eq!(MyTuple::type_path(), info.type_path());
|
assert_eq!(MyTuple::type_path(), info.type_path());
|
||||||
assert_eq!(f32::type_path(), info.field_at(1).unwrap().type_path());
|
assert_eq!(f32::type_path(), info.field_at(1).unwrap().type_path());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::Tuple`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: &dyn Reflect = &(123_u32, 1.23_f32, String::from("Hello!"));
|
let value: &dyn Reflect = &(123_u32, 1.23_f32, String::from("Hello!"));
|
||||||
let info = value.get_represented_type_info().unwrap();
|
let info = value.get_represented_type_info().unwrap();
|
||||||
|
@ -1625,15 +1612,12 @@ mod tests {
|
||||||
// List
|
// List
|
||||||
type MyList = Vec<usize>;
|
type MyList = Vec<usize>;
|
||||||
|
|
||||||
let info = MyList::type_info();
|
let info = MyList::type_info().as_list().unwrap();
|
||||||
if let TypeInfo::List(info) = info {
|
|
||||||
assert!(info.is::<MyList>());
|
assert!(info.is::<MyList>());
|
||||||
assert!(info.item_is::<usize>());
|
assert!(info.item_is::<usize>());
|
||||||
assert_eq!(MyList::type_path(), info.type_path());
|
assert_eq!(MyList::type_path(), info.type_path());
|
||||||
assert_eq!(usize::type_path(), info.item_type_path_table().path());
|
assert_eq!(usize::type_path(), info.item_type_path_table().path());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::List`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: &dyn Reflect = &vec![123_usize];
|
let value: &dyn Reflect = &vec![123_usize];
|
||||||
let info = value.get_represented_type_info().unwrap();
|
let info = value.get_represented_type_info().unwrap();
|
||||||
|
@ -1644,15 +1628,11 @@ mod tests {
|
||||||
{
|
{
|
||||||
type MySmallVec = smallvec::SmallVec<[String; 2]>;
|
type MySmallVec = smallvec::SmallVec<[String; 2]>;
|
||||||
|
|
||||||
let info = MySmallVec::type_info();
|
let info = MySmallVec::type_info().as_list().unwrap();
|
||||||
if let TypeInfo::List(info) = info {
|
|
||||||
assert!(info.is::<MySmallVec>());
|
assert!(info.is::<MySmallVec>());
|
||||||
assert!(info.item_is::<String>());
|
assert!(info.item_is::<String>());
|
||||||
assert_eq!(MySmallVec::type_path(), info.type_path());
|
assert_eq!(MySmallVec::type_path(), info.type_path());
|
||||||
assert_eq!(String::type_path(), info.item_type_path_table().path());
|
assert_eq!(String::type_path(), info.item_type_path_table().path());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::List`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: MySmallVec = smallvec::smallvec![String::default(); 2];
|
let value: MySmallVec = smallvec::smallvec![String::default(); 2];
|
||||||
let value: &dyn Reflect = &value;
|
let value: &dyn Reflect = &value;
|
||||||
|
@ -1663,16 +1643,12 @@ mod tests {
|
||||||
// Array
|
// Array
|
||||||
type MyArray = [usize; 3];
|
type MyArray = [usize; 3];
|
||||||
|
|
||||||
let info = MyArray::type_info();
|
let info = MyArray::type_info().as_array().unwrap();
|
||||||
if let TypeInfo::Array(info) = info {
|
|
||||||
assert!(info.is::<MyArray>());
|
assert!(info.is::<MyArray>());
|
||||||
assert!(info.item_is::<usize>());
|
assert!(info.item_is::<usize>());
|
||||||
assert_eq!(MyArray::type_path(), info.type_path());
|
assert_eq!(MyArray::type_path(), info.type_path());
|
||||||
assert_eq!(usize::type_path(), info.item_type_path_table().path());
|
assert_eq!(usize::type_path(), info.item_type_path_table().path());
|
||||||
assert_eq!(3, info.capacity());
|
assert_eq!(3, info.capacity());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::Array`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: &dyn Reflect = &[1usize, 2usize, 3usize];
|
let value: &dyn Reflect = &[1usize, 2usize, 3usize];
|
||||||
let info = value.get_represented_type_info().unwrap();
|
let info = value.get_represented_type_info().unwrap();
|
||||||
|
@ -1681,13 +1657,10 @@ mod tests {
|
||||||
// Cow<'static, str>
|
// Cow<'static, str>
|
||||||
type MyCowStr = Cow<'static, str>;
|
type MyCowStr = Cow<'static, str>;
|
||||||
|
|
||||||
let info = MyCowStr::type_info();
|
let info = MyCowStr::type_info().as_value().unwrap();
|
||||||
if let TypeInfo::Value(info) = info {
|
|
||||||
assert!(info.is::<MyCowStr>());
|
assert!(info.is::<MyCowStr>());
|
||||||
assert_eq!(std::any::type_name::<MyCowStr>(), info.type_path());
|
assert_eq!(std::any::type_name::<MyCowStr>(), info.type_path());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::Value`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: &dyn Reflect = &Cow::<'static, str>::Owned("Hello!".to_string());
|
let value: &dyn Reflect = &Cow::<'static, str>::Owned("Hello!".to_string());
|
||||||
let info = value.get_represented_type_info().unwrap();
|
let info = value.get_represented_type_info().unwrap();
|
||||||
|
@ -1696,8 +1669,8 @@ mod tests {
|
||||||
// Cow<'static, [u8]>
|
// Cow<'static, [u8]>
|
||||||
type MyCowSlice = Cow<'static, [u8]>;
|
type MyCowSlice = Cow<'static, [u8]>;
|
||||||
|
|
||||||
let info = MyCowSlice::type_info();
|
let info = MyCowSlice::type_info().as_list().unwrap();
|
||||||
if let TypeInfo::List(info) = info {
|
|
||||||
assert!(info.is::<MyCowSlice>());
|
assert!(info.is::<MyCowSlice>());
|
||||||
assert!(info.item_is::<u8>());
|
assert!(info.item_is::<u8>());
|
||||||
assert_eq!(std::any::type_name::<MyCowSlice>(), info.type_path());
|
assert_eq!(std::any::type_name::<MyCowSlice>(), info.type_path());
|
||||||
|
@ -1705,9 +1678,6 @@ mod tests {
|
||||||
std::any::type_name::<u8>(),
|
std::any::type_name::<u8>(),
|
||||||
info.item_type_path_table().path()
|
info.item_type_path_table().path()
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::List`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: &dyn Reflect = &Cow::<'static, [u8]>::Owned(vec![0, 1, 2, 3]);
|
let value: &dyn Reflect = &Cow::<'static, [u8]>::Owned(vec![0, 1, 2, 3]);
|
||||||
let info = value.get_represented_type_info().unwrap();
|
let info = value.get_represented_type_info().unwrap();
|
||||||
|
@ -1716,17 +1686,14 @@ mod tests {
|
||||||
// Map
|
// Map
|
||||||
type MyMap = HashMap<usize, f32>;
|
type MyMap = HashMap<usize, f32>;
|
||||||
|
|
||||||
let info = MyMap::type_info();
|
let info = MyMap::type_info().as_map().unwrap();
|
||||||
if let TypeInfo::Map(info) = info {
|
|
||||||
assert!(info.is::<MyMap>());
|
assert!(info.is::<MyMap>());
|
||||||
assert!(info.key_is::<usize>());
|
assert!(info.key_is::<usize>());
|
||||||
assert!(info.value_is::<f32>());
|
assert!(info.value_is::<f32>());
|
||||||
assert_eq!(MyMap::type_path(), info.type_path());
|
assert_eq!(MyMap::type_path(), info.type_path());
|
||||||
assert_eq!(usize::type_path(), info.key_type_path_table().path());
|
assert_eq!(usize::type_path(), info.key_type_path_table().path());
|
||||||
assert_eq!(f32::type_path(), info.value_type_path_table().path());
|
assert_eq!(f32::type_path(), info.value_type_path_table().path());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::Map`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: &dyn Reflect = &MyMap::new();
|
let value: &dyn Reflect = &MyMap::new();
|
||||||
let info = value.get_represented_type_info().unwrap();
|
let info = value.get_represented_type_info().unwrap();
|
||||||
|
@ -1735,13 +1702,10 @@ mod tests {
|
||||||
// Value
|
// Value
|
||||||
type MyValue = String;
|
type MyValue = String;
|
||||||
|
|
||||||
let info = MyValue::type_info();
|
let info = MyValue::type_info().as_value().unwrap();
|
||||||
if let TypeInfo::Value(info) = info {
|
|
||||||
assert!(info.is::<MyValue>());
|
assert!(info.is::<MyValue>());
|
||||||
assert_eq!(MyValue::type_path(), info.type_path());
|
assert_eq!(MyValue::type_path(), info.type_path());
|
||||||
} else {
|
|
||||||
panic!("Expected `TypeInfo::Value`");
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: &dyn Reflect = &String::from("Hello!");
|
let value: &dyn Reflect = &String::from("Hello!");
|
||||||
let info = value.get_represented_type_info().unwrap();
|
let info = value.get_represented_type_info().unwrap();
|
||||||
|
@ -1881,15 +1845,12 @@ mod tests {
|
||||||
data: Vec<i32>,
|
data: Vec<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = <SomeStruct as Typed>::type_info();
|
let info = <SomeStruct as Typed>::type_info().as_struct().unwrap();
|
||||||
if let TypeInfo::Struct(info) = info {
|
|
||||||
let mut fields = info.iter();
|
let mut fields = info.iter();
|
||||||
assert_eq!(Some(" The name"), fields.next().unwrap().docs());
|
assert_eq!(Some(" The name"), fields.next().unwrap().docs());
|
||||||
assert_eq!(Some(" The index"), fields.next().unwrap().docs());
|
assert_eq!(Some(" The index"), fields.next().unwrap().docs());
|
||||||
assert_eq!(None, fields.next().unwrap().docs());
|
assert_eq!(None, fields.next().unwrap().docs());
|
||||||
} else {
|
|
||||||
panic!("expected struct info");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1910,31 +1871,20 @@ mod tests {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = <SomeEnum as Typed>::type_info();
|
let info = <SomeEnum as Typed>::type_info().as_enum().unwrap();
|
||||||
if let TypeInfo::Enum(info) = info {
|
|
||||||
let mut variants = info.iter();
|
let mut variants = info.iter();
|
||||||
assert_eq!(None, variants.next().unwrap().docs());
|
assert_eq!(None, variants.next().unwrap().docs());
|
||||||
|
|
||||||
let variant = variants.next().unwrap();
|
let variant = variants.next().unwrap().as_tuple_variant().unwrap();
|
||||||
assert_eq!(Some(" Option A"), variant.docs());
|
assert_eq!(Some(" Option A"), variant.docs());
|
||||||
if let VariantInfo::Tuple(variant) = variant {
|
|
||||||
let field = variant.field_at(0).unwrap();
|
let field = variant.field_at(0).unwrap();
|
||||||
assert_eq!(Some(" Index"), field.docs());
|
assert_eq!(Some(" Index"), field.docs());
|
||||||
} else {
|
|
||||||
panic!("expected tuple variant")
|
|
||||||
}
|
|
||||||
|
|
||||||
let variant = variants.next().unwrap();
|
let variant = variants.next().unwrap().as_struct_variant().unwrap();
|
||||||
assert_eq!(Some(" Option B"), variant.docs());
|
assert_eq!(Some(" Option B"), variant.docs());
|
||||||
if let VariantInfo::Struct(variant) = variant {
|
|
||||||
let field = variant.field_at(0).unwrap();
|
let field = variant.field_at(0).unwrap();
|
||||||
assert_eq!(Some(" Name"), field.docs());
|
assert_eq!(Some(" Name"), field.docs());
|
||||||
} else {
|
|
||||||
panic!("expected struct variant")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("expected enum info");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ArrayInfo, EnumInfo, ListInfo, MapInfo, Reflect, StructInfo, TupleInfo, TupleStructInfo,
|
ArrayInfo, EnumInfo, ListInfo, MapInfo, Reflect, ReflectKind, StructInfo, TupleInfo,
|
||||||
TypePath, TypePathTable,
|
TupleStructInfo, TypePath, TypePathTable,
|
||||||
};
|
};
|
||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
/// A static accessor to compile-time type information.
|
/// A static accessor to compile-time type information.
|
||||||
///
|
///
|
||||||
|
@ -81,6 +82,19 @@ pub trait Typed: Reflect + TypePath {
|
||||||
fn type_info() -> &'static TypeInfo;
|
fn type_info() -> &'static TypeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`TypeInfo`]-specific error.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum TypeInfoError {
|
||||||
|
/// Caused when a type was expected to be of a certain [kind], but was not.
|
||||||
|
///
|
||||||
|
/// [kind]: ReflectKind
|
||||||
|
#[error("kind mismatch: expected {expected:?}, received {received:?}")]
|
||||||
|
KindMismatch {
|
||||||
|
expected: ReflectKind,
|
||||||
|
received: ReflectKind,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// Compile-time type information for various reflected types.
|
/// Compile-time type information for various reflected types.
|
||||||
///
|
///
|
||||||
/// Generally, for any given type, this value can be retrieved one of three ways:
|
/// Generally, for any given type, this value can be retrieved one of three ways:
|
||||||
|
@ -174,6 +188,50 @@ impl TypeInfo {
|
||||||
Self::Value(info) => info.docs(),
|
Self::Value(info) => info.docs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [kind] of this `TypeInfo`.
|
||||||
|
///
|
||||||
|
/// [kind]: ReflectKind
|
||||||
|
pub fn kind(&self) -> ReflectKind {
|
||||||
|
match self {
|
||||||
|
Self::Struct(_) => ReflectKind::Struct,
|
||||||
|
Self::TupleStruct(_) => ReflectKind::TupleStruct,
|
||||||
|
Self::Tuple(_) => ReflectKind::Tuple,
|
||||||
|
Self::List(_) => ReflectKind::List,
|
||||||
|
Self::Array(_) => ReflectKind::Array,
|
||||||
|
Self::Map(_) => ReflectKind::Map,
|
||||||
|
Self::Enum(_) => ReflectKind::Enum,
|
||||||
|
Self::Value(_) => ReflectKind::Value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_cast_method {
|
||||||
|
($name:ident : $kind:ident => $info:ident) => {
|
||||||
|
#[doc = concat!("Attempts a cast to [`", stringify!($info), "`].")]
|
||||||
|
#[doc = concat!("\n\nReturns an error if `self` is not [`TypeInfo::", stringify!($kind), "`].")]
|
||||||
|
pub fn $name(&self) -> Result<&$info, TypeInfoError> {
|
||||||
|
match self {
|
||||||
|
Self::$kind(info) => Ok(info),
|
||||||
|
_ => Err(TypeInfoError::KindMismatch {
|
||||||
|
expected: ReflectKind::$kind,
|
||||||
|
received: self.kind(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conversion convenience methods for [`TypeInfo`].
|
||||||
|
impl TypeInfo {
|
||||||
|
impl_cast_method!(as_struct: Struct => StructInfo);
|
||||||
|
impl_cast_method!(as_tuple_struct: TupleStruct => TupleStructInfo);
|
||||||
|
impl_cast_method!(as_tuple: Tuple => TupleInfo);
|
||||||
|
impl_cast_method!(as_list: List => ListInfo);
|
||||||
|
impl_cast_method!(as_array: Array => ArrayInfo);
|
||||||
|
impl_cast_method!(as_map: Map => MapInfo);
|
||||||
|
impl_cast_method!(as_enum: Enum => EnumInfo);
|
||||||
|
impl_cast_method!(as_value: Value => ValueInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A container for compile-time info related to general value types, including primitives.
|
/// A container for compile-time info related to general value types, including primitives.
|
||||||
|
@ -241,3 +299,20 @@ impl ValueInfo {
|
||||||
self.docs
|
self.docs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_error_on_invalid_cast() {
|
||||||
|
let info = <Vec<i32> as Typed>::type_info();
|
||||||
|
assert!(matches!(
|
||||||
|
info.as_struct(),
|
||||||
|
Err(TypeInfoError::KindMismatch {
|
||||||
|
expected: ReflectKind::Struct,
|
||||||
|
received: ReflectKind::List
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue