bevy/crates/bevy_reflect/src/enums/mod.rs
radiish 262846e702
reflect: TypePath part 2 (#8768)
# Objective

- Followup to #7184.
- ~Deprecate `TypeUuid` and remove its internal references.~ No longer
part of this PR.
- Use `TypePath` for the type registry, and (de)serialisation instead of
`std::any::type_name`.
- Allow accessing type path information behind proxies.

## Solution
- Introduce methods on `TypeInfo` and friends for dynamically querying
type path. These methods supersede the old `type_name` methods.
- Remove `Reflect::type_name` in favor of `DynamicTypePath::type_path`
and `TypeInfo::type_path_table`.
- Switch all uses of `std::any::type_name` in reflection, non-debugging
contexts to use `TypePath`.

---

## Changelog

- Added `TypePathTable` for dynamically accessing methods on `TypePath`
through `TypeInfo` and the type registry.
- Removed `type_name` from all `TypeInfo`-like structs.
- Added `type_path` and `type_path_table` methods to all `TypeInfo`-like
structs.
- Removed `Reflect::type_name` in favor of
`DynamicTypePath::reflect_type_path` and `TypeInfo::type_path`.
- Changed the signature of all `DynamicTypePath` methods to return
strings with a static lifetime.

## Migration Guide

- Rely on `TypePath` instead of `std::any::type_name` for all stability
guarantees and for use in all reflection contexts, this is used through
with one of the following APIs:
  - `TypePath::type_path` if you have a concrete type and not a value.
- `DynamicTypePath::reflect_type_path` if you have an `dyn Reflect`
value without a concrete type.
- `TypeInfo::type_path` for use through the registry or if you want to
work with the represented type of a `DynamicFoo`.
  
- Remove `type_name` from manual `Reflect` implementations.
- Use `type_path` and `type_path_table` in place of `type_name` on
`TypeInfo`-like structs.
- Use `get_with_type_path(_mut)` over `get_with_type_name(_mut)`.

## Note to reviewers

I think if anything we were a little overzealous in merging #7184 and we
should take that extra care here.

In my mind, this is the "point of no return" for `TypePath` and while I
think we all agree on the design, we should carefully consider if the
finer details and current implementations are actually how we want them
moving forward.

For example [this incorrect `TypePath` implementation for
`String`](3fea3c6c0b/crates/bevy_reflect/src/impls/std.rs (L90))
(note that `String` is in the default Rust prelude) snuck in completely
under the radar.
2023-10-09 19:33:03 +00:00

636 lines
19 KiB
Rust

mod dynamic_enum;
mod enum_trait;
mod helpers;
mod variants;
pub use dynamic_enum::*;
pub use enum_trait::*;
pub use helpers::*;
pub use variants::*;
#[cfg(test)]
mod tests {
use crate as bevy_reflect;
use crate::*;
#[derive(Reflect, Debug, PartialEq)]
enum MyEnum {
A,
B(usize, i32),
C { foo: f32, bar: bool },
}
#[test]
fn should_get_enum_type_info() {
let info = MyEnum::type_info();
if let TypeInfo::Enum(info) = info {
assert!(info.is::<MyEnum>(), "expected type to be `MyEnum`");
assert_eq!(MyEnum::type_path(), info.type_path());
assert_eq!(MyEnum::type_path(), info.type_path_table().path());
assert_eq!(MyEnum::type_ident(), info.type_path_table().ident());
assert_eq!(MyEnum::module_path(), info.type_path_table().module_path());
assert_eq!(MyEnum::crate_name(), info.type_path_table().crate_name());
assert_eq!(
MyEnum::short_type_path(),
info.type_path_table().short_path()
);
// === MyEnum::A === //
assert_eq!("A", info.variant_at(0).unwrap().name());
assert_eq!("A", info.variant("A").unwrap().name());
if let VariantInfo::Unit(variant) = info.variant("A").unwrap() {
assert_eq!("A", variant.name());
} else {
panic!("Expected `VariantInfo::Unit`");
}
// === MyEnum::B === //
assert_eq!("B", info.variant_at(1).unwrap().name());
assert_eq!("B", info.variant("B").unwrap().name());
if let VariantInfo::Tuple(variant) = info.variant("B").unwrap() {
assert!(variant.field_at(0).unwrap().is::<usize>());
assert!(variant.field_at(1).unwrap().is::<i32>());
} else {
panic!("Expected `VariantInfo::Tuple`");
}
// === MyEnum::C === //
assert_eq!("C", info.variant_at(2).unwrap().name());
assert_eq!("C", info.variant("C").unwrap().name());
if let VariantInfo::Struct(variant) = info.variant("C").unwrap() {
assert!(variant.field_at(0).unwrap().is::<f32>());
assert!(variant.field("foo").unwrap().is::<f32>());
} else {
panic!("Expected `VariantInfo::Struct`");
}
} else {
panic!("Expected `TypeInfo::Enum`");
}
}
#[test]
fn dynamic_enum_should_set_variant_fields() {
// === Unit === //
let mut value = MyEnum::A;
let dyn_enum = DynamicEnum::from(MyEnum::A);
value.apply(&dyn_enum);
assert_eq!(MyEnum::A, value);
// === Tuple === //
let mut value = MyEnum::B(0, 0);
let dyn_enum = DynamicEnum::from(MyEnum::B(123, 321));
value.apply(&dyn_enum);
assert_eq!(MyEnum::B(123, 321), value);
// === Struct === //
let mut value = MyEnum::C {
foo: 0.0,
bar: false,
};
let dyn_enum = DynamicEnum::from(MyEnum::C {
foo: 1.23,
bar: true,
});
value.apply(&dyn_enum);
assert_eq!(
MyEnum::C {
foo: 1.23,
bar: true,
},
value
);
}
#[test]
fn partial_dynamic_enum_should_set_variant_fields() {
// === Tuple === //
let mut value = MyEnum::B(0, 0);
let mut data = DynamicTuple::default();
data.insert(123usize);
let mut dyn_enum = DynamicEnum::default();
dyn_enum.set_variant("B", data);
value.apply(&dyn_enum);
assert_eq!(MyEnum::B(123, 0), value);
// === Struct === //
let mut value = MyEnum::C {
foo: 1.23,
bar: false,
};
let mut data = DynamicStruct::default();
data.insert("bar", true);
let mut dyn_enum = DynamicEnum::default();
dyn_enum.set_variant("C", data);
value.apply(&dyn_enum);
assert_eq!(
MyEnum::C {
foo: 1.23,
bar: true,
},
value
);
}
#[test]
fn dynamic_enum_should_apply_dynamic_enum() {
let mut a = DynamicEnum::from(MyEnum::B(123, 321));
let b = DynamicEnum::from(MyEnum::B(123, 321));
// Sanity check that equality check works
assert!(
a.reflect_partial_eq(&b).unwrap_or_default(),
"dynamic enums should be equal"
);
a.set_variant("A", ());
assert!(
!a.reflect_partial_eq(&b).unwrap_or_default(),
"dynamic enums should not be equal"
);
a.apply(&b);
assert!(a.reflect_partial_eq(&b).unwrap_or_default());
}
#[test]
fn dynamic_enum_should_change_variant() {
let mut value = MyEnum::A;
// === MyEnum::A -> MyEnum::B === //
let mut dyn_enum = DynamicEnum::from(MyEnum::B(123, 321));
value.apply(&dyn_enum);
assert_eq!(MyEnum::B(123, 321), value);
// === MyEnum::B -> MyEnum::C === //
let mut data = DynamicStruct::default();
data.insert("foo", 1.23_f32);
data.insert("bar", true);
dyn_enum.set_variant("C", data);
value.apply(&dyn_enum);
assert_eq!(
MyEnum::C {
foo: 1.23,
bar: true
},
value
);
// === MyEnum::C -> MyEnum::B === //
let mut data = DynamicTuple::default();
data.insert(123_usize);
data.insert(321_i32);
dyn_enum.set_variant("B", data);
value.apply(&dyn_enum);
assert_eq!(MyEnum::B(123, 321), value);
// === MyEnum::B -> MyEnum::A === //
dyn_enum.set_variant("A", ());
value.apply(&dyn_enum);
assert_eq!(MyEnum::A, value);
}
#[test]
fn enum_should_iterate_fields() {
// === Unit === //
let value: &dyn Enum = &MyEnum::A;
assert_eq!(0, value.field_len());
let mut iter = value.iter_fields();
assert!(iter.next().is_none());
// === Tuple === //
let value: &dyn Enum = &MyEnum::B(123, 321);
assert_eq!(2, value.field_len());
let mut iter = value.iter_fields();
assert!(iter
.next()
.and_then(|field| field.value().reflect_partial_eq(&123_usize))
.unwrap_or_default());
assert!(iter
.next()
.and_then(|field| field.value().reflect_partial_eq(&321_i32))
.unwrap_or_default());
// === Struct === //
let value: &dyn Enum = &MyEnum::C {
foo: 1.23,
bar: true,
};
assert_eq!(2, value.field_len());
let mut iter = value.iter_fields();
assert!(iter
.next()
.and_then(|field| field
.value()
.reflect_partial_eq(&1.23_f32)
.and(field.name().map(|name| name == "foo")))
.unwrap_or_default());
assert!(iter
.next()
.and_then(|field| field
.value()
.reflect_partial_eq(&true)
.and(field.name().map(|name| name == "bar")))
.unwrap_or_default());
}
#[test]
fn enum_should_return_correct_variant_type() {
// === Unit === //
let value = MyEnum::A;
assert_eq!(VariantType::Unit, value.variant_type());
// === Tuple === //
let value = MyEnum::B(0, 0);
assert_eq!(VariantType::Tuple, value.variant_type());
// === Struct === //
let value = MyEnum::C {
foo: 1.23,
bar: true,
};
assert_eq!(VariantType::Struct, value.variant_type());
}
#[test]
fn enum_should_return_correct_variant_path() {
// === Unit === //
let value = MyEnum::A;
assert_eq!(
"bevy_reflect::enums::tests::MyEnum::A",
value.variant_path()
);
// === Tuple === //
let value = MyEnum::B(0, 0);
assert_eq!(
"bevy_reflect::enums::tests::MyEnum::B",
value.variant_path()
);
// === Struct === //
let value = MyEnum::C {
foo: 1.23,
bar: true,
};
assert_eq!(
"bevy_reflect::enums::tests::MyEnum::C",
value.variant_path()
);
}
#[test]
#[should_panic(expected = "`bevy_reflect::DynamicTuple` is not an enum")]
fn applying_non_enum_should_panic() {
let mut value = MyEnum::B(0, 0);
let mut dyn_tuple = DynamicTuple::default();
dyn_tuple.insert((123_usize, 321_i32));
value.apply(&dyn_tuple);
}
#[test]
fn should_skip_ignored_fields() {
#[derive(Reflect, Debug, PartialEq)]
enum TestEnum {
A,
B,
C {
#[reflect(ignore)]
foo: f32,
bar: bool,
},
}
if let TypeInfo::Enum(info) = TestEnum::type_info() {
assert_eq!(3, info.variant_len());
if let VariantInfo::Struct(variant) = info.variant("C").unwrap() {
assert_eq!(
1,
variant.field_len(),
"expected one of the fields to be ignored"
);
assert!(variant.field_at(0).unwrap().is::<bool>());
} else {
panic!("expected `VariantInfo::Struct`");
}
} else {
panic!("expected `TypeInfo::Enum`");
}
}
#[test]
fn enum_should_allow_generics() {
#[derive(Reflect, Debug, PartialEq)]
enum TestEnum<T: FromReflect> {
A,
B(T),
C { value: T },
}
if let TypeInfo::Enum(info) = TestEnum::<f32>::type_info() {
if let VariantInfo::Tuple(variant) = info.variant("B").unwrap() {
assert!(variant.field_at(0).unwrap().is::<f32>());
} else {
panic!("expected `VariantInfo::Struct`");
}
if let VariantInfo::Struct(variant) = info.variant("C").unwrap() {
assert!(variant.field("value").unwrap().is::<f32>());
} else {
panic!("expected `VariantInfo::Struct`");
}
} else {
panic!("expected `TypeInfo::Enum`");
}
let mut value = TestEnum::<f32>::A;
// === Tuple === //
let mut data = DynamicTuple::default();
data.insert(1.23_f32);
let dyn_enum = DynamicEnum::new("B", data);
value.apply(&dyn_enum);
assert_eq!(TestEnum::B(1.23), value);
// === Struct === //
let mut data = DynamicStruct::default();
data.insert("value", 1.23_f32);
let dyn_enum = DynamicEnum::new("C", data);
value.apply(&dyn_enum);
assert_eq!(TestEnum::C { value: 1.23 }, value);
}
#[test]
fn enum_should_allow_struct_fields() {
#[derive(Reflect, Debug, PartialEq)]
enum TestEnum {
A,
B(TestStruct),
C { value: TestStruct },
}
#[derive(Reflect, Debug, PartialEq)]
struct TestStruct(usize);
let mut value = TestEnum::A;
// === Tuple === //
let mut data = DynamicTuple::default();
data.insert(TestStruct(123));
let dyn_enum = DynamicEnum::new("B", data);
value.apply(&dyn_enum);
assert_eq!(TestEnum::B(TestStruct(123)), value);
// === Struct === //
let mut data = DynamicStruct::default();
data.insert("value", TestStruct(123));
let dyn_enum = DynamicEnum::new("C", data);
value.apply(&dyn_enum);
assert_eq!(
TestEnum::C {
value: TestStruct(123)
},
value
);
}
#[test]
fn enum_should_allow_nesting_enums() {
#[derive(Reflect, Debug, PartialEq)]
enum TestEnum {
A,
B(OtherEnum),
C { value: OtherEnum },
}
#[derive(Reflect, Debug, PartialEq)]
enum OtherEnum {
A,
B(usize),
C { value: f32 },
}
let mut value = TestEnum::A;
// === Tuple === //
let mut data = DynamicTuple::default();
data.insert(OtherEnum::B(123));
let dyn_enum = DynamicEnum::new("B", data);
value.apply(&dyn_enum);
assert_eq!(TestEnum::B(OtherEnum::B(123)), value);
// === Struct === //
let mut data = DynamicStruct::default();
data.insert("value", OtherEnum::C { value: 1.23 });
let dyn_enum = DynamicEnum::new("C", data);
value.apply(&dyn_enum);
assert_eq!(
TestEnum::C {
value: OtherEnum::C { value: 1.23 }
},
value
);
}
#[test]
fn enum_should_apply() {
let mut value: Box<dyn Reflect> = Box::new(MyEnum::A);
// === MyEnum::A -> MyEnum::A === //
value.apply(&MyEnum::A);
assert!(value.reflect_partial_eq(&MyEnum::A).unwrap_or_default());
// === MyEnum::A -> MyEnum::B === //
value.apply(&MyEnum::B(123, 321));
assert!(value
.reflect_partial_eq(&MyEnum::B(123, 321))
.unwrap_or_default());
// === MyEnum::B -> MyEnum::B === //
value.apply(&MyEnum::B(321, 123));
assert!(value
.reflect_partial_eq(&MyEnum::B(321, 123))
.unwrap_or_default());
// === MyEnum::B -> MyEnum::C === //
value.apply(&MyEnum::C {
foo: 1.23,
bar: true,
});
assert!(value
.reflect_partial_eq(&MyEnum::C {
foo: 1.23,
bar: true
})
.unwrap_or_default());
// === MyEnum::C -> MyEnum::C === //
value.apply(&MyEnum::C {
foo: 3.21,
bar: false,
});
assert!(value
.reflect_partial_eq(&MyEnum::C {
foo: 3.21,
bar: false
})
.unwrap_or_default());
// === MyEnum::C -> MyEnum::B === //
value.apply(&MyEnum::B(123, 321));
assert!(value
.reflect_partial_eq(&MyEnum::B(123, 321))
.unwrap_or_default());
// === MyEnum::B -> MyEnum::A === //
value.apply(&MyEnum::A);
assert!(value.reflect_partial_eq(&MyEnum::A).unwrap_or_default());
}
#[test]
fn enum_should_set() {
let mut value: Box<dyn Reflect> = Box::new(MyEnum::A);
// === MyEnum::A -> MyEnum::A === //
value.set(Box::new(MyEnum::A)).unwrap();
assert!(value.reflect_partial_eq(&MyEnum::A).unwrap_or_default());
// === MyEnum::A -> MyEnum::B === //
value.set(Box::new(MyEnum::B(123, 321))).unwrap();
assert!(value
.reflect_partial_eq(&MyEnum::B(123, 321))
.unwrap_or_default());
// === MyEnum::B -> MyEnum::B === //
value.set(Box::new(MyEnum::B(321, 123))).unwrap();
assert!(value
.reflect_partial_eq(&MyEnum::B(321, 123))
.unwrap_or_default());
// === MyEnum::B -> MyEnum::C === //
value
.set(Box::new(MyEnum::C {
foo: 1.23,
bar: true,
}))
.unwrap();
assert!(value
.reflect_partial_eq(&MyEnum::C {
foo: 1.23,
bar: true
})
.unwrap_or_default());
// === MyEnum::C -> MyEnum::C === //
value
.set(Box::new(MyEnum::C {
foo: 3.21,
bar: false,
}))
.unwrap();
assert!(value
.reflect_partial_eq(&MyEnum::C {
foo: 3.21,
bar: false
})
.unwrap_or_default());
// === MyEnum::C -> MyEnum::B === //
value.set(Box::new(MyEnum::B(123, 321))).unwrap();
assert!(value
.reflect_partial_eq(&MyEnum::B(123, 321))
.unwrap_or_default());
// === MyEnum::B -> MyEnum::A === //
value.set(Box::new(MyEnum::A)).unwrap();
assert!(value.reflect_partial_eq(&MyEnum::A).unwrap_or_default());
}
#[test]
fn enum_should_partial_eq() {
#[derive(Reflect)]
enum TestEnum {
A,
A1,
B(usize),
B1(usize),
B2(usize, usize),
C { value: i32 },
C1 { value: i32 },
C2 { value: f32 },
}
let a: &dyn Reflect = &TestEnum::A;
let b: &dyn Reflect = &TestEnum::A;
assert!(
a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::A == TestEnum::A"
);
let a: &dyn Reflect = &TestEnum::A;
let b: &dyn Reflect = &TestEnum::A1;
assert!(
!a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::A != TestEnum::A1"
);
let a: &dyn Reflect = &TestEnum::B(123);
let b: &dyn Reflect = &TestEnum::B(123);
assert!(
a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::B(123) == TestEnum::B(123)"
);
let a: &dyn Reflect = &TestEnum::B(123);
let b: &dyn Reflect = &TestEnum::B(321);
assert!(
!a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::B(123) != TestEnum::B(321)"
);
let a: &dyn Reflect = &TestEnum::B(123);
let b: &dyn Reflect = &TestEnum::B1(123);
assert!(
!a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::B(123) != TestEnum::B1(123)"
);
let a: &dyn Reflect = &TestEnum::B(123);
let b: &dyn Reflect = &TestEnum::B2(123, 123);
assert!(
!a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::B(123) != TestEnum::B2(123, 123)"
);
let a: &dyn Reflect = &TestEnum::C { value: 123 };
let b: &dyn Reflect = &TestEnum::C { value: 123 };
assert!(
a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::C{{value: 123}} == TestEnum::C{{value: 123}}"
);
let a: &dyn Reflect = &TestEnum::C { value: 123 };
let b: &dyn Reflect = &TestEnum::C { value: 321 };
assert!(
!a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::C{{value: 123}} != TestEnum::C{{value: 321}}"
);
let a: &dyn Reflect = &TestEnum::C { value: 123 };
let b: &dyn Reflect = &TestEnum::C1 { value: 123 };
assert!(
!a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::C{{value: 123}} != TestEnum::C1{{value: 123}}"
);
let a: &dyn Reflect = &TestEnum::C { value: 123 };
let b: &dyn Reflect = &TestEnum::C2 { value: 1.23 };
assert!(
!a.reflect_partial_eq(b).unwrap_or_default(),
"expected TestEnum::C{{value: 123}} != TestEnum::C2{{value: 1.23}}"
);
}
}