use crate::{
ArrayInfo, DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicStruct, DynamicTuple,
DynamicTupleStruct, EnumInfo, Generics, ListInfo, MapInfo, PartialReflect, Reflect,
ReflectKind, SetInfo, StructInfo, TupleInfo, TupleStructInfo, TypePath, TypePathTable,
};
use core::{
any::{Any, TypeId},
fmt::{Debug, Formatter},
hash::Hash,
};
use thiserror::Error;
#[diagnostic::on_unimplemented(
message = "`{Self}` does not implement `Typed` so cannot provide static type information",
note = "consider annotating `{Self}` with `#[derive(Reflect)]`"
)]
pub trait Typed: Reflect + TypePath {
fn type_info() -> &'static TypeInfo;
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "`{Self}` does not implement `Typed` so cannot provide static type information",
note = "consider annotating `{Self}` with `#[derive(Reflect)]`"
)]
pub trait MaybeTyped: PartialReflect {
fn maybe_type_info() -> Option<&'static TypeInfo> {
None
}
}
impl<T: Typed> MaybeTyped for T {
fn maybe_type_info() -> Option<&'static TypeInfo> {
Some(T::type_info())
}
}
impl MaybeTyped for DynamicEnum {}
impl MaybeTyped for DynamicTupleStruct {}
impl MaybeTyped for DynamicStruct {}
impl MaybeTyped for DynamicMap {}
impl MaybeTyped for DynamicList {}
impl MaybeTyped for DynamicArray {}
impl MaybeTyped for DynamicTuple {}
#[diagnostic::on_unimplemented(
message = "`{Self}` can not provide dynamic type information through reflection",
note = "consider annotating `{Self}` with `#[derive(Reflect)]`"
)]
pub trait DynamicTyped {
fn reflect_type_info(&self) -> &'static TypeInfo;
}
impl<T: Typed> DynamicTyped for T {
#[inline]
fn reflect_type_info(&self) -> &'static TypeInfo {
Self::type_info()
}
}
#[derive(Debug, Error)]
pub enum TypeInfoError {
#[error("kind mismatch: expected {expected:?}, received {received:?}")]
KindMismatch {
expected: ReflectKind,
received: ReflectKind,
},
}
#[derive(Debug, Clone)]
pub enum TypeInfo {
Struct(StructInfo),
TupleStruct(TupleStructInfo),
Tuple(TupleInfo),
List(ListInfo),
Array(ArrayInfo),
Map(MapInfo),
Set(SetInfo),
Enum(EnumInfo),
Opaque(OpaqueInfo),
}
impl TypeInfo {
pub fn ty(&self) -> &Type {
match self {
Self::Struct(info) => info.ty(),
Self::TupleStruct(info) => info.ty(),
Self::Tuple(info) => info.ty(),
Self::List(info) => info.ty(),
Self::Array(info) => info.ty(),
Self::Map(info) => info.ty(),
Self::Set(info) => info.ty(),
Self::Enum(info) => info.ty(),
Self::Opaque(info) => info.ty(),
}
}
#[inline]
pub fn type_id(&self) -> TypeId {
self.ty().id()
}
pub fn type_path_table(&self) -> &TypePathTable {
self.ty().type_path_table()
}
pub fn type_path(&self) -> &'static str {
self.ty().path()
}
pub fn is<T: Any>(&self) -> bool {
self.ty().is::<T>()
}
#[cfg(feature = "reflect_documentation")]
pub fn docs(&self) -> Option<&str> {
match self {
Self::Struct(info) => info.docs(),
Self::TupleStruct(info) => info.docs(),
Self::Tuple(info) => info.docs(),
Self::List(info) => info.docs(),
Self::Array(info) => info.docs(),
Self::Map(info) => info.docs(),
Self::Set(info) => info.docs(),
Self::Enum(info) => info.docs(),
Self::Opaque(info) => info.docs(),
}
}
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::Set(_) => ReflectKind::Set,
Self::Enum(_) => ReflectKind::Enum,
Self::Opaque(_) => ReflectKind::Opaque,
}
}
impl_generic_info_methods!(self => {
match self {
Self::Struct(info) => info.generics(),
Self::TupleStruct(info) => info.generics(),
Self::Tuple(info) => info.generics(),
Self::List(info) => info.generics(),
Self::Array(info) => info.generics(),
Self::Map(info) => info.generics(),
Self::Set(info) => info.generics(),
Self::Enum(info) => info.generics(),
Self::Opaque(info) => info.generics(),
}
});
}
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(),
}),
}
}
};
}
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_opaque: Opaque => OpaqueInfo);
}
#[derive(Copy, Clone)]
pub struct Type {
type_path_table: TypePathTable,
type_id: TypeId,
}
impl Type {
pub fn of<T: TypePath + ?Sized>() -> Self {
Self {
type_path_table: TypePathTable::of::<T>(),
type_id: TypeId::of::<T>(),
}
}
#[inline]
pub fn id(&self) -> TypeId {
self.type_id
}
pub fn path(&self) -> &'static str {
self.type_path_table.path()
}
pub fn short_path(&self) -> &'static str {
self.type_path_table.short_path()
}
pub fn ident(&self) -> Option<&'static str> {
self.type_path_table.ident()
}
pub fn crate_name(&self) -> Option<&'static str> {
self.type_path_table.crate_name()
}
pub fn module_path(&self) -> Option<&'static str> {
self.type_path_table.module_path()
}
pub fn type_path_table(&self) -> &TypePathTable {
&self.type_path_table
}
pub fn is<T: Any>(&self) -> bool {
TypeId::of::<T>() == self.type_id
}
}
impl Debug for Type {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.type_path_table.path())
}
}
impl Eq for Type {}
impl PartialEq for Type {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.type_id == other.type_id
}
}
impl Hash for Type {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.type_id.hash(state);
}
}
macro_rules! impl_type_methods {
($field:ident) => {
$crate::type_info::impl_type_methods!(self => {
&self.$field
});
};
($self:ident => $expr:expr) => {
pub fn ty(&$self) -> &$crate::type_info::Type {
$expr
}
pub fn type_id(&self) -> ::core::any::TypeId {
self.ty().id()
}
pub fn type_path(&self) -> &'static str {
self.ty().path()
}
pub fn type_path_table(&self) -> &$crate::type_path::TypePathTable {
&self.ty().type_path_table()
}
pub fn is<T: ::core::any::Any>(&self) -> bool {
self.ty().is::<T>()
}
};
}
use crate::generics::impl_generic_info_methods;
pub(crate) use impl_type_methods;
#[derive(Debug, Clone)]
pub struct OpaqueInfo {
ty: Type,
generics: Generics,
#[cfg(feature = "reflect_documentation")]
docs: Option<&'static str>,
}
impl OpaqueInfo {
pub fn new<T: Reflect + TypePath + ?Sized>() -> Self {
Self {
ty: Type::of::<T>(),
generics: Generics::new(),
#[cfg(feature = "reflect_documentation")]
docs: None,
}
}
#[cfg(feature = "reflect_documentation")]
pub fn with_docs(self, doc: Option<&'static str>) -> Self {
Self { docs: doc, ..self }
}
impl_type_methods!(ty);
#[cfg(feature = "reflect_documentation")]
pub fn docs(&self) -> Option<&'static str> {
self.docs
}
impl_generic_info_methods!(generics);
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
#[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
})
));
}
}