mirror of
https://github.com/bevyengine/bevy
synced 2025-01-26 03:45:16 +00:00
e512cb602c
# 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
380 lines
10 KiB
Rust
380 lines
10 KiB
Rust
use crate::attributes::{impl_custom_attribute_methods, CustomAttributes};
|
|
use crate::{NamedField, UnnamedField};
|
|
use bevy_utils::HashMap;
|
|
use std::slice::Iter;
|
|
|
|
use std::sync::Arc;
|
|
use thiserror::Error;
|
|
|
|
/// Describes the form of an enum variant.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
pub enum VariantType {
|
|
/// Struct enums take the form:
|
|
///
|
|
/// ```
|
|
/// enum MyEnum {
|
|
/// A {
|
|
/// foo: usize
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
Struct,
|
|
/// Tuple enums take the form:
|
|
///
|
|
/// ```
|
|
/// enum MyEnum {
|
|
/// A(usize)
|
|
/// }
|
|
/// ```
|
|
Tuple,
|
|
/// Unit enums take the form:
|
|
///
|
|
/// ```
|
|
/// enum MyEnum {
|
|
/// A
|
|
/// }
|
|
/// ```
|
|
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.
|
|
#[derive(Clone, Debug)]
|
|
pub enum VariantInfo {
|
|
/// Struct enums take the form:
|
|
///
|
|
/// ```
|
|
/// enum MyEnum {
|
|
/// A {
|
|
/// foo: usize
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
Struct(StructVariantInfo),
|
|
/// Tuple enums take the form:
|
|
///
|
|
/// ```
|
|
/// enum MyEnum {
|
|
/// A(usize)
|
|
/// }
|
|
/// ```
|
|
Tuple(TupleVariantInfo),
|
|
/// Unit enums take the form:
|
|
///
|
|
/// ```
|
|
/// enum MyEnum {
|
|
/// A
|
|
/// }
|
|
/// ```
|
|
Unit(UnitVariantInfo),
|
|
}
|
|
|
|
impl VariantInfo {
|
|
pub fn name(&self) -> &'static str {
|
|
match self {
|
|
Self::Struct(info) => info.name(),
|
|
Self::Tuple(info) => info.name(),
|
|
Self::Unit(info) => info.name(),
|
|
}
|
|
}
|
|
|
|
/// The docstring of the underlying variant, if any.
|
|
#[cfg(feature = "documentation")]
|
|
pub fn docs(&self) -> Option<&str> {
|
|
match self {
|
|
Self::Struct(info) => info.docs(),
|
|
Self::Tuple(info) => info.docs(),
|
|
Self::Unit(info) => info.docs(),
|
|
}
|
|
}
|
|
|
|
/// 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!(
|
|
self,
|
|
match self {
|
|
Self::Struct(info) => info.custom_attributes(),
|
|
Self::Tuple(info) => info.custom_attributes(),
|
|
Self::Unit(info) => info.custom_attributes(),
|
|
},
|
|
"variant"
|
|
);
|
|
}
|
|
|
|
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.
|
|
#[derive(Clone, Debug)]
|
|
pub struct StructVariantInfo {
|
|
name: &'static str,
|
|
fields: Box<[NamedField]>,
|
|
field_names: Box<[&'static str]>,
|
|
field_indices: HashMap<&'static str, usize>,
|
|
custom_attributes: Arc<CustomAttributes>,
|
|
#[cfg(feature = "documentation")]
|
|
docs: Option<&'static str>,
|
|
}
|
|
|
|
impl StructVariantInfo {
|
|
/// Create a new [`StructVariantInfo`].
|
|
pub fn new(name: &'static str, fields: &[NamedField]) -> Self {
|
|
let field_indices = Self::collect_field_indices(fields);
|
|
let field_names = fields.iter().map(NamedField::name).collect();
|
|
Self {
|
|
name,
|
|
fields: fields.to_vec().into_boxed_slice(),
|
|
field_names,
|
|
field_indices,
|
|
custom_attributes: Arc::new(CustomAttributes::default()),
|
|
#[cfg(feature = "documentation")]
|
|
docs: None,
|
|
}
|
|
}
|
|
|
|
/// Sets the docstring for this variant.
|
|
#[cfg(feature = "documentation")]
|
|
pub fn with_docs(self, docs: Option<&'static str>) -> Self {
|
|
Self { docs, ..self }
|
|
}
|
|
|
|
/// Sets the custom attributes for this variant.
|
|
pub fn with_custom_attributes(self, custom_attributes: CustomAttributes) -> Self {
|
|
Self {
|
|
custom_attributes: Arc::new(custom_attributes),
|
|
..self
|
|
}
|
|
}
|
|
|
|
/// The name of this variant.
|
|
pub fn name(&self) -> &'static str {
|
|
self.name
|
|
}
|
|
|
|
/// A slice containing the names of all fields in order.
|
|
pub fn field_names(&self) -> &[&'static str] {
|
|
&self.field_names
|
|
}
|
|
|
|
/// Get the field with the given name.
|
|
pub fn field(&self, name: &str) -> Option<&NamedField> {
|
|
self.field_indices
|
|
.get(name)
|
|
.map(|index| &self.fields[*index])
|
|
}
|
|
|
|
/// Get the field at the given index.
|
|
pub fn field_at(&self, index: usize) -> Option<&NamedField> {
|
|
self.fields.get(index)
|
|
}
|
|
|
|
/// Get the index of the field with the given name.
|
|
pub fn index_of(&self, name: &str) -> Option<usize> {
|
|
self.field_indices.get(name).copied()
|
|
}
|
|
|
|
/// Iterate over the fields of this variant.
|
|
pub fn iter(&self) -> Iter<'_, NamedField> {
|
|
self.fields.iter()
|
|
}
|
|
|
|
/// The total number of fields in this variant.
|
|
pub fn field_len(&self) -> usize {
|
|
self.fields.len()
|
|
}
|
|
|
|
fn collect_field_indices(fields: &[NamedField]) -> HashMap<&'static str, usize> {
|
|
fields
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, field)| (field.name(), index))
|
|
.collect()
|
|
}
|
|
|
|
/// The docstring of this variant, if any.
|
|
#[cfg(feature = "documentation")]
|
|
pub fn docs(&self) -> Option<&'static str> {
|
|
self.docs
|
|
}
|
|
|
|
impl_custom_attribute_methods!(self.custom_attributes, "variant");
|
|
}
|
|
|
|
/// Type info for tuple variants.
|
|
#[derive(Clone, Debug)]
|
|
pub struct TupleVariantInfo {
|
|
name: &'static str,
|
|
fields: Box<[UnnamedField]>,
|
|
custom_attributes: Arc<CustomAttributes>,
|
|
#[cfg(feature = "documentation")]
|
|
docs: Option<&'static str>,
|
|
}
|
|
|
|
impl TupleVariantInfo {
|
|
/// Create a new [`TupleVariantInfo`].
|
|
pub fn new(name: &'static str, fields: &[UnnamedField]) -> Self {
|
|
Self {
|
|
name,
|
|
fields: fields.to_vec().into_boxed_slice(),
|
|
custom_attributes: Arc::new(CustomAttributes::default()),
|
|
#[cfg(feature = "documentation")]
|
|
docs: None,
|
|
}
|
|
}
|
|
|
|
/// Sets the docstring for this variant.
|
|
#[cfg(feature = "documentation")]
|
|
pub fn with_docs(self, docs: Option<&'static str>) -> Self {
|
|
Self { docs, ..self }
|
|
}
|
|
|
|
/// Sets the custom attributes for this variant.
|
|
pub fn with_custom_attributes(self, custom_attributes: CustomAttributes) -> Self {
|
|
Self {
|
|
custom_attributes: Arc::new(custom_attributes),
|
|
..self
|
|
}
|
|
}
|
|
|
|
/// The name of this variant.
|
|
pub fn name(&self) -> &'static str {
|
|
self.name
|
|
}
|
|
|
|
/// Get the field at the given index.
|
|
pub fn field_at(&self, index: usize) -> Option<&UnnamedField> {
|
|
self.fields.get(index)
|
|
}
|
|
|
|
/// Iterate over the fields of this variant.
|
|
pub fn iter(&self) -> Iter<'_, UnnamedField> {
|
|
self.fields.iter()
|
|
}
|
|
|
|
/// The total number of fields in this variant.
|
|
pub fn field_len(&self) -> usize {
|
|
self.fields.len()
|
|
}
|
|
|
|
/// The docstring of this variant, if any.
|
|
#[cfg(feature = "documentation")]
|
|
pub fn docs(&self) -> Option<&'static str> {
|
|
self.docs
|
|
}
|
|
|
|
impl_custom_attribute_methods!(self.custom_attributes, "variant");
|
|
}
|
|
|
|
/// Type info for unit variants.
|
|
#[derive(Clone, Debug)]
|
|
pub struct UnitVariantInfo {
|
|
name: &'static str,
|
|
custom_attributes: Arc<CustomAttributes>,
|
|
#[cfg(feature = "documentation")]
|
|
docs: Option<&'static str>,
|
|
}
|
|
|
|
impl UnitVariantInfo {
|
|
/// Create a new [`UnitVariantInfo`].
|
|
pub fn new(name: &'static str) -> Self {
|
|
Self {
|
|
name,
|
|
custom_attributes: Arc::new(CustomAttributes::default()),
|
|
#[cfg(feature = "documentation")]
|
|
docs: None,
|
|
}
|
|
}
|
|
|
|
/// Sets the docstring for this variant.
|
|
#[cfg(feature = "documentation")]
|
|
pub fn with_docs(self, docs: Option<&'static str>) -> Self {
|
|
Self { docs, ..self }
|
|
}
|
|
|
|
/// Sets the custom attributes for this variant.
|
|
pub fn with_custom_attributes(self, custom_attributes: CustomAttributes) -> Self {
|
|
Self {
|
|
custom_attributes: Arc::new(custom_attributes),
|
|
..self
|
|
}
|
|
}
|
|
|
|
/// The name of this variant.
|
|
pub fn name(&self) -> &'static str {
|
|
self.name
|
|
}
|
|
|
|
/// The docstring of this variant, if any.
|
|
#[cfg(feature = "documentation")]
|
|
pub fn docs(&self) -> Option<&'static str> {
|
|
self.docs
|
|
}
|
|
|
|
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
|
|
})
|
|
));
|
|
}
|
|
}
|