Skip to content

Commit e512cb6

Browse files
authored
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
1 parent 9f376df commit e512cb6

File tree

3 files changed

+238
-139
lines changed

3 files changed

+238
-139
lines changed

crates/bevy_reflect/src/enums/variants.rs

+74
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use crate::attributes::{impl_custom_attribute_methods, CustomAttributes};
22
use crate::{NamedField, UnnamedField};
33
use bevy_utils::HashMap;
44
use std::slice::Iter;
5+
56
use std::sync::Arc;
7+
use thiserror::Error;
68

79
/// Describes the form of an enum variant.
810
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@@ -35,6 +37,19 @@ pub enum VariantType {
3537
Unit,
3638
}
3739

40+
/// A [`VariantInfo`]-specific error.
41+
#[derive(Debug, Error)]
42+
pub enum VariantInfoError {
43+
/// Caused when a variant was expected to be of a certain [type], but was not.
44+
///
45+
/// [type]: VariantType
46+
#[error("variant type mismatch: expected {expected:?}, received {received:?}")]
47+
TypeMismatch {
48+
expected: VariantType,
49+
received: VariantType,
50+
},
51+
}
52+
3853
/// A container for compile-time enum variant info.
3954
#[derive(Clone, Debug)]
4055
pub enum VariantInfo {
@@ -85,6 +100,17 @@ impl VariantInfo {
85100
}
86101
}
87102

103+
/// Returns the [type] of this variant.
104+
///
105+
/// [type]: VariantType
106+
pub fn variant_type(&self) -> VariantType {
107+
match self {
108+
Self::Struct(_) => VariantType::Struct,
109+
Self::Tuple(_) => VariantType::Tuple,
110+
Self::Unit(_) => VariantType::Unit,
111+
}
112+
}
113+
88114
impl_custom_attribute_methods!(
89115
self,
90116
match self {
@@ -96,6 +122,29 @@ impl VariantInfo {
96122
);
97123
}
98124

125+
macro_rules! impl_cast_method {
126+
($name:ident : $kind:ident => $info:ident) => {
127+
#[doc = concat!("Attempts a cast to [`", stringify!($info), "`].")]
128+
#[doc = concat!("\n\nReturns an error if `self` is not [`VariantInfo::", stringify!($kind), "`].")]
129+
pub fn $name(&self) -> Result<&$info, VariantInfoError> {
130+
match self {
131+
Self::$kind(info) => Ok(info),
132+
_ => Err(VariantInfoError::TypeMismatch {
133+
expected: VariantType::$kind,
134+
received: self.variant_type(),
135+
}),
136+
}
137+
}
138+
};
139+
}
140+
141+
/// Conversion convenience methods for [`VariantInfo`].
142+
impl VariantInfo {
143+
impl_cast_method!(as_struct_variant: Struct => StructVariantInfo);
144+
impl_cast_method!(as_tuple_variant: Tuple => TupleVariantInfo);
145+
impl_cast_method!(as_unit_variant: Unit => UnitVariantInfo);
146+
}
147+
99148
/// Type info for struct variants.
100149
#[derive(Clone, Debug)]
101150
pub struct StructVariantInfo {
@@ -304,3 +353,28 @@ impl UnitVariantInfo {
304353

305354
impl_custom_attribute_methods!(self.custom_attributes, "variant");
306355
}
356+
357+
#[cfg(test)]
358+
mod tests {
359+
use super::*;
360+
use crate as bevy_reflect;
361+
use crate::{Reflect, Typed};
362+
363+
#[test]
364+
fn should_return_error_on_invalid_cast() {
365+
#[derive(Reflect)]
366+
enum Foo {
367+
Bar,
368+
}
369+
370+
let info = Foo::type_info().as_enum().unwrap();
371+
let variant = info.variant_at(0).unwrap();
372+
assert!(matches!(
373+
variant.as_tuple_variant(),
374+
Err(VariantInfoError::TypeMismatch {
375+
expected: VariantType::Tuple,
376+
received: VariantType::Unit
377+
})
378+
));
379+
}
380+
}

0 commit comments

Comments
 (0)